Disruptor 学习

Disruptor 是什么

Disruptor 是一种解决并发编程难题的通用机制

Disruptor 结合使用

应用场景:

做埋点上报记录,埋点上报的特点: 多线程同时操作,而且并发量较大。

Disruptor分为生产者和消费者,生产者向环形数组中添加消息,消费者从环形数组中取消息

生产者写法:

java 复制代码
@Component
@Slf4j
public class FillingPointGatewayImpl implements FillingPointGateway {

    private Disruptor<FillingPointEvent> disruptor;
    private RingBuffer<FillingPointEvent> ringBuffer;
    private ExecutorService executorService;

    @Resource
    private SystemValueConfig systemValueConfig;

    @Resource
    private FillingPointEventHandler fillingPointEventHandler;

    @PostConstruct
    public void initAddFillPointCmdExe() {
        FillingPointEventFactory factory = new FillingPointEventFactory();
        int bufferSize = 1024;

        executorService = Executors.newCachedThreadPool();
        disruptor = new Disruptor<>(factory, bufferSize, executorService);
        disruptor.handleEventsWith(fillingPointEventHandler);
        disruptor.start();

        ringBuffer = disruptor.getRingBuffer();
    }

    @Override
    public boolean add(FillingPointCmd fillingPointCmd) {
        log.info("AddFillPointCmdExe execute");
        long sequence = ringBuffer.next();
        try {
            FillingPointEvent event = ringBuffer.get(sequence);
            event.setCmd(fillingPointCmd);
        } finally {
            ringBuffer.publish(sequence);
        }
        return true;
    }

    @PreDestroy
    public void shutdown() {
        log.info("Shutting down Disruptor");
        disruptor.shutdown();
        fillingPointEventHandler.flushCache();
        executorService.shutdown();
    }
}

消费者写法

java 复制代码
@Slf4j
@Component
public class FillingPointEventHandler implements EventHandler<FillingPointEvent> {

    private final List<FillingPointCmd> batch = new ArrayList<>();

    @Resource
    private SystemValueConfig systemValueConfig;

    @Resource
    private PetFillingPointMapper petFillingPointMapper;

    @PostConstruct
    public void init() {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(this::flushCache, 120, systemValueConfig.getFillPointFlushInterval(), TimeUnit.SECONDS);
    }

    @Override
    public void onEvent(FillingPointEvent event, long sequence, boolean endOfBatch) {
        log.info("FillingPointEventHandler onEvent event:{}", JsonUtil.of(event.getCmd()));
        batch.add(event.getCmd());
        if (batch.size() >= systemValueConfig.getFillPointBatchSize()) {
            log.info("batch size:{} try to flush cache ", batch.size());
            flushCache();
        }
    }

    public void flushCache() {
        if (!batch.isEmpty()) {
            // 将批量数据写入数据库
            insertBatch(batch);
            log.info("Flushing cache with {} items", batch.size());
            batch.clear();
        }
    }

    private void insertBatch(List<FillingPointCmd> fillingPointCmds) {
        List<PetFillingPointDO> petFillingPointDOs = FillingPointConvertorMapper.INSTANCE.toDOList(fillingPointCmds);
        petFillingPointMapper.batchInsert(petFillingPointDOs);
    }
}

为什么要用 Disruptor

对比于传统 Java 的线程安全的内存队列

这些队列要么加锁有界,要么不加锁无界,如果想要一个不加锁使用 CAS 操作的有界队列,是没有的,这时候就可以用Disruptor

该图是 Lmax 官方对于 Disruptor 和 ArrayBlockingQueue 的性能对比,上面表示,Disruptor能在很低的 ns(纳秒)延迟下处理很多事件,而ArrayBlockingQueue需要在比较高的延迟下才能处理完相关事件。

Disruptor 其他支持的功能

  1. 生成Disruptor对象的时候可以指定单生产者和多生产者,也就是单线程和多线程的生产者。
  2. 支持多消费者,并且多消费者可以有顺序

Disruptor 为什么这么快

Disruptor 原理图:

RingBuffer 是个环形数组

Producter 向里面写入消息

消费者们通过 SequenceBarrier 来消费消息

消费者等待队列消息的时候可以执行 waitFor()的策略

  • Disruptor 是怎么解决之前队列里面性能问题的
  1. 数组要比链表快,内存空间连续,对 CPU 友好

  2. 传统队列中在队列末尾多个生产者争抢一个尾标这种情况下总是需要加锁,但是Disruptor是每个生产者都会有自己的一个 Sequence,在生产者投放消息的过程中,先使用 CAS 申请空间,申请成功后根据返回的最大序列来投放消息。

  3. 伪共享的问题解决

每个线程要处理变量,需要先从主内存读取变量到本地中,CPU 读取变量是以缓存行的形式读取。 假设一个类有两个字段

java 复制代码
{
    int productValue;
    int consumerValue;
}

CPU 加载时这两个字段有可能是加载到一个缓存行,如果生产者更新了productValue,没有更新consumerValue,但是由于在一个缓存行,consumerValue之前缓存的值也会失效。

这种无法充分使用缓存行特性的现象,称为伪共享。

Disruptor在每个字段中添加了多个空数据,这样保证productValue和consumerValue能分配到两个不同的缓存行

java 复制代码
abstract class SingleProducerSequencerPad extends AbstractSequencer
{
    protected long p1, p2, p3, p4, p5, p6, p7;

    SingleProducerSequencerPad(int bufferSize, WaitStrategy waitStrategy)
    {
        super(bufferSize, waitStrategy);
    }
}

abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad
{
    SingleProducerSequencerFields(int bufferSize, WaitStrategy waitStrategy)
    {
        super(bufferSize, waitStrategy);
    }

    /**
     * Set to -1 as sequence starting point
     */
    long nextValue = Sequence.INITIAL_VALUE;
    long cachedValue = Sequence.INITIAL_VALUE;
}

/**
 * <p>Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s.
 * Not safe for use from multiple threads as it does not implement any barriers.</p>
 *
 * <p>* Note on {@link Sequencer#getCursor()}:  With this sequencer the cursor value is updated after the call
 * to {@link Sequencer#publish(long)} is made.</p>
 */

public final class SingleProducerSequencer extends SingleProducerSequencerFields
{
    protected long p1, p2, p3, p4, p5, p6, p7;
  • Disruptor 是如何保证投递的数据其他消费者立即可以看到

即使不在 Event 对象上添加 volatile 关键字,生产者投放的消息,其他线程也都能立马看到,是因为Disruptor在写以后添加了写屏障、读之前又添加了读屏障

Disruptor 源码解析

引用文章:juejin.cn/post/722282...

相关推荐
承渊政道35 分钟前
飞算Java炫技赛:Java×Unity数字孪生园区平台的从零落地记录
java·开发语言·unity·技术分享·vibe coding·飞算javaai·飞算java ai炫技赛
wdfk_prog7 小时前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
卓怡学长7 小时前
w255基于springboot仓库管理系统
java·数据库·spring boot·spring·intellij-idea
这个DBA有点耶9 小时前
SQL调优进阶:从“优化一条SQL”到“优化一个系统”的思维升级
java·大数据·数据库·sql·程序人生·dba·改行学it
csdn2015_10 小时前
springboot读取配置的方法
java·spring boot·spring
AOwhisky11 小时前
下一代容器来了?Docker 宣布原生支持 WebAssembly
java·运维·docker·容器·rust·wasm
云云只是个程序马喽12 小时前
海外短剧平台搭建方案:私有化源码系统选型|云微短剧系统技术架构拆解
java·php
C137的本贾尼13 小时前
第七篇:消息队列(MQ)——就是个带存储的异步通信管道
java·开发语言·中间件
Flittly13 小时前
【AgentScope Java新手村系列】(19)多模态-图像音频视频
java·spring boot·spring
Javatutouhouduan14 小时前
国内大厂Java面试高频题库(2026突击版)
java·架构师·java面试·java面试题·后端开发·java程序员·java八股文