java多线13: disruptor框架

Disruptor系列(一)— disruptor介绍

本文翻译自Disruptor在github上的wiki文章Introduction,原文可以看这里。


### 一.前言

作为程序猿大多数都有对技术的执着,想在这个方面有所提升。对于优秀的事物保持积极学习的心态,并发编程是开发中一大难题,无论是底层的各种理论还是上层的各种关于并发组件的实现,都非常的晦涩难懂。并发之所以难,就是因为"多"而难以控制。大多数都会使用"锁"这种技术进行控制,但是"锁"这种技术往往和性能又是背道而驰。为了能够将性能最大化,无锁 去锁显然是提升并发性能的关键。前人的智慧还真是慧深莫测,还真在这块领域有所突破,用一种不同以往的设计方式实现了无锁的高性能并发队列 — disruptor

本系列就对disruptor进行学习:

  • diruptor介绍
  • disruptor使用
  • disruptor原理
  • disruptor在开源中的使用

系列将从以上几个方面由浅入深,层层递进式学习。disruptor是open source的,源代码被托管在github上LMAX-Exchange/disruptor

其中wiki有很多非常优秀的文章介绍,是非常不错的学习资源。但都是英文,索性这里将其介绍篇使用篇翻译一遍。


### 二.是什么

LMAX Disruptor是高性能的线程内部通信的消息库。它源于LMAX对并发、性能、非阻塞算法的研究,目前已经成为LMAX的交易基础设施的核心部分。

理解Disruptor是什么的最好方式就是将其与现有的比较好理解的东西比较。Disruptor就相当于Java中BlockingQueue。同队列一样,Disruptor的目的就是在同一进程的线程间进行数据交互。然而Disruptor又提供了一些关键的不同于队列的特征:

  • 广播事件至消费者,并且能遵循消费者依赖关系
  • 为事件预分配内存
  • 可选择的无锁


### 三.核心概念

在理解Disruptor如何工作之前,定义一些普遍存在文档和源代码中的术语是非常有价值的。对于倾向DDD的人而言,它们就是Disruptor领域的无处不在的语言。

  • Ring Buffer(环形缓冲):RingBuffer是Disruptor的主要概念。但从Diruptor 3.0开始,RingBuffer只负责存储和更新Disruptor的数据。一些高级的使用场景,可以被用户替换。

  • Sequence(序列):Disruptor使用Sequence来标记特定组件到达什么位置的。每个消费者(EventProcessor)内部都维护一个Sequence来标记自己消费到的位置。大多数并发代码都是依赖于Sequence的移动,因此Sequence支持大量AtomicLong的特征。实际上两者之间的不同在于Sequence实现了额外的阻止伪共享的功能。

  • Sequencer(序列器):Sequencer是Disruptor中的实际核心。其中两个实现(Single producer,multi producer)全都实现了在生产者与消费者间进行快速正确的传递数据的算法。

  • Sequence Barrier(sequence屏障):Sequence Barrier由Sequencer创建,它包含了来自Sequencer的已经发布的主要sequence的引用,或者包含了依赖的消费者的sequence。同时也包含决定是否有时间可达共消费者处理的逻辑。

  • Wait Strategy(等待策略):Wait Strategy决定了消费者以何种方式等待生产者将事件放进Disruptor。

  • Event(事件):从生产者传到消费者的数据单元。Event通常由用户定义,没有特定的代码规约。

  • Event Processor(事件处理器):处理来自Disruptor的事件的主要事件循环,且包含了消费者的sequence。有一个实现为BatchEventProcessor,包含了高效的事件循环,将不断回调被提供的EventHandler接口。

  • EventHandler(事件处理逻辑):一个接口,由用户实现定义消费者的处理逻辑。

  • Producer(生产者):这也是用于实现的用户代码,调用Disruptor入队事件。没有任何代码规约。

为了能够将这些概念关联起来,即放在一个上下文中表示,下图是个例子,展示LMAX在高性能的核心服务中使用Disruptor的场景:

Note:
原文中没有对这张图进行解释,笔者看到时一脸懵逼,也是在全局的了解了Disruptor后再来研究这张图,才弄明白其含义。这张图可谓是将Disruptor描绘的淋漓尽致。
这里笔者也不对其做过多的介绍,因为要弄懂这张图,势必要对Disruptor有个整体理解。后面介绍原理时再细致解析。
整体而言,Producer生产事件放入RingBuffer中,Consumer利用Sequence Barrier和自身包含的Sequence从RingBuffer中获取可消费的事件。


