Welcome to my GitHub

https://github.com/zq2599/blog_demos

Content: Classification and summary of all original articles and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;

"Disruptor Notes" series link

  1. Quick Start
  2. Disruptor class analysis
  3. circular queue (without Disruptor class)
  4. Summary of event consumption knowledge points
  5. event consumption actual combat
  6. Common scenarios
  7. Waiting strategy
  8. knowledge points supplement (final)

Overview of this article

  • This article is the final chapter of the "Disruptor Notes" series. We have seen so much code and wrote so much code before. Now let's look at a few knowledge points and complete the disruptor journey in a relaxed reading process;
  • The knowledge points to be paid attention to are as follows:
  • False sharing
  • Translators
  • Lambda style
  • Clean up data
  • Next, start to understand one by one;

False sharing

  • The following figure shows the CPU cache of a multi-core processor. It can be seen that each core has its own L1 and L2 cache, and the L3 cache is shared:

在这里插入图片描述

  • Assuming that the sequence of the disruptor is a long type, then the disruptor of a producer and a consumer should have two long-type sequences. When these two numbers are cached in L1, because the size of each cache line is 64 bytes, there are two Sequence is likely to be in a cache line
  • At this time, if the program modifies the value of the producer Sequence, it will invalidate the corresponding cache line on L1, and then read the latest value from the Main memory. At this time, because the consumer Sequence is also in the same cache line, it will also It is invalidated, which leads to a value that has not changed and is also cleared, and it must be fetched from the Main memory again. This is a behavior that affects performance.
  • Seeing this, you must be wise to think of a solution to the problem: Don’t let two Sequences be in the same cache line
  • What are the specific methods? Do you think of occupying a seat in daily life, putting a backpack on the seat next to you so that other people cannot use it (this is uncivilized behavior, just for example)
  • In fact, the disruptor uses a seat-occupying routine. Let’s take a look at the source code of Sequence and it’s clear at a glance. As shown in the figure below, the value of Sequence is the member variable of the <font color="blue">Value</font> object. color="red">value</font>:
// 父类,
class LhsPadding
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding
{
    protected volatile long value;
}

class RhsPadding extends Value
{
    protected long p9, p10, p11, p12, p13, p14, p15;
}

public class Sequence extends RhsPadding
{
    ...
  • The class diagram is as follows, it can be seen that the parent and child classes of Value are all occupying long types:

在这里插入图片描述

  • Therefore, the Sequence object has 16 member variables, which are arranged in the following figure in the L1 cache:

在这里插入图片描述

  • Imagine that the cache line of L1 cache is one for every 64 bytes, that is to say, the above string occupies one cache line for every eight (8 bytes for each long type), so there are the following three arrangements possible:
  1. V appears at the top of a cache line;
  2. V appears at the end of a cache line;
  3. V appears in one of the other six positions between the first and the end of a cache line;
  • That is, there are three possibilities in the figure below (U is the other content in the L1 cache). It can be seen that no matter which possibility, V can use P to occupy all the cache lines in which it is located, so that two Sequences will not appear in the same The situation of the cache line:

在这里插入图片描述

Translators

  • Translators are a small programming skill. The disruptor helps users make some encapsulation to make the code for publishing events more concise;
  • Open the consume-mode module of the disruptor-tutorials project and review the method to be called for the business release event in OrderEventProducer.java:
    public void onData(String content) {

        // ringBuffer是个队列,其next方法返回的是下最后一条记录之后的位置,这是个可用位置
        long sequence = ringBuffer.next();

        try {
            // sequence位置取出的事件是空事件
            OrderEvent orderEvent = ringBuffer.get(sequence);
            // 空事件添加业务信息
            orderEvent.setValue(content);
        } finally {
            // 发布
            ringBuffer.publish(sequence);
        }
    }
  • In the above code, in fact, the developer is most concerned about the <font color="blue">orderEvent.setValue(content)</font> part. The other lines are copied from the official demo...
  • Obviously the disruptor also found this small problem, so the <font color="blue">EventTranslatorOneArg</font> interface has been provided since version 3.0. Developers put business logic into the implementation class of this interface. As for the previous code The <font color="blue">ringBuffer.next()</font>, ringBuffer.get(sequence), and try-finally code blocks in <font color="blue">ringBuffer.next()</font> are all omitted. We can transform OrderEventProducer.java into A new class, the code is as follows, you can see the new internal class EventTranslatorOneArg, which contains the business logic of converting data into events. In the onData method that is provided to the outside, only one line of code is needed, and all the code that has nothing to do with the business is omitted. :
package com.bolingcavalry.service;

import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;

public class OrderEventProducerWithTranslator {

