开发者社区> ali清英> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Dissecting the Disruptor: Writing to the ring buffer

简介:
+关注继续查看

原文地址:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-writing-to-ring.html(因被墙移到墙内)  作者:Trisha

This is the missing piece in the end-to-end view of the Disruptor.  Brace yourselves, it’s quite long.  But I decided to keep it in a single blog so you could have the context in one place.

The important areas are: not wrapping the ring; informing the consumers; batching for producers; and how multiple producers work.

ProducerBarriers
The Disruptor code has interfaces and helper classes for the Consumers, but there’s no interface for your producer, the thing that writes to the ring buffer.  That’s because nothing else needs to access your producer, only you need to know about it.  However, like the consuming side, aProducerBarrier is created by the ring buffer and your producer will use this to write to it.

Writing to the ring buffer involves a two-phase commit.  First, your producer has to claim the next slot on the buffer.  Then, when the producer has finished writing to the slot, it will call commit on theProducerBarrier.

So let’s look at the first bit.  It sounds easy – “get me the next slot on the ring buffer”.  Well, from your producer’s point of view it is easy.  You simply call nextEntry() on the ProducerBarrier.  This will return you an Entry object which is basically the next slot in the ring buffer.

The ProducerBarrier makes sure the ring buffer doesn’t wrap
Under the covers, the ProducerBarrier is doing all the negotiation to figure out what the next slot is, and if you’re allowed to write to it yet.

(I’m not convinced the shiny new graphics tablet is helping the clarity of my pictures, but it’s fun to use).

For this illustration, we’re going to assume there’s only one producer writing to the ring buffer.  We will deal with the intricacies of multiple producers later.

The ConsumerTrackingProducerBarrier has a list of all the Consumers that are accessing the ring buffer.  Now to me this seemed a bit odd – I wouldn’t expect the ProducerBarrier to know anything about the consuming side. But wait, there is a reason.  Because we don’t want the “conflation of concerns” a queue has (it has to track the head and tail which are sometimes the same point), our consumers are responsible for knowing which sequence number they’re up to, not the ring buffer.  So, if we want to make sure we don’t wrap the buffer, we need to check where the consumers have got to.

In the diagram above, one Consumer is happily at the same point as the highest sequence number (12, highlighted in red/pink). The second Consumer is a bit behind – maybe it’s doing I/O operations or something – and it’s at sequence number 3.  Therefore consumer 2 has the whole length of the buffer to go before it catches up with consumer 1.

The producer wants to write to the slot on the ring buffer currently occupied by sequence 3, because this slot is the one after the current ring buffer cursor.  But the ProducerBarrier knows it can’t write here because a Consumer is using it.  So the ProducerBarrier sits and spins, waiting, until the consumers move on.

Claiming the next slot
Now imagine consumer 2 has finished that batch of entries, and moves its sequence number on. Maybe it got as far as sequence 9 (in real life I expect it will make it as far as 12 because of the way consumer batching works, but that doesn’t make the example as interesting).

The diagram above shows what happens when consumer 2 updates to sequence number 9.  I’ve slimmed down the ConsumerBarrier in this picture because it takes no active part in this scene.

The ProducerBarrier sees that the next slot, the one that had sequence number 3, is now available.  It grabs the Entry that sits in this slot (I’ve not talked specifically about the Entry class, but it’s basically a bucket for stuff you want to put into the ring buffer slot which has a sequence number), sets the sequence number on the Entry to the next sequence number (13) and returns this entry to your producer.  The producer can then write whatever value it wants into this Entry.

Committing the new value
The second phase of the two-stage commit is, well, the commit.

The green represents our newly updated Entry with sequence 13 – yeah, I’m sorry, I’m red-green colour-blind too.  But other colours were even more rubbish.

When the producer has finished writing stuff into the entry it tells the ProducerBarrier to commit it.

The ProducerBarrier waits for the ring buffer cursor to catch up to where we are (for a single producer this will always be a bit pointless – e.g. we know the cursor is already at 12, nothing else is writing to the ring buffer).  Then the ProducerBarrier updates the ring buffer cursor to the sequence number on the updated Entry – 13 in our case.  Next, the ProducerBarrier lets the consumers know there’s something new in the buffer.  It does this by poking the WaitStrategy on theConsumerBarrier – “Oi, wake up! Something happened!” (note – different WaitStrategyimplementations deal with this in different ways, depending upon whether it’s blocking or not).

Now consumer 1 can get entry 13, consumer 2 can get everything up to and including 13, and they all live happily ever after.