### 四.特征

1.广播事件

这是队列和Disruptor之间的一个巨大的行为差异。当你有多个消费者监听在相同的Disruptor上是,所有的事件将都被发送给所有的消费者,这点与队列不同,在队列中,一个时间将只发送给一个消费者。Disruptor更偏向用在当有多个无依赖的消费者并行处理相同数据的场景中。在LMAX中有个非常经典的案例,有三个操作journalling(写输入的数据到一个持久化的日志文件),replication(发送输入数据到另一台机器,确保远程备份)和业务逻辑处理。类似Executor风格的事件处理,在这里同时并行的处理不同的事件,可以使用WorkePool。

再看上图,有三个事件处理器(JournalConsumer, ReplicationConsumer和 ApplicationConsumer)监听Disruptor,每一个都将接受Disruptor中所有的可用消息。这个允许三个消费者并行的工作。

2.消费者依赖图

为了支持并行处理行为的实际应用,支持消费者之前的协调是很有必要的。再以上述例子来说,业务逻辑的处理必须在journalling和replication完成之后。我们把这种概念叫做gating,或者更准确的说这个行为特征的超集被称为gating。Disruptor中,Gating发生在两个地方。第一,我们需要确保生产者不要超过消费者。通过调用RingBuffer.addGatingConsumers()增加相关的消费者至Disruptor来完成。第二,就是之前所说的场景,通过构造包含需要必须先完成的消费者的Sequence的SequenceBarrier来实现。

引用上图来说,有三个消费者监听来自RingBuffer的事件。在这个例子中,有一个依赖关系图。ApplicationConsumer依赖JournalConsumer和ReplicationConsumer。这个意味着JournalConsumer和ReplicationConsumer可以自由的并发运行。依赖关系可以看成是从ApplicationConsumer的SequenceBarrier到JournalConsumer和ReplicationConsumer的Sequence的连接。还有一点值得关注,Sequencer与下游的消费者之间的关系。它的角色是确保发布不会包裹RingBuffer。为了做到这点,下游消费者的Sequence没有一个是低于RingBuffer的Sequence而不是RingBuffer的大小。然后使用依赖关系时,一个有趣的优化可以使用。因为ApplicationConsumers的Sequence被保证是低于或者等于JournalConsumer和ReplicationConsumer的Sequence,所以Sequencer只需要检查ApplicationConsumers的Sequence。在更为普遍的应用场景中,Sequencer只需要意识到消费者树中的叶子节点的的Sequence即可。

3.事件预分配

Disruptor的一个目标之一是被用在低延迟的环境中。在低延迟系统中,必须要减少或者去除内存分配。在基于Java的系统中,需要减少由于GC导致的停顿次数(在低延迟的C/C++系统中,由于内存分配器的争用,大量的内存分配也会导致问题)。

为了满足这点,用户可以在Disruptor中为事件预分配内存。在构造期间,EventFactory由用户提供,并将在Disruptor的RingBuffer中为每个条目调用。当发布新的数据至Disruptor时,API允许用户获取已经被构造的对象,以便可以调用方法或者更新在该对象的域。Disruptor将确保这些操作是线程安全。

4.可选择的无锁

对于低延迟的需求又推动了另一个关键性是广泛的使用无锁算法实现Disruptor。所有的内存可见性和正确性都使用内存屏障和CAS操作实现。只仅仅一个场景BlockingWaitStrategy中使用到了lock。这仅仅是为了使用条件,以便在等待新事件到达时停放消耗线程。许多低延迟系统都使用忙等来避免使用Condition造成的抖动。但是忙等的数量将会导致性能的下降,特别是CPU资源严重受限的情况下。例如,在虚拟环境中的Web服务器。

Disruptor 系列(二)使用场景

今天用一个订单问题来加深对 Disruptor 的理解。当系统中有订单产生时,系统首先会记录订单信息。同时也会发送消息到其他系统处理相关业务,最后才是订单的处理。

代码包含以下内容:

1) 事件对象 Event

2)三个消费者 Handler

3)一个生产者 Producer

4)执行 Main 方法

一、订单处理系统代码