    // 存储数据的环形队列
    private final RingBuffer<OrderEvent> ringBuffer;

    public OrderEventProducerWithTranslator(RingBuffer<OrderEvent> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    /**
     * 内部类
     */
    private static final EventTranslatorOneArg<OrderEvent, String> TRANSLATOR = new EventTranslatorOneArg<OrderEvent, String>() {
        @Override
        public void translateTo(OrderEvent event, long sequence, String arg0) {
            event.setValue(arg0);
        }
    };

    public void onData(String content) {
        ringBuffer.publishEvent(TRANSLATOR, content);
    }
}
  • In consume-mode, the above code has a corresponding service class TranslatorPublishServiceImpl.java, and has a corresponding unit test code (ConsumeModeServiceTest.testTranslatorPublishService), which will not take up space here. If you are interested, you can check it yourself;
  • Take a look at the internal implementation of ringBuffer.publishEvent, how it helps us save the previous lines. First, we call <font color="blue">sequencer.next</font>:
@Override
    public <A> void publishEvent(EventTranslatorOneArg<E, A> translator, A arg0)
    {
        final long sequence = sequencer.next();
        translateAndPublish(translator, sequence, arg0);
    }
  • Open translateAndPublish again and see that ringBuffer.get, try-finally code block, sequencer.publish are all there, so don’t worry, what we did before, now the disruptor does it for us, let’s concentrate on the business logic:
    private <A> void translateAndPublish(EventTranslatorOneArg<E, A> translator, long sequence, A arg0)
    {
        try
        {
            translator.translateTo(get(sequence), sequence, arg0);
        }
        finally
        {
            sequencer.publish(sequence);
        }
    }

Lambda style

  • The important APIs of disruptor also support Lambda expressions as input parameters. Here are several commonly used APIs as follows:
  1. Disruptor class instantiation (LambdaServiceImpl.java):
// lambda类型的实例化
disruptor = new Disruptor<OrderEvent>(OrderEvent::new, BUFFER_SIZE, DaemonThreadFactory.INSTANCE);
  1. When setting up event consumers, you can use Lambda to replace the previous object (LambdaServiceImpl.java):
        // lambda表达式指定具体消费逻辑
        disruptor.handleEventsWith((event, sequence, endOfBatch) -> {
            log.info("lambda操作, sequence [{}], endOfBatch [{}], event : {}", sequence, endOfBatch, event);
            // 这里延时100ms,模拟消费事件的逻辑的耗时
            Thread.sleep(100);
            // 计数
            eventCountPrinter.accept(null);
        });
  1. The operation of publishing events also supports Lambda expressions. As shown below, I added the publistEvent method to the parent class ConsumeModeService.java. The input parameters of the disruptor.getRingBuffer().publishEvent called inside are the Lambda expressions and events required. Business data, so that the provinces and regions have previously published the event class OrderEventProducer.java:
public void publistEvent(EventTranslatorOneArg<OrderEvent, String> translator, String value) {
        disruptor.getRingBuffer().publishEvent(translator, value);
    }
  1. As shown below, now that the operation of publishing events after getting business data becomes very light, the logic of business data to events can be done in the Lambda expression. In the end, OrderEventProducer.java is no longer needed, and one line of code completes the event publishing ( ConsumeModeServiceTest.java):
for(int i=0;i<EVENT_COUNT;i++) {
  log.info("publich {}", i);
  final String content = String.valueOf(i);
  lambdaService.publistEvent((event, sequence, value) -> event.setValue(value), content);
}

Clean up data

  • Since the stored data structure is a circular queue, each event instance will always be stored in the queue and will not be overwritten by a new event instance until it is written at this location again. Considering that some scenarios require data to be consumed After it was cleared immediately, the disruptor official provided the following suggestions:
  1. In the event definition class, add a method to clean up business data (assuming it is the clear method of the ObjectEvent class);
  2. Add a new event processing class (assuming it is ClearingEventHandler), and actively call the method of clearing business data of the event definition class in it;
  3. When writing the event consumption logic, finally add the above event processing class ClearingEventHandler, so that the clear method of the ObjectEvent instance will be called to clear the business data;
  • The official code is as follows:

在这里插入图片描述

  • At this point, the entire "disruptor notes" is complete, thank you for your attention, and hope that the content of this series will help you, and there will be more choices and references in the development;

You are not alone, Xinchen and original are with you all the way

  1. Java series
  2. Spring series
  3. Docker series
  4. kubernetes series
  5. database + middleware series
  6. DevOps series

Welcome to pay attention to the public account: programmer Xin Chen

Search "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos

程序员欣宸
147 声望24 粉丝

热爱Java和Docker