Original address: http://coderbee.net/index.php/open-source/20130812/400
What is Disruptor?
Disruptor is a high-performance asynchronous processing framework that can also be thought of as a message framework that implements the observer pattern.
Disruptor has the advantage over the traditional lock-based message framework: It is lock-free, CPU-friendly, it does not erase the data in the cache, it only overwrites, reducing the frequency at which the garbage collection mechanism starts.
This interpretation is on the latest version of the 3.1.1 source.
More about disruptor visible: http://ifeve.com/disruptor/
Second, disruptor why fast
- Do not use locks. Replace the lock with a memory barrier and atomic CAS operation.
The cache is based on an array instead of a linked list and uses bitwise operations instead of modulo. The length of the cache is always 2 of the n-th square, which can be replaced with bitwise operations i & (length - 1)
i % length
.
Remove pseudo-shares. CPU cache is generally the smallest unit of cache behavior, corresponding to the main memory of a corresponding size unit; The current cache row size is typically 64 bytes, each cache row can only be accessed by one CPU at a time, and if a cache line is accessed by multiple CPU cores, it will cause competition, Causes a kernel to wait for other cores to finish processing to respond to performance. Removing pseudo-shares is to ensure that the CPU cores do not compete when they access a cache row.
Pre-allocating cache objects to reduce garbage collection by updating the properties of objects in the cache rather than deleting objects.
Third, disruptor architecture core class and interface
-
EventHandler
: The user provides a concrete implementation that implements the processing logic for the event.
-
Sequence
: Represents an event ordinal or an ordinal that points to a location in the cache.
-
waitstrategy
: Features include: when there are no consumable events, wait according to the specific implementation, there are consumable events to return the event sequence number, there is a new event when the announcement of the waiting Sequencebarrier
.
-
Sequencer
: The controller that the producer uses to access the cache, which holds a reference to the consumer serial number; After the new event is published through waitstrategy
notifications waiting for Sequencebarrier
.
-
sequencebarrier
: consumer levels. The controller that the consumer uses to access the cache, each access controller also holds a reference to the predecessor Access controller to maintain the correct sequence of events, and to obtain the consumable event sequence number through waitstrategy
.
-
eventprocessor
: The event handler, which is the executable unit, runs in the specified executor, and it continuously acquires consumable events through sequencebarrier
. Invokes the user-supplied when there is a consumable event, and the EventHandler
implementation handles the event.
-
eventtranslator
: Event converter, because disruptor only overwrites the cache, you need to update the events in the cache with the implementation of this interface to overwrite the old event.
-
ringbuffer
: An array-based cache implementation that internally holds the Executor
, waitstrategy
, References to producer and consumer access controllers.
-
disruptor
: Provides a package for Ringbuffer
, and provides some DSL-style methods for ease of use.
Each event handler EventProcessor
holds a sequence number that represents the event it was last processed Sequence
, so it can be used Sequence
to represent the event handler;
Here is an example diagram of event handling in Disruptor:
IV. Realization of Sequence class
The sequence class represents a sequence number, which is a thread-safe encapsulation of a long field that tracks the progress of Ringbuffer and the progress of the event processor.
Supports some concurrent operations, including CAs and ordered write.
Try populating content around the volatile field to avoid pseudo-sharing and become more efficient.
Realize
public class Sequence {static final long initial_value = -1l; Private static final unsafe unsafe; Private static final long value_offset; static {UNSAFE = Util.getunsafe (); Final int base = Unsafe.arraybaseoffset (Long[].class); Final int scale = Unsafe.arrayindexscale (Long[].class); Value_offset = base + (scale * 7); }//15 elements, starting from 0, the valid value is in 7th, so that there are 7 long field padding,//8 long for the total of 64 bytes, and the current CPU cache row size is 64 bytes, this can avoid the read and write of sequence pseudo-share. Private final long [] paddedvalue = new long [15]; Atomic read public long get () {return UNSAFE. Getlongvolatile (Paddedvalue, Value_offset); }//atomically writes public void set (final Long value) {Unsafe.putorderedlong (Paddedvalue, Value_offset, value); }//CAS public boolean compareandset (final long expectedvalue, final long NewValue) {return UNSAFE. Compare Andswaplong (Paddedvalue, Value_offset, Expectedvalue, NewValue); } Public long Addandget (final long increment) { Long CurrentValue; Long NewValue; do {currentvalue = get (); NewValue = CurrentValue + increment; } while (!compareandset (CurrentValue, newvalue)); return newvalue; }//There are other methods that are implemented using the Sun.misc.Unsafe class.
Pseudo Share
For pseudo-sharing, refer to:
Disruptor class
The Disruptor class is the façade of this class library, providing a visual representation of the function of the chain that assembles event callback processing in the form of a DSL, and provides a way to get events, publish events, and cache container life cycle management.
Property
Private final Ringbuffer Ringbuffer; Core, most of the functions are entrusted to ringbuffer processing private final Executor Executor; thread pool for executing event handlers private final consumerrepository consumerrepository = new Consumerrepository (); Event Processor Repository, which is the collection of event handlers private final Atomicboolean started = new Atomicboolean (false); Start-up check, can only start once private exceptionhandler Exceptionhandler; Exception handler
Setting up the EventHandler event handler
/* * Barriersequences is Eventhandlers's pre-event handling level, which is the key to ensuring the timing of event processing; * */eventhandlergroup createeventprocessors (final Sequence[] barriersequences, final eventhandler[] eventhandlers) {CHECKNOTST Arted (); Make sure the final sequence[] processorsequences = new Sequence[eventhandlers.length] is set before the container starts; The array that holds the cursor is final sequencebarrier barrier = Ringbuffer.newbarrier (barriersequences); Gets the ordinal level for the preceding (int i = 0, eventhandlerslength = eventhandlers.length; i < eventhandlerslength; i++) {fin Al EventHandler EventHandler = eventhandlers[i]; Encapsulated as a bulk event processor Batcheventprocessor, which implements the Runnable interface, so it can be put into executor to execute processing logic, the processor will also automatically establish an ordinal sequence. Final Batcheventprocessor batcheventprocessor = new Batcheventprocessor (Ringbuffer, Barrier, EventHandler); if (Exceptionhandler! = null) {//if any, set exception handler Batcheventprocessor.setexceptionhandler (Exceptionhandler); }//Added to the consumer warehouse, it is encapsulated as a Eventprocessorinfo object (representing the eventA stage of processing), Consumerrepository.add (Batcheventprocessor, EventHandler, barrier); Processorsequences[i] = Batcheventprocessor.getsequence (); } if (processorsequences. length > 0) {//If there is a pre-level, cancels the end tag of the eventprocessor chain corresponding to the preceding level. Consumerrepository.unmarkeventprocessorsasendofchain (barriersequences); }//Eventhandlergroup is a set of eventprocessor that, as part of Disruptor, provides a method of DSL form, as a starting point for the method chain, for setting up the event handler. return new Eventhandlergroup (this, consumerrepository, processorsequences);}
It is important to note that EventHandler can only be added before startup.
From the code point of view, EventHandler is a user-provided, simple implementation of the event processing logic, before being added to the consumer warehouse, it will be encapsulated as a Eventprocessor object.
Ringbuffer class Properties
First look at the properties of the Ringbuffer class:
the initialization of the property declares public static final long initial_cursor_value = Sequence.initial_value; private final int indexmask; Private final object[] entries; private final int buffersize; Private final Sequencer Sequencer;// Property initialization code ringbuffer (eventfactory eventfactory, Sequencer Sequencer) { this.sequencer = sequencer; This.buffersize = Sequencer.getbuffersize (); if (BufferSize < 1) { throw new IllegalArgumentException ("buffersize must not is less than 1"); } if (Integer.bitcount (buffersize)! = 1) { throw new IllegalArgumentException ("buffersize must be a power of 2");
} this.indexmask = bufferSize-1; This.entries = new Object[sequencer.getbuffersize ()]; Fill (eventfactory); }
The code shows that:
- Ringbuffer is built on an array, because the array is cache-friendly, and adjacent elements are generally in the same cache block.
The size of the cache must be 2 x times, this is to improve performance with bit operations, because the capacity of the array cache is always limited, when the cache fills up, but also starting from subscript 0, if the cache size is not 2 x-square, it can only use modulo operation to obtain a new subscript, so there is a indexmask to save the subscript mask , a safe subscript can be obtained by indexmask with the------no need for subscript checking, such as: (E)entries[( int)sequence & indexMask ]
.
Get events from cache
Gets the event of the specified ordinal from the cache public E get (long sequence) { //This bitwise AND operation explains why the size of the Ringbuffer must be 2 of the n-th square: use efficient bitwise AND instead of inefficient modulo operations. return (E) entries[(int) sequence & Indexmask];}
Publish Events
There are 3 steps to publishing an event: Get the serial number of the new event, overwrite the old event, and wait for the notification. The simplest form of publishing events:
public void Publishevent (Eventtranslator translator) { final long sequence = Sequencer. Next ();//Get available serial number by producer serial number Controller
translateandpublish (translator, sequence); Convert event to queue cache and publish event}private void Translateandpublish (eventtranslator translator, long sequence) { try { // Before you publish an event, get the old event at the corresponding location, and then use translator to convert the properties of the new event to the properties of the old event to achieve the purpose of the publication. ///That is, disruptor is not deleted for consumed events, and new events only replace the properties of the old event with the properties of the new event. //This brings up a problem with memory consumption translator.translateto (get (sequence), sequence); } finally { sequencer.publish ( sequence); atomically updates the serial number of the producer and notifies the waiting consumer level. }}
It should be noted that the producer serial number controller and the consumer level are sharing the same wait policy, and a disruptor container has only one waiting policy instance.
Eventprocessor
The execution unit of the event handler. There are two implementations: NoOpEventProcessor
and BatchEventProcessor
, where the NoOpEventProcessor
event is not handled, it is not concerned.
Batcheventprocessor
The only useful implementation class provided by Disruptor EventProcessor
.
Disruptor the method that will be called when the container is started, ConsumerInfo
start
if ConsumerInfo
it encapsulates a user-submitted EventHandler
instance, it will run constructor online EventProcessor
, that is, BatchEventProcessor
the method of the instance run
.
Core Run method
The document description of the method mentions that the method halt
can be re-executed after invoking the method.
public void Run () {//ensures that only one thread at a time executes this method, so that the sequence number of the access itself is not locked if (! Running.compareandset (False, True)) {throw new I Llegalstateexception ("Thread is already running"); } sequencebarrier.clearalert (); Clears the notification status of the front ordinal level notifystart (); Declaration period notification, before start callback T event = null; Long nextsequence = Sequence.get () + 1L; Sequence points to the last processed event, which is-1 by default. try {while (true) {try {///from its front ordinal level to get the next number of events that can be processed. If the event handler is not dependent on the other event handlers, the front level is the producer serial number;//If the event handler relies on 1 or more event handlers, then this front level is the slowest of these pre-event handlers. By doing this, you can ensure that the event handler does not advance to the event. Final Long availablesequence = Sequencebarrier.waitfor (nextsequence); Handles a batch of events while (NextSequence <= availablesequence) {event = Dataprovider.get (Nextse Quence); Eventhandler.onevent (event, nextsequence, nextsequence = = availablesequence); nextsequence++; } Sets its own last-processed event sequence so that it relies on its processor to handle events that it has just handled. Sequence.set (availablesequence); } catch (Final TimeoutException e) {//Get event Sequence timeout processing notifytimeout (Sequence.get ()); } catch (Final Alertexception ex) {//Handles notification events, detects if you want to stop, if not continue processing event if (!running. Get ()) { Break }} catch (Final Throwable ex) {///other exception, handled with the event handler, and proceed with the next event Exceptionhandler.ha Ndleeventexception (ex, NextSequence, event); Sequence.set (nextsequence); nextsequence++; }}} finally {//Declaration period notification, stop event callback, reset run status flag, ensure that this method can be run again. Notifyshutdown (); Running.set (FALSE); }}
while
as you can see from the loop, event handling can be divided into three steps:
- Gets
SequenceBarrier
The maximum number of events that can be processed from the fetch;
- Cyclic processing can handle events;
- Updates its own handled sequence number so that the event handler that relies on it can continue processing.
sequence .set
And sequence .get
methods are atomically read, update the ordinal, so that the lock is avoided, thereby providing performance.
sequenceBarrier .waitFor
The method is also called eventually sequence .get
.
Sequencebarrier
A collaborative level that tracks the data structure of producer cursors and the ordinal of dependent event handlers. There are two implementations DummySequenceBarrier
and ProcessingSequenceBarrier
, as the name of the class, the former is virtual, only the empty method, the latter is practical.
Sequencebarrier Interface Definition
Public interface Sequencebarrier { //wait for the specified ordinal to become consumable long waitFor (long sequence) throws Alertexception, Interruptedexception, timeoutexception; Returns the currently readable cursor (an ordinal) long getcursor (); Whether there is currently a notification status to this level , Boolean isalerted (); Notifies the event that the processor state has changed and remains in this state until the Void alert () is cleared; Clears the current notification status void Clearalert (); Check the notification status and throw void Checkalert () throws Alertexception if there is an exception;
Processingsequencebarrier generation
Instances of Processingsequencebarrier are controlled by the framework.
First, within the Createeventprocessors method of the Disruptor class: final sequencebarrier barrier = Ringbuffer.newbarrier (barriersequences); Get the Newbarrier method for the pre-numbered level ringbufferd: public sequencebarrier newbarrier (Sequence ... sequencestotrack) { return Sequencer.newbarrier (sequencestotrack); is generated by the producer serial number controller. The Newbarrier method of}abstractsequencer. Public Sequencebarrier newbarrier (Sequence ... sequencestotrack) { return new Processingsequencebarrier (this, Waitstrategy, cursor, sequencestotrack);}
constructor function
/** * @param sequencer producer Serial Number Controller * @param waitstrategy wait policy * @param cursorsequence producer Serial number * @param dependentsequences dependent SE Quence */public processingsequencebarrier (final Sequencer Sequencer, final waitstrategy Waitstrategy, final Sequence cursorsequence, Final sequence[] dependentsequences) {this . Sequencer = Sequencer; This. Waitstrategy = Waitstrategy; This. Cursorsequence = cursorsequence; If the event handler is not dependent on any predecessor processor, then the dependentsequence also points to the serial number of the producer. if (0 = = dependentsequences. Length) { dependentsequence = cursorsequence; } else { //If there is more than one predecessor processor, It is encapsulated, and the combined mode is implemented. dependentsequence = new Fixedsequencegroup (dependentsequences);} }
How to get the ordinal number
/** * This method does not guarantee that an unhandled ordinal will always be returned, and if there are more processing ordinals, the returned ordinal may be more than the specified ordinal. */public Long WaitFor (final long sequence) throws Alertexception, Interruptedexception, timeoutexception { // First check if there is no notification checkalert (); By waiting for the policy to get the number of processing events, long availablesequence = waitstrategy.waitfor (sequence, cursorsequence, Dependentsequence, this); This method does not guarantee that the return availablesequence is always returned as a processed ordinal ; if (Availablesequence < sequence) { } //re-returns the largest processed ordinal return via the producer serial number controller Sequencer.gethighestpublishedsequence (sequence, availablesequence);}
Waitstrategy
Waitstrategy defines a wait policy for a eventprocessor when there are no consumable events in sequence.
Interface definition
Public interface Waitstrategy { /** * If the event handler is not dependent on any predecessor processor, then both the cursor and the dependentsequence will point to the producer's ordinal number. * * sequence: To get the ordinal number * cursor: Point to the producer's sequence * dependentsequence: The caller relies on the front level * Barrier: The caller itself, By calling Barrier.checkalert you can respond to notifications in a timely manner * /Long waitFor (long sequence, sequence cursor, sequence dependentsequence, Sequencebarrier barrier) throws Alertexception, Interruptedexception, timeoutexception; Notifies eventprocessor void signalallwhenblocking () when the cursor advances (there are events that can be handled) ;}
Realize
Blockingwaitstrategy: Blocking waits for the producer to wake up when there are no consumable events.
Busyspinwaitstrategy: Busy and other strategies.
Phasedbackoffwaitstrategy:
Timeoutblockingwaitstrategy:
Yieldingwaitstrategy: By invoking Thread.yield
the method to give up the CPU, to achieve the purpose of waiting, waiting time is not guaranteed, depending on the thread scheduling system.
Summary
Disruptor provides a CPU-friendly, thread-safe ordinal representation with buffer row padding and the appropriate encapsulation.
With each consumer holding its own event serial number, no interdependent consumer can handle the event in parallel. Consumer levels are introduced between consumers, which easily realizes the relationship between consumers and their dependencies.
For producers, even if multiple threads are accessed concurrently, since they both access the Ringbuffer,disruptor framework via the serial number sequencer to replace the locking and synchronization blocks with CAs, this is also a guideline for concurrent programming: Minimizing the synchronization block to a variable.
The use of the lock is eliminated by the atomic read-write sequence number and the CAS operation, which improves the performance.
One drawback of distuptor is memory footprint: because it does not purge old event data.
Appendix:
Http://developer.51cto.com/art/201306/399370.htm
Disruptor Source Reading notes--turn