(1) Event

public class Trade {  
    
    private String id;//ID  
    private String name;
    private double price;//金额  
    private AtomicInteger count = new AtomicInteger(0);
    
    // 省略getter/setter
}  

(2) Handler 类

一个负责存储订单信息,一个负责发送 kafka 信息到其他系统中,最后一个负责处理订单信息。

import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.WorkHandler;

/**
 * 第一个 Handler1,存储到数据库中
 */
public class Handler1 implements EventHandler<Trade>, WorkHandler<Trade> {
      
    @Override  
    public void onEvent(Trade event, long sequence, boolean endOfBatch) throws Exception {  
        this.onEvent(event);  
    }  
  
    @Override  
    public void onEvent(Trade event) throws Exception {
        long threadId = Thread.currentThread().getId();     // 获取当前线程id
        String id = event.getId();                          // 获取订单号
        System.out.println(String.format("%s:Thread Id %s 订单信息保存 %s 到数据库中 ....",
                this.getClass().getSimpleName(), threadId, id));
    }  
}  
import com.lmax.disruptor.EventHandler;

/**
 * 第二个 Handler2,订单信息发送到其它系统中
 */
public class Handler2 implements EventHandler<Trade> {  
      
    @Override  
    public void onEvent(Trade event, long sequence,  boolean endOfBatch) throws Exception {
        long threadId = Thread.currentThread().getId();     // 获取当前线程id
        String id = event.getId();                          // 获取订单号
        System.out.println(String.format("%s:Thread Id %s 订单信息 %s 发送到 karaf 系统中 ....",
                this.getClass().getSimpleName(), threadId, id));
    }
}    
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.WorkHandler;

/**
 * 第三个 Handler2,处理订单信息
 */
public class Handler3 implements EventHandler<Trade>, WorkHandler<Trade> {

    @Override  
    public void onEvent(Trade event, long sequence,  boolean endOfBatch) throws Exception {
        onEvent(event);
    }

    @Override
    public void onEvent(Trade event) throws Exception {
        long threadId = Thread.currentThread().getId();     // 获取当前线程id
        String id = event.getId();                          // 获取订单号
        System.out.println(String.format("%s:Thread Id %s 订单信息 %s 处理中 ....",
                this.getClass().getSimpleName(), threadId, id));
    }
} 

(3) Producer 类

import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.UUID;
import java.util.concurrent.CountDownLatch;

public class TradePublisher implements Runnable {  
    
    Disruptor<Trade> disruptor;  
    private CountDownLatch latch;  
    
    private static int LOOP = 1;    // 模拟百万次交易的发生
  
    public TradePublisher(CountDownLatch latch, Disruptor<Trade> disruptor) {
        this.disruptor=disruptor;  
        this.latch=latch;  
    }  
  
    @Override  
    public void run() {  
        TradeEventTranslator tradeTransloator = new TradeEventTranslator();  
        for(int i = 0; i < LOOP; i++) {
            disruptor.publishEvent(tradeTransloator);  
        }  
        latch.countDown();  
    }  
      
}  
  
class TradeEventTranslator implements EventTranslator<Trade>{
    
    @Override  
    public void translateTo(Trade event, long sequence) {
        event.setId(UUID.randomUUID().toString());
    }
    
}  

(4) 执行的 Main 方法

package com.github.binarylei.disruptor.demo3;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import com.lmax.disruptor.dsl.ProducerType;

public class Main {  
    public static void main(String[] args) throws InterruptedException {  
       
        long beginTime=System.currentTimeMillis();  
        int bufferSize=1024;  
        ExecutorService executor=Executors.newFixedThreadPool(8);

        Disruptor<Trade> disruptor = new Disruptor<>(new EventFactory<Trade>() {
            @Override  
            public Trade newInstance() {  
                return new Trade();  
            }  
        }, bufferSize, executor, ProducerType.SINGLE, new BusySpinWaitStrategy());  
        
        //菱形操作
        //使用disruptor创建消费者组C1,C2  
        EventHandlerGroup<Trade> handlerGroup = 
                disruptor.handleEventsWith(new Handler1(), new Handler2());
        //声明在C1,C2完事之后执行JMS消息发送操作 也就是流程走到C3 
        handlerGroup.then(new Handler3());

        disruptor.start();//启动
        CountDownLatch latch=new CountDownLatch(1);  
        //生产者准备  
        executor.submit(new TradePublisher(latch, disruptor));
        
        latch.await();//等待生产者完事. 
       
        disruptor.shutdown();  
        executor.shutdown();  
        System.out.println("总耗时:"+(System.currentTimeMillis()-beginTime));  
    }  
}  

