Rqueue Documentation

repository·master·Indexed 18 days ago

https://github.com/sonus21/rqueue

A job queue and producer-consumer system for Spring and Spring Boot applications. Rqueue supports background jobs, scheduled tasks, and event-driven workflows using pluggable backends such as Redis or NATS JetStream. It provides the RqueueMessageEnqueuer for task submission, @RqueueListener for message consumption, and a built-in web dashboard for monitoring queue visibility and latency.

Tokens
26.8K
Snippets
67
Records
109
Agent score
62%

What's inside Rqueue

  1. Use Generic Envelope Types with GenericMessageConverter

    master

    The default GenericMessageConverter supports single-level generic envelope types like Event<T>. The type parameter T is resolved at serialization by inspecting the runtime class of the field value.

    Constraints:

    • T must be a non-generic concrete class (e.g., Order, not List<Order>).
    • At least one field of type T on the envelope must be non-null during serialization.
    • Multi-level nesting (e.g., Wrapper<Event<T>>) is not supported.
    • List<T> is supported only if T is a non-generic concrete class.
    // A generic envelope type
    public class Event<T> {
      private String id;
      private T payload;
      // getters/setters ...
    }
    
    // Enqueue
    Event<Order> event = new Event<>("evt-123", order);
    rqueueMessageEnqueuer.enqueue("order-queue", event);
    
    // Consume
    @RqueueListener(value = "order-queue")
    public void onEvent(Event<Order> event) { ... }
    
    // The serialized form encodes both the envelope class and the type parameter:
    // {"msg":"...","name":"com.example.Event#com.example.Order"}
  2. How Hard Strict Priority works

    master

    In HARD_STRICT mode, the poller follows a strict top-down priority order without starvation prevention.

    • The poller always starts with the highest-priority queue.
    • It continues to fetch from that queue as long as messages are available.
    • It only moves to the next queue in the hierarchy when the current higher-priority queue has no messages available.
    • Warning: This mode may starve lower-priority queues if higher-priority queues are constantly receiving traffic.
  3. Configure Task and Queue Concurrency

    master

    Rqueue manages concurrency through task executors and queue-level settings:

    • Shared Executor: By default, the number of task executors is twice the number of queues. You can provide a custom ThreadPoolTaskExecutor using factory.setTaskExecutor(executor).
    • Queue-level Concurrency: Use the @RqueueListener annotation's concurrency field.
      • A fixed number (e.g., concurrency = 10) creates a dedicated task executor for that queue.
      • A range (e.g., concurrency = "5-10") also uses a dedicated executor.
      • If omitted, the queue uses the shared task executor.
    • Global Worker Limit: Use factory.setMaxNumWorkers(int) to set a global limit on workers.
    • Batch Size: The @RqueueListener batchSize field determines how many messages are fetched at once. Default is 10 for listeners with explicit concurrency, and 1 for others.

    Important Note on queueCapacity: A non-zero queueCapacity in your executor can lead to duplicate message processing if a message sits in the executor's queue longer than its visibilityTimeout. Ensure visibilityTimeout is long enough to accommodate queuing delays.

    class RqueueConfiguration {
      @Bean
      public SimpleRqueueListenerContainerFactory simpleRqueueListenerContainerFactory() {
        SimpleRqueueListenerContainerFactory factory = new SimpleRqueueListenerContainerFactory();
        //...
        factory.setMaxNumWorkers(10);
        return factory;
      }
    }
    
    class RqueueConfiguration {
    
      @Bean
      public SimpleRqueueListenerContainerFactory simpleRqueueListenerContainerFactory() {
        SimpleRqueueListenerContainerFactory factory = new SimpleRqueueListenerContainerFactory();
        //...
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("taskExecutor");
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadTaskExecutor.setMaxPoolSize(50);
        threadPoolTaskExecutor.setQueueCapacity(0);
        threadPoolTaskExecutor.afterPropertiesSet();
        factory.setTaskExecutor(threadPoolTaskExecutor);
        return factory;
      }
    }
  4. How NATS consumer names are resolved

    master

    When using @RqueueListener, the consumer name is critical for NATS durable consumers. If you do not explicitly provide a name via @RqueueListener.consumerName(), the ConsumerNameResolver generates one using the pattern: rqueue-<queue>-<bean>_<method}

    All characters outside [A-Za-z0-9_-] are collapsed to _ to comply with NATS durable-name constraints.

    @RqueueListener(queue = "my-queue", consumerName = "custom-consumer-name")
    public void handle(Message msg) { ... }
  5. Understand Rqueue NATS KV bucket usage

    master

    Rqueue uses a shared set of JetStream KV buckets for all queues. Scoping is handled via key prefixes within the buckets.

    Core Buckets:

    Bucket NamePurposeTTL Behavior
    rqueue-queue-configQueue configurations and DLQ wiringNo TTL
    rqueue-jobsExecution history per message IDCaptured from first createJob/save expiry
    rqueue-locksDistributed locks (scheduler, message-level)Captured from first acquireLock duration
    rqueue-message-metadataDelivery status, retry counts, DLQ flagsNo TTL (per-write TTL ignored)
    rqueue-workersWorker process info (host, pid, etc.)Set by rqueue.workerRegistry.workerTtl
    rqueue-worker-heartbeatsPer-(queue, worker) heartbeatsSet by rqueue.workerRegistry.queueTtl

    Important: TTL is fixed at the moment of bucket creation. Changing Rqueue properties will not update an existing bucket's TTL.

  6. Monitor Rqueue queue statistics via Micrometer

    master

    Rqueue provides built-in monitoring support using the Micrometer metrics library. This allows you to export real-time queue health data to monitoring backends like Prometheus, Datadog, or any other system supported by Micrometer.

    Key gauge metrics available include:

    • queue.size: Number of tasks currently waiting in the queue.
    • dead.letter.queue.size: Number of tasks moved to the dead letter queue after repeated failures.
    • scheduled.queue.size: Approximate number of tasks scheduled for future execution.
    • processing.queue.size: Approximate number of tasks currently being processed by workers.
  7. NATS Backend Capabilities and Limitations

    master

    When using the NATS backend (via rqueue-nats), certain features available in the Redis backend are not supported due to the architectural differences of NATS JetStream.

    Supported Operations:

    • pauseUnpauseQueue via NatsRqueueUtilityService
    • getDataType
    • aggregateDataCounter
    • soft deleteMessage

    Unsupported Operations (will throw BackendCapabilityException or UnsupportedOperationException):

    • Message Manipulation: moveMessage, enqueueMessage, and makeEmpty are not supported as JetStream does not provide equivalent primitives.
    • Scheduling: Delayed, scheduled, or cron messages are not supported.
    • Advanced Queueing: Cross-queue priorityGroup weighting is not honored (will trigger a boot warning).
    • Concurrency: Elastic @RqueueListener.concurrency (where min < max) falls back to a fixed max concurrency.
    • Handler Configuration: @RqueueHandler(primary) is ignored (will trigger a boot warning).
  8. How Multicast retries work and how to ensure idempotency

    master

    In Multicast mode, the primary listener is responsible for managing retries. If the primary listener fails, the entire message is retried.

    Crucially, during a retry, all handlers (both primary and secondary) may be invoked again. This means that even if a secondary handler succeeded during the first attempt, it might be called a second time during the retry of the primary handler.

    Requirement: Ensure all handlers are idempotent when using multicasting to prevent duplicate processing side effects.

  9. Listen to Rqueue Execution and Lifecycle Events

    master

    Rqueue publishes Spring Application Events that you can listen to for monitoring and lifecycle management:

    • Job/Task Execution Events: After a task completes, Rqueue publishes an RqueueExecutionEvent. Use this to monitor job performance and outcomes.
    • Container Lifecycle Events: When an RqueueListenerContainer starts or stops, it publishes an RqueueBootstrapEvent. Use this for setup or cleanup operations during system initialization or shutdown.
  10. How Strict Priority works

    master

    In STRICT mode, the poller prefers the highest-priority queue but implements starvation prevention.

    • The poller attempts to poll the highest-priority queue (e.g., Q1) first.
    • If Q1 is empty, it moves to Q2, then Q3.
    • If a queue is empty, it becomes inactive for the duration of the polling interval.
    • To prevent permanent starvation, if messages are not fetched from a lower-priority queue for a certain interval, that queue becomes eligible to be polled even if higher-priority queues are still receiving traffic.
  11. Use middleware to intercept message processing

    master

    Middleware allows you to execute common logic across all message listeners in your application. It is useful for cross-cutting concerns such as:

    • Logging and auditing: Recording job details.
    • Profiling: Measuring listener performance.
    • Transaction Management: Managing database transactions, distributed tracing, or tools like New Relic.
    • Rate Limiting: Restricting the number of jobs processed (e.g., 10 jobs per minute).
    • Sequential Execution: Ensuring specific tasks run one at a time.
    • Access Control: Checking for user bans or enforcing permissions.

    Middleware is invoked in the order it is registered, allowing you to build a structured processing pipeline.