LMAX Disruptor
repository·master·Indexed 12 days ago
https://github.com/lmax-exchange/disruptorA high-performance inter-thread messaging library designed for low-latency, high-throughput concurrency. It utilizes a ring buffer pattern to minimize contention and maximize hardware efficiency, serving as an alternative to traditional queue-based messaging. Version 4.0.0 requires Java 11 and introduces updates to the BatchEventProcessor and EventHandler interfaces.
What's inside LMAX Disruptor
- LMAX Disruptor is a high-performance inter-thread messaging library. It is designed to facilitate extremely low-latency, high-throughput communication between threads, typically used in financial trading systems and other performance-critical applications where traditional queue-based concurrency models encounter contention bottlenecks.
What is the LMAX Disruptor?
masterThe LMAX Disruptor is a high-performance, inter-thread messaging library designed for low-latency and high-throughput concurrent programming. Unlike conventional approaches that use queues to pass data between stages (which can introduce latency due to locks and cache misses), the Disruptor uses a lock-free ring buffer architecture. It is designed with "mechanical sympathy" for modern hardware to minimize CPU-level cache misses and avoid costly kernel arbitration required by locks. It is a general-purpose concurrency framework, not limited to financial applications.What is the LMAX Disruptor and when should I use it?
masterThe LMAX Disruptor is a high-performance alternative to bounded queues for exchanging data between concurrent threads. It is designed for asynchronous event processing architectures where high throughput and low latency are critical requirements.
Unlike conventional queues that conflate the concerns of producers, consumers, and data storage (leading to contention and cache misses), the Disruptor separates these concerns using a pre-allocated ring-buffer. This design minimizes write contention, reduces concurrency overhead, and is highly cache-friendly, making it ideal for building low-latency pipelines, order matching engines, or real-time risk management systems.
Recover from recoverable errors using Batch Rewind
masterWhen using a
BatchEventProcessorto process events in batches, you can implement a recovery mechanism called "Batch Rewind". This allows the processor to automatically retry a batch from the beginning if a recoverable error occurs during processing.To trigger a rewind, your event handler must throw a
RewindableException. When this exception is caught, theBatchEventProcessorconsults the configuredBatchRewindStrategyto decide whether to:- Rewind the sequence number back to the start of the current batch to reattempt processing.
- Rethrow the exception and delegate to the standard
ExceptionHandler.
For example, if a batch contains sequences 150 through 155 and a
RewindableExceptionoccurs at 153 using aSimpleBatchRewindStrategy, the execution flow will be:150, 151, 152, 153(failed -> rewind), 150, 151, 152, 153(succeeded), 154, 155.Use extended EventHandler interfaces in 4.0.0
masterIn version 4.0.0, several extension interfaces have been rolled up directly onto the
EventHandlerinterface. You can now implement these directly on your event handler to access specific lifecycle or batching capabilities:BatchStartAware: Allows handling the start of a batch.LifecycleAware: Allows handling lifecycle events.SequenceReportingEventHandler: Allows reporting sequences.
Understand Disruptor latency performance characteristics
masterThe Disruptor is designed to provide low and predictable latency compared to traditional bounded queues like
ArrayBlockingQueue.Key performance characteristics include:
- Low Mean Latency: In a three-stage pipeline, the Disruptor can achieve mean latency in the nanosecond range (e.g., ~52ns) compared to the microsecond range for
ArrayBlockingQueue. - Predictable Latency (Flat 'J' Curve): Unlike many systems where latency increases exponentially as load increases (the 'J' curve), the Disruptor's latency remains almost flat until the memory sub-system reaches saturation.
- Batching Effect: Consumers can process multiple entries up to a given threshold without contention, which helps maintain high throughput and low latency under load.
- Contention Reduction: The architecture minimizes write contention and read contention by working efficiently with modern CPU caching mechanisms.
- Low Mean Latency: In a three-stage pipeline, the Disruptor can achieve mean latency in the nanosecond range (e.g., ~52ns) compared to the microsecond range for
Understand the performance implications of memory and cache
masterTo achieve maximum performance with the Disruptor, it is important to understand how it interacts with modern CPU architectures:
- Pre-allocation: All entries in the ring buffer are pre-allocated at startup. This makes them 'immortal' from the perspective of the Garbage Collector (GC), reducing GC pressure and avoiding 'stop the world' pauses. It also ensures data is laid out contiguously to support cache striding.
- False Sharing: If two independent variables are written to by different threads but reside on the same cache line (typically 64 bytes), it causes performance degradation. The Disruptor's design aims to minimize this by separating producer and consumer concerns.
- Memory Barriers: The Disruptor uses memory barriers (implemented via
volatilein Java) to ensure that when a producer commits a sequence, the changes made to the ring buffer entries are immediately visible to the consumers. - Cache Striding: By using an array-backed ring buffer with a predictable access pattern, the Disruptor allows the CPU to effectively use its pre-fetcher to load data into cache before it is needed.
Core Concepts of the Disruptor
masterThe Disruptor is a low-latency, high-throughput concurrent ring buffer data structure. Understanding its domain language is essential for correct usage:
- Ring Buffer: The storage mechanism for
Events. It can be replaced by the user for advanced use cases. - Sequence: An identifier for a component's progress (e.g., a consumer's position). It functions similarly to an
AtomicLongbut includes padding to prevent false sharing. - Sequencer: The core engine that implements concurrent algorithms for passing data between producers and consumers.
- Sequence Barrier: Logic that determines if events are available for a consumer by referencing the published
Sequenceand dependent consumerSequences. - Wait Strategy: Determines how a consumer waits for new events (e.g., busy-spinning vs. blocking).
- Event: The unit of data passed through the system, defined by the user.
- Event Processor: The event loop for handling events;
BatchEventProcessoris the standard efficient implementation. - Event Handler: A user-implemented interface representing a consumer.
- Producer: User code that enqueues events into the Disruptor.
- Ring Buffer: The storage mechanism for
Multicast Events and Consumer Dependency Graphs
masterUnlike a standard
BlockingQueuewhere one event goes to one consumer, the Disruptor supports Multicast: every event is published to all registered consumers.To coordinate parallel consumers (e.g., ensuring business logic only runs after journaling and replication are complete), use a Consumer Dependency Graph via "gating":
- Prevent Producer Overrun: Use
RingBuffer.addGatingConsumers()to add relevant consumers to the Disruptor. - Coordinate Consumers: Construct a
SequenceBarriercontaining theSequences of the components that must complete first.
In a dependency chain, the
Sequenceronly needs to track theSequenceof the leaf nodes in the dependency tree to ensure it doesn't wrap the Ring Buffer.- Prevent Producer Overrun: Use
Event Pre-allocation
masterTo minimize Garbage Collection (GC) pressure and latency, the Disruptor uses event pre-allocation.
Instead of creating new objects for every message, you provide an
EventFactoryduring construction. The Disruptor calls this factory to pre-populate the Ring Buffer. When publishing, you do not pass a new object; instead, you obtain a reference to the existing pre-allocated object in the buffer and update its fields. This ensures memory is reused rather than reallocated.How the Disruptor manages concurrency and sequencing
masterConcurrency in the Disruptor is managed through a strict sequencing concept rather than heavy-weight locks. This approach avoids the high cost of kernel context switches and arbitration associated with traditional locks.
Key Mechanisms:
- Ring Buffer: A pre-allocated, bounded data structure that stores entries. Using a power-of-2 size allows for efficient remainder calculation via bit masking.
- Sequencing:
- Producers claim a slot in the sequence. In single-producer scenarios, this is a simple counter. In multi-producer scenarios, it uses CAS (Compare And Swap) operations.
- Consumers track their own sequences as they process entries. This allows producers to track consumer progress and prevent the ring buffer from wrapping around.
- Barriers:
- ProducerBarrier: Manages claiming slots and committing changes to make them visible to consumers.
- ConsumerBarrier: Notifies consumers when new entries are available.
- Batching Effect: When a consumer lags behind, it can process all available entries up to the current cursor in a single batch without re-engaging concurrency mechanisms, which helps the system regain pace quickly during bursts.
Getting started with the LMAX Disruptor
masterTo begin using the Disruptor, follow these recommended steps:
- Understand the core concepts: Read the Technical Paper to understand the underlying concurrency problems the Disruptor solves and its performance characteristics.
- Learn how to use it: Consult the User Guide for practical implementation instructions.
- Explore the API: Refer to the Javadoc for detailed technical specifications of the public interfaces.
- Troubleshoot and learn: Check the Frequently Asked Questions for common queries.