测试结果如下:

Handler1:Thread Id 10 订单信息保存 a097c77d-08f1-430a-8342-2143963f268f 到数据库中 ....
Handler2:Thread Id 11 订单信息 a097c77d-08f1-430a-8342-2143963f268f 发送到 karaf 系统中 ....
Handler3:Thread Id 13 订单信息 a097c77d-08f1-430a-8342-2143963f268f 处理中 ....
总耗时:1631

可以看到 Handler3 在 Handler1 和 Handler2 执行完成后才执行。

二、Disruptor DSL

虽然 disruptor 模式使用起来很简单,但是建立多个消费者以及它们之间的依赖关系需要的样板代码太多了。为了能快速又简单适用于99%的场景,我为 Disruptor 模式准备了一个简单的领域特定语言(DSL),定义了消费顺序。更多Disruptor场景使用

在讲解 Disruptor DSL 之前先看一下多个消费者不重复消费的问题。

2.1 多个消费者不重复消费

默认一个消费者一个线程,如果想要实现 C3 多个消费者共同不重复消费数据,可以使用 handlerGroup.thenHandleEventsWithWorkerPool(customers)

//使用disruptor创建消费者组C1, C2  
EventHandlerGroup<Trade> handlerGroup = disruptor.handleEventsWith(new Handler1(), new Handler2());

// 多个消费者不重复消费
Handler3[] customers = new Handler3[]{new Handler3(), new Handler3(), new Handler3()};
handlerGroup.thenHandleEventsWithWorkerPool(customers);

2.2 消费者的“四边形模式”

四边形模式

在这种情况下,只要生产者(P1)将元素放到ring buffer上,消费者C1和C2就可以并行处理这些元素。但是消费者C3必须一直等到C1和C2处理完之后,才可以处理。在现实世界中的对应的案例就像:在处理实际的业务逻辑(C3)之前,需要校验数据(C1),以及将数据写入磁盘(C2)。

//1. 使用disruptor创建消费者组C1,C2  
EventHandlerGroup<Trade> handlerGroup = disruptor.handleEventsWith(new Handler1(), new Handler2());

//2. 声明在C1,C2完事之后执行JMS消息发送操作 也就是流程走到C3 
handlerGroup.then(new Handler3());

2.3 消费者的“顺序执行模式”

disruptor.handleEventsWith(new Handler1()).
    handleEventsWith(new Handler2()).
    handleEventsWith(new Handler3());

2.4 消费者的“六边形模式”

我们甚至可以在一个更复杂的六边形模式中构建一个并行消费者链:

六边形模式

 

 

 

Handler1 h1 = new Handler1();
Handler2 h2 = new Handler2();
Handler3 h3 = new Handler3();
Handler4 h4 = new Handler4();
Handler5 h5 = new Handler5();
disruptor.handleEventsWith(h1, h2);
disruptor.after(h1).handleEventsWith(h4);
disruptor.after(h2).handleEventsWith(h5);
disruptor.after(h4, h5).handleEventsWith(h3);

 

剖析Disruptor:为什么会这么快

  1. 剖析Disruptor:为什么会这么快?(一)锁的缺点
  2. 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充
  3. 剖析Disruptor:为什么会这么快?(三)伪共享
  4. 剖析Disruptor:为什么会这么快?(四)揭秘内存屏障

Disruptor如何工作和使用

  1. 如何使用Disruptor(一)Ringbuffer的特别之处
  2. 如何使用Disruptor(二)如何从Ringbuffer读取
  3. 如何使用Disruptor(三)写入Ringbuffer
  4. 解析Disruptor关系组装
  5. Disruptor(无锁并发框架)-发布
  6. LMAX Disruptor——一个高性能、低延迟且简单的框架
  7. Disruptor Wizard已死,Disruptor Wizard永存!
  8. Disruptor 2.0更新摘要
  9. 线程间共享数据不需要竞争

Disruptor的应用

  1. LMAX的架构
  2. 通过Axon和Disruptor处理1M tps
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值