Recently, we open-source LMAX Disruptor, which is the key reason for our transaction system's fast throughput LMAX is a new transaction platform and claims to be able to process millions of orders per second in a single thread. Why should we open source it? We realized that some traditional ideas about high-performance programming were not right. We have found a better and faster way to share data between threads. If we do not share data in the industry, it would be too selfish. At the same time, open source also makes us feel cool.
From this site, you can download a document explaining what Disruptor is and why it is so high. I did not participate in writing this document too much. I simply inserted some punctuation marks and reorganized some sentences that I don't understand, but I am very happy that, I have improved my writing skills.
I found it a little difficult to explain all the things at once. I have prepared to explain them in part to suit my NADD listener.
Ringbuffer is introduced first. My first impression on Disruptor is ringbuffer. However, I realized that although ringbuffer is the core of the entire Disruptor mode, the access control policy of Disruptor on ringbuffer is the real key point.
Ringbuffer
What is it?
Well, as the name says, it is a ring that is connected at the beginning and end of the ring). You can use it as a buffer for data transmission between different context threads.
Okay, I drew it through a canvas. I tried to draw a sketch. I hope my obsessive-compulsive disorder won't let me draw beautiful circles and straight lines)
Basically, ringbuffer has a sequence number that points to the next available element in the array.Note: For example, the picture on the right indicates the serial number, which points to the position of index 4 of the array .)
As you keep filling in the buffer, the buffer may also be read.) The serial number will keep increasing until the ring is bypassed.
To find the element pointed to by the current serial number in the array, you can use the mod operation:
Sequence mod array length = array index
The above ringbuffer is used as an example of java's mod syntax): 12% 10 = 2. It's easy.
In fact, the ringbuffer only has 10 slots, which is totally unexpected. If the number of slots is 2 to the Npower, it is more conducive to computing based on binary computers.
Note: If the Npower of 2 is changed to a binary value of 1000,100, a number such as 10 and 1, sequence & array length-1) = array index, for example, a total of 8 slots, 3 & 8-1) = 3. HashMap uses this method to locate array elements, which is faster than modulo .)
What then?
If you read the entry about the circular buffer in Wikipedia, you will find that our implementation method is the biggest difference: no tail pointer. We only maintain a sequence number pointing to the next available location. This implementation is well thought out-the initial reason we chose to use a ring buffer is to provide reliable message transmission. We need to save the messages that have been sent by the service, so that when another service tells us that the message is not successfully received through the nak (check Note: reject response signal, we can resend them.
It sounds like loop buffer is very suitable for this scenario. It maintains a serial number pointing to the end. When receiving a request from nak (Verification Note: reject the response signal), you can resend all messages from that point to the current serial number:
The difference between the ring buffer we implement and common queues is that we do not delete the data in the buffer, that is, the data is stored in the buffer until new data overwrites them. This is why we do not need a tail pointer compared to the Wikipedia version. Ringbuffer itself does not control whether overlap is required to determine whether the overlap is part of the producer-consumer behavior model. If you are not in a rush to write a blog to describe them, you can check out the Disruptor project on your own ).
Why is it so good?
Ringbuffer adopts this data structure because it has good performance in reliable message transmission. This is enough, but it also has some other advantages.
First, because it is an array, It is faster than the linked list, and there is an easy-to-predict access mode.Note: The memory address of elements in the array is stored continuously.). This is CPU-friendly-that is, at the hardware level, elements in the array are pre-loaded. Therefore, in ringbuffer, the cpu does not need to load the next element in the array from time to time from the primary storage.Note: As long as one element is loaded to the cache row, several other adjacent elements will be loaded to the same cache row)
Second, you can pre-allocate memory for the array so that the array object will always exist unless the program is terminated ). This means that a large amount of time is not required for garbage collection. In addition, unlike the linked list, you need to create a Node object for each object added to it-corresponding. When deleting a node, You need to perform the corresponding memory cleaning operation.
Missing parts
I have not introduced in this article how to avoid overlap between ringbuffer and how to perform read and write operations on ringbuffer. You may have noticed that I compared the data structure of ringbuffer with the linked list, because I think the linked list is the standard answer to the actual problem.
When you compare Disruptor with queue-based implementations, things will become interesting. The queue usually focuses on maintaining the header and tail elements of the queue, and adding and deleting elements. None of these are mentioned in ringbuffer. This is because ringbuffer is not responsible for these tasks and we have moved these operations to the outside of the data structure ringbuffer.
Http://ifeve.com/dissecting-disruptor-whats-so-special/.