ProducerBarrier batching
Interestingly the disruptor can batch on the producer side as well as on the Consumer side.  Remember when consumer 2 finally got with the programme and found itself at sequence 9?  There is a very cunning thing the ProducerBarrier can do here – it knows the size of the buffer, and it knows where the slowest Consumer is.  So it can figure out which slots are now available.

If the ProducerBarrier knows the ring buffer cursor is at 12, and the slowest Consumer is at 9, it can let producers write to slots 3, 4, 5, 6, 7 and 8 before it needs to check where the consumers are.

Multiple producers
You thought I was done, but there’s more.

I slightly lied in some of the above drawings.  I implied that the sequence number theProducerBarrier deals with comes directly from the ring buffer’s cursor.  However, if you look at the code you’ll see that it uses the ClaimStrategy to get this.  I skipped this to simplify the diagrams, it’s not so important in the single-producer case.

With multiple producers, you need yet another thing tracking a sequence number.  This is the sequence that is available for writing to.  Note that this is not the same as ring-buffer-cursor-plus-one – if you have more than one producer writing to the buffer, it’s possible there are entries in the process of being written that haven’t been committed yet.

Let’s revisit claiming a slot.  Each producer asks the ClaimStrategy for the next available slot.  Producer 1 gets sequence 13, like in the single producer case above.  Producer 2 gets sequence 14, even though the ring buffer cursor is still only pointing to 12, because the ClaimSequence is dishing out the numbers and has been keeping track of what’s been allocated.

So each producer has its own slot with a shiny new sequence number.

I’m going colour producer 1 and its slot in green, and producer 2 and its slot in a suspiciously pink-looking purple.

Now imaging producer 1 is away with the fairies, and hasn’t got around to committing for whatever reason.  Producer 2 is ready to commit, and asks the ProducerBarrier to do so.
As we saw in the earlier commit diagram, the ProducerBarrier is only going to commit when the ring buffer cursor reaches the slot behind the one it wants to commit into.  In this case, the cursor needs to reach 13 so that we can commit 14.  But we can’t, because producer 1 is staring at something shiny and hasn’t committed yet.  So the ClaimStrategy sits there spinning until the ring buffer cursor gets to where it should be.

Now producer 1 wakes up from its coma and asks to commit entry 13 (green arrows are sparked by the request from producer 1).  The ProducerBarrier tells the ClaimStrategy to wait for the ring buffer cursor to get to 12, which it already had of course.  So the ring buffer cursor is incremented to 13, and the ProducerBarrier pokes the WaitStrategy to let everything know the ring buffer was updated.  Now the ProducerBarrier can finish the request from producer 2, increment the ring buffer cursor to 14, and let everyone know that we’re done.

You’ll see that the ring buffer retains the ordering implied by the order of the initial nextEntry() calls, even if the producers finish writing at different times.  It also means that if a producer is causing a pause in writing to the ring buffer, when it unblocks any other pending commits can happen immediately.

Phew.  And I managed to describe all that without mentioning a memory barrier once.

EDIT: The most recent version of the RingBuffer hides away the Producer Barrier.  If you can’t see a ProducerBarrier in the code you’re looking at, then assume where I say “producer barrier” I mean “ring buffer”

EDIT 2: Note that version 2.0 of the Disruptor uses different names to the ones in this article.  Please see my summary of the changes if you are confused about class names.

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
composer的基本使用和常见报错解决
composer的基本使用和常见报错解决
26 0
composer的基本使用和常见报错解决
composer的基本使用和常见报错解决
163 0
Message no. V1762 during the Intercompany Sales.
Maintain Plant for Sales Area error appeared during the Intercompany Sales. For this maintain Sales Area in Shipping data for ...
1117 0
ActiveMQ第三弹:在Spring中使用内置的Message Broker
在上个例子中我们演示了如何使用Spring JMS来向ActiveMQ发送消息和接收消息。但是这个例子需要先从控制台使用ActiveMQ提供的命令行功能启动一个Message Broker,然后才能运行示例。
813 0
df用法
df - report filesystem disk space usage a:显示全部的档案系统和各分割区的磁盘使用情形 i:显示i -nodes的使用量 -h print sizes in human readable format (e.
673 0
Disruptor 全解析(1):Ring Buffer 有什么特别?
原文地址: http://mechanitis.blogspot.com/2011/06/dissecting-disruptor-whats-so-special.html​  作者是 Trisha Gee, LMAX 公司的一位女工程师。
1634 0
+关注
ali清英
方腾飞,花名清英,英文名kiral,并发编程网创始人,支付宝技术专家,《Java并发编程的艺术》作者。
614
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载