1. 什么是Disruptor
1.1. 简介
Disruptor是一个LMAX开源的Java高性能的异步处理框架,它能够在一个线程里每秒处理600万的订单,业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing),具有低延迟,高吞吐的特性。
1.2. 扩展
Disruptor还允许开发者使用多线程技术,去创建基于任务的工作流。Disruptor能用来并行创建任务,同时保证多个处理过程的有序性。
2. Disruptor的高性能
2.1. Disruptor有多快
官方给出了和ArrayBlockingQueue的比较图表
Array Blocking Queue (ns) | Disruptor (ns) | |
最小延迟 | 145 | 29 |
平均延迟 | 32,757 | 52 |
99% observations less than | 2,097,152 | 128 |
99.99% observations less than | 4,194,304 | 8,192 |
最大延迟 | 5,069,086 | 175,567 |
2.2. 优点
Disruptor 相对于 JDK 内置阻塞队列 ArrayBlockingQueue 、LinkedBlockingDeque 等)的优点:
- 无锁,使用内存屏障:采用CAS无锁方式,保证线程的安全性和ABA问题
- 序号栅栏机制:允许多个生产者与多个消费者共享相同的数据结构
- 缓存行填充:解决伪共享,提高cache命中率
- 环形数组RingBuffer:为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好
- 元素位置定位:数组长度2^n,通过位运算(index & (size -1)),加快定位的速度。下标采取递增的形式。不用担心index溢出的问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完
2.3. 伪共享
2.3.1. 什么是伪共享
缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
如上图变量x,y同时被放到了CPU的一级和二级缓存,当线程1使用CPU1对变量x进行更新时候,首先会修改cpu1的一级缓存变量x所在缓存行,这时候缓存一致性协议会导致cpu2中变量x对应的缓存行失效,那么线程2写入变量y的时候就只能去二级缓存去查找,这就破坏了一级缓存,而一级缓存比二级缓存更快。更坏的情况下如果cpu只有一级缓存,那么会导致频繁的直接访问主内存。
/**
* @Author: JustinBo
* @Time: 2024-07-20 17:19
* @Feature: 伪共享
*/
public class FalseSharing implements Runnable {
/**
* 线程数
*/
public static int NUM_THREADS = 4;
/**
* 迭代次数
*/
public final static long ITERATIONS = 100000000L;
/**
* VolatileLong数组索引
*/
private final int index;
/**
* VolatileLong数组
*/
private static VolatileLong[] vLongArr;
public FalseSharing(int index) {
this.index = index;
}
@Override
public void run() {
long i = ITERATIONS + 1;
while (0 != i) {
vLongArr[index].value = --i;
}
}
public final static class VolatileLong {
/**
* value值
*/
public volatile long value;
/**
* 缓存行填充
*/
public long p1, p2, p3, p4, p5, p6;
}
private static void test() throws InterruptedException {
// 线程数组
Thread[] threadArr = new Thread[NUM_THREADS];
// 创建4个线程
for (int i = 0; i < threadArr.length; i++) {
threadArr[i] = new Thread(new FalseSharing(i));
}
// 启动线程
for (Thread t : threadArr) {
t.start();
}
// 阻塞主线程
for (Thread t : threadArr) {
t.join();
}
}
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
long sumTime = 0L;
for (int i = 0; i < 10; i++) {
System.out.println("第" + i + "次执行");
vLongArr = new VolatileLong[NUM_THREADS];
for (int j = 0; j < vLongArr.length; j++) {
vLongArr[j] = new VolatileLong();
}
final long start = System.nanoTime();
test();
final long end = System.nanoTime();
sumTime += end - start;
}
System.out.println("平均耗时:" + sumTime / 10 + "ns");
}
}
四个线程修改一数组不同元素的内容。元素的类型是 VolatileLong,只有一个长整型成员 value 和 6 个没用到的长整型成员。value 设为 volatile 是为了让 value 的修改对所有线程都可见。程序分两种情况执行,第一种情况为不屏蔽缓存行填充,第二种情况为屏蔽缓存行填充。为了"保证"数据的相对可靠性,程序取 10 次执行的平均时间。执行情况如下:
两种情况采用的同一套代码,后者仅是注释掉了 VolatileLong 类中的缓存行填充的6个长整型成员,耗时却是相差接近两倍。原因是:后者 longs 数组的 4 个元素,由于 VolatileLong 只有 1 个长整型成员,所以一个数组单元就是16个字节(long数据类型8个字节+类对象的字节码的对象头8个字节),进而整个数组都将被加载至同一缓存行(16*4字节),但有4个线程同时操作这条缓存行,于是伪共享就悄悄地发生了。
2.3.2. 如何解决伪共享
- 缓存行填充
一条缓存行有 64 字节,而 Java 程序的对象头固定占 8 字节(32位系统)或 12 字节( 64 位系统默认开启压缩, 不开压缩为 16 字节),所以只需要填 6 个无用的长整型补上6*8=48字节,让不同的 VolatileLong 对象处于不同的缓存行,就避免了伪共享( 64 位系统超过缓存行的 64 字节也无所谓,只要保证不同线程不操作同一缓存行就可以)。
2.4. 应用领域
1. 金融交易系统
- 特点:金融交易系统是高并发、低延迟的典型应用场景。交易订单的处理速度至关重要,任何延迟都可能导致巨大的经济损失。
- 应用:Disruptor通过其高效的无锁设计和内存预分配,能够在极短的时间内处理大量的交易订单,并将其按照指定的规则进行排序和处理,从而实现高吞吐量和低延迟的要求。
2. 广告投放系统
- 特点:广告投放系统需要对用户的行为进行实时监测和分析,以便准确地投放相关广告。由于广告请求量巨大,传统的处理方式往往无法满足实时处理的需求。
- 应用:Disruptor的高性能和低延迟特性,使得广告投放系统能够在短时间内高效处理海量的广告请求,并根据用户的行为快速进行广告匹配。
3. 大型多人在线游戏(MMOG)
- 特点:在大型多人在线游戏中,服务器需要同时处理大量玩家的操作请求。这些请求往往是高度并发的,要求服务器在短时间内做出响应。
- 应用:使用Disruptor可以将玩家的操作请求按照优先级进行处理,保证游戏服务器的稳定性和流畅性。同时,Disruptor还可以用于处理游戏内的事件消息,如玩家之间的交互、战斗结果等,提升游戏的实时性和可玩性。
4. 物流管理系统
- 特点:物流管理系统需要实时监控货物的运输进程,并对异常情况进行处理。在这个过程中,大量的数据需要进行实时处理和分析。
- 应用:Disruptor的高性能和低延迟使得物流管理系统能够快速响应各种事件,如货物丢失、运输延误等,并及时采取相应的措施。同时,Disruptor还可以将物流系统与其他相关系统进行无缝集成,实现信息的共享和交换。
5. 电信运营商的信令系统
- 特点:电信运营商的信令系统需要处理大量的用户请求,并进行实时的信令交换。传统的处理方式往往无法胜任这种高并发的场景。
- 应用:Disruptor的高性能和低延迟特性,使得信令系统能够高效地处理大量的信令请求,并实时进行信令交换。这样一来,电信运营商能够提供更加稳定、高质量的通信服务。
6. 其他领域
- 知名项目应用:除了上述领域外,Disruptor还被广泛应用于其他需要高性能并发处理的场景中。例如,Apache Storm、Camel、Log4j2等知名项目都应用了Disruptor以获取高性能。
- 广泛适用性:由于Disruptor提供了无锁的、高性能的消息传递和处理能力,它几乎可以应用于任何需要处理高并发、低延迟、大数据量的系统中。
3. Disruptor核心概念
3.1. 类图
3.2. 结构图
3.3. 环形缓冲区 - RingBuffer
Disruptor中数据存在一个环形数组,叫做环形缓冲区RingBuffer,是由一个大数组组成(比链表快,对CPU缓存友好),理解RingBuffer的结构有利于我们理解disruptor为什么这么快、无锁的实现方式、生产者/消费者模式的实现细节。
一个生产者 + 一个消费者 生产者维护一个生产指针P,消费者维护一个消费者指针C,当然P和C本质上就是序号。二者各操作各的,不需要锁,仅需要注意的是生产者和消费者的速度问题,当然这个在 Disruptor 内部已经为我们做了处理,就是判断一下P和C之间不能超过一圈的大小。 一个生产者 + 多个消费者 多个消费者当然持有多个消费指针C1,C2,...,消费者依据C进行各自读取数据,只需要保证生产者的速度“协调”最慢的消费者的速度,就是不能超出一圈的概念。此时也不需要进行锁定。 多个生产者 + N个消费者 无论生产者有几个,生产者指针P只能存在一个。那么多个生产者之间共享一个P指针,在 Disruptor 中实际上是利用了CAS机制来保证多线程的数据安全,也没有使用到锁。
3.4. 序号 - Sequence
在Disruptor框架中,任何地方都有序号。生产者生产的数据放在RingBuffer中的哪个位置,消费者应该消费哪个位置的数据,RingBuffer中的某个位置的数据是什么,这些都是由这个序号来决定的。这个序号可以简单的理解为一个AtomicLong类型的变量。其使用了padding的方法去消除缓存的伪共享问题。
3.5. 序号生成器 - Sequencer
这个类主要是用来协调生产者的,在生产者生产数据的时候,Sequencer会产生一个可用的序号(Sequence),然后生产者就就知道数据放在环形队列的那个位置了。Sequencer此接口有两个实现类SingleProducerSequencer、MultiProducerSequencer ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
3.5.1.SingleProducerSequencer
概述:
SingleProducerSequencer
是单生产者场景下的Sequencer实现。由于只有一个生产者线程会写入RingBuffer,因此可以避免多线程竞争的问题,从而提高了性能。
特点:
- 线程安全:由于只有一个生产者,因此不需要复杂的线程同步机制。
- 高效性:通过使用一个
long
类型的变量(如nextValue
)来跟踪下一个可用的序列号,避免了线程竞争和锁的开销。 - 缓存行填充:在某些实现中,为了避免伪共享(false sharing)问题,
SingleProducerSequencer
可能会使用缓存行填充(padding)技术,即在变量周围添加额外的内存空间,以确保它们不会与其他变量共享同一个缓存行。
主要方法:
hasAvailableCapacity(int requiredCapacity)
:检查是否有足够的剩余空间来存储指定数量的元素。next(int n)
:获取下一个或多个序列号的值,用于在RingBuffer中写入新的事件。tryNext(int n)
:尝试获取下一个或多个序列号,如果空间不足则抛出异常。
3.5.2. MultiProducerSequencer
概述:
MultiProducerSequencer
是多生产者场景下的Sequencer实现。它支持多个生产者线程同时写入RingBuffer,因此需要更复杂的线程同步机制来确保线程安全。
特点:
- 线程安全:通过CAS(Compare-And-Swap)等原子操作来确保多个生产者线程在更新序列号时的线程安全。
- 性能开销:与
SingleProducerSequencer
相比,MultiProducerSequencer
在性能上可能有所牺牲,因为它需要处理更多的线程同步和竞争条件。 - gatingSequenceCache:使用
Sequence
对象来缓存最近的gating sequence值,以减少对共享内存的访问次数,从而提高性能。
主要方法:
hasAvailableCapacity(int requiredCapacity)
:与SingleProducerSequencer
类似,但实现方式可能更复杂,以处理多个生产者的并发访问。next(int n)
:允许多个生产者线程安全地获取下一个或多个序列号。
3.5.3. 总结
- 使用场景:
SingleProducerSequencer
适用于只有一个生产者的场景,而MultiProducerSequencer
适用于多个生产者并发的场景。 - 性能:在单生产者场景下,
SingleProducerSequencer
由于避免了线程竞争和锁的开销,通常具有更高的性能。而在多生产者场景下,MultiProducerSequencer
通过复杂的线程同步机制来确保线程安全,但可能会牺牲一些性能。 - 设计选择:在选择使用哪种Sequencer时,应根据具体的应用场景和需求来决定。如果应用场景中只有一个生产者,那么使用
SingleProducerSequencer
将是更好的选择;如果有多个生产者,那么则需要使用MultiProducerSequencer
。
3.6. 序号屏障 - SequenceBarrier
消费者在消费数据的时候,需要知道消费哪个位置的数据。SequencerBarrier起到的就是这样一个“栅栏”般的阻隔作用,当消费者消费数据时,序号屏障提供一个序号(Sequence),消费者到该位置上的数据。要是没有数据,消费者就一直等着。
3.7. 数据单位 - EventData
从生产者到消费者传递的数据叫做EventData。它不是一个被 Disruptor定义的特定类型,而是由Disruptor的使用者定义并指定,即泛型T。
3.8. 消费者线程 - EventProcessor
该类集成了Runnable,该线程会一直从Ring Buffer获取数据来消费数据,其有两个核心实现类:BatchEventProcessor和WorkProcessor。
3.8.1. BatchEventProcessor
定义与功能:
- BatchEventProcessor是Disruptor框架中的一个事件处理器,它实现了EventProcessor接口,并提供了事件循环的有效实现。
- 它主要用于批量处理事件,通过合并多个事件为一个批次进行处理,从而减少了线程间的竞争,提高了处理效率。
- BatchEventProcessor通常与WorkerPool结合使用,由WorkerPool中的线程执行事件处理任务。
特点:
- 多线程并发执行:BatchEventProcessor可以配置多个线程来并发处理事件,每个线程可以处理不同的事件批次。
- 高效的事件处理:通过批量处理事件,减少了线程切换和同步的开销,提高了事件处理的吞吐量。
- 用户自定义处理逻辑:用户需要实现EventHandler接口,并提供具体的事件处理逻辑。
示例:
在Disruptor的示例中,BatchEventProcessor通常与RingBuffer和EventHandler一起使用。生产者将事件发布到RingBuffer中,BatchEventProcessor从RingBuffer中批量获取事件,并调用EventHandler来处理这些事件。
3.8.2. WorkProcessor(在Disruptor中通常指的是WorkerPool中的Worker)
定义与功能:
- 在Disruptor中,WorkerPool是一个线程池,其中包含了多个WorkProcessor(在Disruptor的上下文中,更常被称为Worker)。
- 每个WorkProcessor(Worker)是一个独立的线程,它从任务队列或事件队列中获取任务并执行。
- WorkProcessor确保了每个sequence只被一个processor消费,从而避免了数据竞争和重复处理。
特点:
- 线程隔离:每个WorkProcessor(Worker)都运行在自己的线程中,实现了任务的并行处理。
- 任务队列:WorkProcessor从任务队列中获取任务并执行,队列中的任务可以是单个事件或事件批次。
- 灵活性:用户可以根据需要调整WorkerPool中WorkProcessor(Worker)的数量,以适应不同的并发需求。
示例:
在Disruptor的示例中,创建WorkerPool时指定了线程的数量,并创建了相应的WorkProcessor(Worker)。这些WorkProcessor(Worker)会从RingBuffer中获取事件,并按照BatchEventProcessor的配置进行批量处理。
3.8.3. 总结
BatchEventProcessor和WorkProcessor(WorkerPool中的Worker)在Disruptor框架中共同协作,实现了高效的事件处理。BatchEventProcessor负责批量处理事件,而WorkProcessor(Worker)则作为执行这些处理任务的线程。通过合理配置和使用这两个组件,可以显著提高并发事件处理的性能和吞吐量。
3.9. 事件处理器 - EventHandler、WorkHandler
Disruptor定义的事件处理接口,由用户实现,用于处理事件,是Consumer的真正实现。
3.9.1 EventHandler
定义与功能:
- EventHandler是Disruptor框架中最基本的事件处理接口。它定义了单个事件的处理逻辑,即当事件被发布到Ring Buffer时应该如何处理。
- 框架会并发地将每个事件传递给所有的EventHandler实例进行处理,这些处理器实例可以在不同的线程中工作。
特点:
- 并行处理:EventHandler用于并行处理事件的场景。它允许多个EventHandler实例同时处理相同类型的事件,以提高并行处理的效率。
- 独立处理:每个EventHandler都独立地处理Ring Buffer中的事件,每个事件只会被一个EventHandler处理(但如果有多个EventHandler实例,它们会各自独立处理事件)。
使用场景:
- 当需要提高事件处理的并行性时,可以使用EventHandler。
- 每个EventHandler实例可以处理相同类型的事件,但它们之间的工作是独立的。
3.9.2. WorkHandler
定义与功能:
- WorkHandler是EventHandler的扩展。它定义了一组处理事件的工作线程,其中每个工作线程只会处理特定的事件序列。
- 同一事件序列中的事件会被顺序分发给不同的工作线程,保证同一事件序列的有序处理。
特点:
- 有序处理:WorkHandler保证同一事件序列的有序处理,即使这些事件是在不同的线程中处理的。
- 负载均衡:当有多个WorkHandler时,不同的事件序列可以被分发到不同的WorkHandler进行处理,从而实现负载均衡。
使用场景:
- 当需要保证事件序列的有序处理时,可以使用WorkHandler。
- WorkHandler通常用于实现“消费者-生产者”模式,其中一个或多个生产者向Ring Buffer发布事件,而多个工作线程则从Ring Buffer消费事件。
3.9.3. 总结
EventHandler | WorkHandler | |
---|---|---|
定义与功能 | Disruptor框架中最基本的事件处理接口,定义单个事件的处理逻辑。 | EventHandler的扩展,定义了一组处理事件的工作线程,保证同一事件序列的有序处理。 |
特点 | 1. 并行处理:允许多个EventHandler实例同时处理相同类型的事件。 | 1. 有序处理:保证同一事件序列的有序性。 |
2. 独立处理:每个EventHandler都独立地处理Ring Buffer中的事件。 | 2. 负载均衡:不同的事件序列可以被分发到不同的WorkHandler进行处理。 | |
使用场景 | 适用于需要提高事件处理并行性的场景。 | 适用于需要保证事件序列有序处理的场景,以及实现“消费者-生产者”模式的场景。 |
在Disruptor框架中,EventHandler和WorkHandler都是用于处理事件的重要组件,但它们各自适用于不同的场景和需求。用户可以根据具体的应用场景和需求选择合适的处理器类型来实现高效的事件处理。
3.10. 等待策略 - WaitStrategy
决定消费者如何等待生产者的策略方式,当消费者消费速度过快时,消费者等待是通过以下哪种策略进行等待。
3.11. 异常处理 - ExceptionHandler
ExceptionHandler
是一个重要的接口,用于处理在事件处理过程中发生的异常。由于Disruptor的设计目标是追求极致的性能,因此它假设事件处理过程是高效的,并且尽可能少地处理或忽略异常。然而,在实际应用中,异常处理是必不可少的,特别是在处理来自外部系统或不受控的数据源时,当事件处理器在处理事件时抛出异常时,Disruptor会调用配置的ExceptionHandler
来处理这个异常 。
4. Disruptor应用
4.1. 定义消费数据单位DataEvent
/**
* @Description:
* @author: zhangaobo
* @date: 2023/12/5 11:32
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataEvent<T> {
/**
* 业务数据
*/
private T data;
/**
* Prometheus 度量指标标签键
*/
private String tagKey;
/**
* Prometheus 度量指标标签值
*/
private String tagValue;
/**
* 用于清除ringbuffer数组中的数据,如果设置的队列大小过大,可能会导致OOM,调用此方法可以避免,
* 参照的是Log4j2的思路
*/
public void clear() {
this.data = null;
}
}
4.2. 定义生产者类DisruptorQueue
此类中DemoMeterRegistry是用户将Disruptor中的队列大小、队列长度、输入输出流量做监控。
/**
* @Description:
* @author: zhangaobo
* @date: 2023/12/5 005 11:32
*/
public class DisruptorQueue<T> {
/**
* disruptor
*/
private Disruptor<DataEvent<T>> disruptor;
/**
* 换冲区
*/
private RingBuffer<DataEvent<T>> ringBuffer;
/**
* 标签key
*/
private final String tagKey;
/**
* 标签value
*/
private final String tagValue;
/**
* metric监控
*/
private final DemoMeterRegistry demoMeterRegistry;
/**
* 构造方法
*
* @param tagKey Prometheus 度量指标标签键
* @param tagValue Prometheus 度量指标标签值
* @param disruptor disruptor
*/
public DisruptorQueue(String tagKey, String tagValue, Disruptor<DataEvent<T>> disruptor) {
this.disruptor = disruptor;
this.ringBuffer = disruptor.getRingBuffer();
this.tagKey = tagKey;
this.tagValue = tagValue;
// 获取metric监控对象
this.demoMeterRegistry= SpringUtil.getBean(DemoMeterRegistry.class);
// 新建一个 metric 键值对和指标对象
CalcMetricValue<T> calcMetricValue = new CalcMetricValue<>(tagKey, tagValue, this.ringBuffer);
// 将队列指标注册到监控中
calcMetricValue.bindTo(this.demoMeterRegistry);
this.disruptor.start();
}
/**
* 添加数据到队列
*
* @param data 数据
*/
public void add(T data) {
if (data != null) {
this.demoMeterRegistry.counter("input_disruptor", this.tagKey, this.tagValue).increment();
long sequence = this.ringBuffer.next();
try {
DataEvent<T> event = this.ringBuffer.get(sequence);
event.setData(data);
event.setTagKey(this.tagKey);
event.setTagValue(this.tagValue);
} finally {
this.ringBuffer.publish(sequence);
}
}
}
/**
* 添加数据列表到队列
*
* @param list 数据列表
*/
public void addAll(List<T> list) {
if (CollUtil.isEmpty(list)) {
return;
}
list.forEach(this::add);
}
public long cursor() {
return this.disruptor.getRingBuffer().getCursor();
}
public void shutdown() {
this.disruptor.shutdown();
}
public Disruptor<DataEvent<T>> getDisruptor() {
return this.disruptor;
}
public void setDisruptor(Disruptor<DataEvent<T>> disruptor) {
this.disruptor = disruptor;
}
public RingBuffer<DataEvent<T>> getRingBuffer() {
return this.ringBuffer;
}
public void setRingBuffer(RingBuffer<DataEvent<T>> ringBuffer) {
this.ringBuffer = ringBuffer;
}
}
4.3. 定义消费者BaseDisruptorConsumer
/**
* @Description:
* @author: zhangaobo
* @date: 2023/12/5 11:32
*/
public abstract class BaseDisruptorConsumer<T> implements EventHandler<DataEvent<T>>, WorkHandler<DataEvent<T>> {
private DemoMeterRegistry registry;
@Override
public void onEvent(DataEvent<T> event, long sequence, boolean endOfBatch) {
this.onEvent(event);
}
@Override
public void onEvent(DataEvent<T> event) {
// try {
if (Objects.isNull(registry)) {
registry = SpringUtil.getBean(DemoMeterRegistry .class);
}
this.consumer(event.getData());
registry.counter("output_disruptor", event.getTagKey(), event.getTagValue()).increment();
// } finally {
// event.clear();
// }
}
/**
* 消费处理方法
*
* @param data 数据
*/
public abstract void consumer(T data);
}
4.4. 定义DisruptorQueue的构建工厂
/**
* @Description:
* @author: zhangaobo
* @date: 2023/12/5 11:33
*/
public class DisruptorQueueFactory {
/**
* 定义单个队列的 Prometheus 度量指标标签值,自增避免多个 DisruptorQueue 重复无法注册到监控
*/
private static final LongAdder NUM = new LongAdder();
private static final LongAdder STRATEGY_NUM = new LongAdder();
private DisruptorQueueFactory() {
}
/**
* 创建"发布订阅模式"的操作队列,即同一事件会被多个消费者并行消费
*
* @param <T>
* @param queueSize 队列大小,必需是2的n次方
* @param isMoreProducer 是否有多发布者
* @param tagKey 队列的标签key
* @param tagValue 队列的标签Value
* @param consumers
* @return
*/
@SafeVarargs
public static <T> DisruptorQueue<T> getHandleEventsQueue(int queueSize, boolean isMoreProducer, String tagKey, String tagValue,
BaseDisruptorConsumer<T>... consumers) {
Disruptor<DataEvent<T>> disruptor = new Disruptor<>(new DataEventFactory<>(),
queueSize, Executors.defaultThreadFactory(),
isMoreProducer ? ProducerType.MULTI : ProducerType.SINGLE,
new BlockingWaitStrategy());
disruptor.setDefaultExceptionHandler(new DataEventExceptionHandler<>());
disruptor.handleEventsWith(consumers);
return new DisruptorQueue<>(tagKey, tagValue, disruptor);
}
/**
* 创建"发布订阅模式"的操作队列,即同一事件会被多个消费者并行消费
*
* @param <T>
* @param queueSize 队列大小,必需是2的n次方
* @param isMoreProducer 是否有多发布者
* @param tagKey 队列的标签key
* @param tagValue 队列的标签Value
* @param waitStrategy 消费等待策略
* @param consumers
* @return
*/
@SafeVarargs
public static <T> DisruptorQueue<T> getHandleEventsQueue(int queueSize, boolean isMoreProducer, String tagKey, String tagValue,
WaitStrategy waitStrategy, BaseDisruptorConsumer<T>... consumers) {
Disruptor<DataEvent<T>> disruptor = new Disruptor<>(new DataEventFactory<>(),
queueSize, Executors.defaultThreadFactory(),
isMoreProducer ? ProducerType.MULTI : ProducerType.SINGLE,
waitStrategy);
disruptor.setDefaultExceptionHandler(new DataEventExceptionHandler<>());
disruptor.handleEventsWith(consumers);
return new DisruptorQueue<>(tagKey, tagValue, disruptor);
}
/**
* 创建"发布订阅模式"的操作队列,即同一事件会被多个消费者并行消费
*
* @param <T>
* @param queueSize 队列大小,必需是2的n次方
* @param isMoreProducer 是否有多发布者
* @param consumers
* @return
*/
@SafeVarargs
public static <T> DisruptorQueue<T> getHandleEventsQueue(int queueSize, boolean isMoreProducer,
BaseDisruptorConsumer<T>... consumers) {
Disruptor<DataEvent<T>> disruptor = new Disruptor<>(new DataEventFactory<>(),
queueSize, Executors.defaultThreadFactory(),
isMoreProducer ? ProducerType.MULTI : ProducerType.SINGLE,
new BlockingWaitStrategy());
disruptor.setDefaultExceptionHandler(new DataEventExceptionHandler<>());
disruptor.handleEventsWith(consumers);
String appName = ThreadUtils.getCurrentInitBeanName();
int value = NUM.intValue();
NUM.increment();
return new DisruptorQueue<>(appName, String.valueOf(value), disruptor);
}
/**
* 创建"发布订阅模式"的操作队列,即同一事件会被多个消费者并行消费
*
* @param <T>
* @param queueSize 队列大小,必需是2的n次方
* @param isMoreProducer 是否有多发布者
* @param waitStrategy 消费等待策略
* @param consumers
* @return
*/
@SafeVarargs
public static <T> DisruptorQueue<T> getHandleEventsQueue(int queueSize, boolean isMoreProducer,
WaitStrategy waitStrategy, BaseDisruptorConsumer<T>... consumers) {
Disruptor<DataEvent<T>> disruptor = new Disruptor<>(new DataEventFactory<>(),
queueSize, Executors.defaultThreadFactory(),
isMoreProducer ? ProducerType.MULTI : ProducerType.SINGLE,
waitStrategy);
disruptor.setDefaultExceptionHandler(new DataEventExceptionHandler<>());
disruptor.handleEventsWith(consumers);
String appName = ThreadUtils.getCurrentInitBeanName() + "_waitStrategy";
int value = STRATEGY_NUM.intValue();
STRATEGY_NUM.increment();
return new DisruptorQueue<>(appName, String.valueOf(value), disruptor);
}
}
4.5. 自定义异常处理器DataEventExceptionHandler
/**
* @Description:
* @author: zhangaobo
* @date: 2023/12/5 11:32
*/
@Slf4j
public class DataEventExceptionHandler<T> implements ExceptionHandler<DataEvent<T>> {
@Override
public void handleEventException(Throwable t, long sequence, DataEvent<T> event) {
log.error("handleEventException: ", t);
}
@Override
public void handleOnStartException(Throwable t) {
log.error("handleOnStartException: ", t);
}
@Override
public void handleOnShutdownException(Throwable t) {
log.error("handleOnShutdownException: ", t);
}
}
4.6. 用法
普通使用,无法监控队列大小、队列长度、输入输出流量
public class DisruptorDemo {
public static void main(String[] args) {
queueTest();
}
/**
* 单消费者,发布订阅测试
*/
public static void queueTest() {
// 通过构建工厂创建DisruptorQueue对象
DisruptorQueue<Integer> queue = DisruptorQueueFactory.getHandleEventsQueue(8192, true, new BaseDisruptorConsumer<Integer>() {
@Override
public void consumer(Integer data) {
System.out.println("消费数据:" + data);
}
});
// 10次循环往队列加内容
int i = 0;
while (i < 10) {
queue.add(i++);
}
}
/**
* 工作流测试
*/
public static void queueFlowTest() {
DisruptorQueue<Integer> queueFlow = DisruptorQueueFactory.getHandleEventsQueueFlow(8192, true, new BaseDisruptorConsumer<Integer>() {
@Override
public void consumer(Integer data) {
System.out.println("consumer1 消费数据:" + data);
}
}, new BaseDisruptorConsumer<Integer>() {
@Override
public void consumer(Integer data) {
System.out.println("consumer2 消费数据:" + data);
}
}, new BaseDisruptorConsumer<Integer>() {
@Override
public void consumer(Integer data) {
System.out.println("consumer3 消费数据:" + data);
}
});
// 10次循环往队列加内容
int i = 0;
while (i < 10) {
queueFlow.add(i++);
}
}
}
执行queueTest结果如下:一个消费者消费,是有序的,
执行queueFlowTest果如下,三个消费者,其中consumer3是consumer1和consumer2完成后再执行的。显然,多消费者,如果不是使用工作流模式,则是消费者之间不是有序的,但是其内部仍是有序的。
Springboot中,可以将DisruptorQueue定义到IOC容器中使用,队列大小可自行设置,并且可以监控Disruptor队列大小、队列长度、输入输出流量
@Configuration
public class DisruptorConfig {
@Value("${demo.queue.size:32768}")
private Integer queueSize;
@Bean
public DisruptorQueue<Consumer<String>> disruptorQueue() {
return DisruptorQueueFactory.getHandleEventsQueue(queueSize, true, new BaseDisruptorConsumer<Consumer<String>>() {
@Override
public void consumer(Consumer<String> consumer) {
consumer.accept("");
}
});
}
}
值得注意的是,Disruptor的事件处理类的onEvent方法是void方法,是不会有返回值的,如果需要获取该方法中的某些值,需要在外部定义,如果涉及多消费者,还需要考虑线程安全问题。
4.7. Prometheus监控Disruptor指标
public interface DemoMeterBinder {
void bindTo(DemoMeterRegistry registry);
}
public class CalcMetricValue<T> implements DemoMeterBinder {
/**
* 当前disruptor的标签
*/
private final String tagKey;
private final String tagValue;
private final RingBuffer<DataEvent<T>> ringBuffer;
public CalcMetricValue(String tagKey, String tagValue, RingBuffer<DataEvent<T>> ringBuffer) {
this.tagKey = tagKey;
this.tagValue = tagValue;
this.ringBuffer = ringBuffer;
}
@Override
public void bindTo(DemoMeterRegistry registry) {
Gauge.builder("disruptor.buffer.size", this.ringBuffer, RingBuffer::getBufferSize)
.tags(tagKey, tagValue)
.description("Buffer size of Disruptor")
.baseUnit(BaseUnits.BYTES)
.register(registry);
Gauge.builder("disruptor.remaining.capacity", this.ringBuffer, RingBuffer::remainingCapacity)
.tags(tagKey, tagValue)
.description("Remaining capacity of Disruptor")
.baseUnit(BaseUnits.BYTES)
.register(registry);
}
}
public class DemoMeterRegistry extends PrometheusMeterRegistry {
private List<DemoMeterBinder> meterBinderList;
private List<Consumer<DemoMeterRegistry>> consumerList;
public DemoMeterRegistry() {
this(PrometheusConfig.DEFAULT);
}
public DemoMeterRegistry(PrometheusConfig config) {
super(config, new CollectorRegistry(), Clock.SYSTEM);
this.meterBinderList = new ArrayList();
this.consumerList = new ArrayList();
}
public DemoMeterRegistry(PrometheusConfig config, CollectorRegistry registry, Clock clock) {
super(config, registry, clock);
this.meterBinderList = new ArrayList();
this.consumerList = new ArrayList();
}
public void scrape(Writer writer) throws IOException {
this.consumerList.forEach((consumer) -> consumer.accept(this));
TextFormat.write004(writer, this.getPrometheusRegistry().metricFamilySamples());
}
@PostConstruct
public void init() {
this.config().meterFilter(new EhcacheFilter());
this.meterBinderList.forEach((meterBinder) -> meterBinder.bindTo(this));
}
}
public abstract class DemoCollector<Child> extends SimpleCollector<Child> {
protected DemoCollector(Builder b) {
super(b);
}
public int getChildrenSize() {
return super.children.size();
}
public abstract String getCollectorId();
public abstract void removeChild();
}
public class EhcacheFilter implements MeterFilter {
private HashSet<String> ignoreMetrics = new HashSet() {
{
this.add("cache.local.offheap.size");
this.add("cache.local.heap.size");
this.add("cache.local.disk.size");
}
};
public EhcacheFilter() {
}
public MeterFilterReply accept(Id id) {
return this.ignoreMetrics.contains(id.getName()) ? MeterFilterReply.DENY : MeterFilterReply.NEUTRAL;
}
}
@RestController
@RequestMapping({"/status/metrics"})
public class MetricsController {
@Autowired(required = false)
@Qualifier("carbondMeterRegistry")
private DemoMeterRegistry meterRegistry;
private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
public MetricsController() {
}
@GetMapping
public void getMetrics(HttpServletResponse resp) {
try {
Writer writer = resp.getWriter();
Throwable throwable = null;
try {
resp.setStatus(200);
resp.setContentType("text/plain; version=0.0.4; charset=utf-8");
if (null != this.meterRegistry) {
this.meterRegistry.scrape(writer);
} else {
writer.append("# HELP no data");
}
writer.flush();
} catch (Throwable throwable1) {
throwable = throwable1;
throw throwable1;
} finally {
if (writer != null) {
if (throwable != null) {
try {
writer.close();
} catch (Throwable throwable2) {
throwable.addSuppressed(throwable2);
}
} else {
writer.close();
}
}
}
} catch (Exception e) {
this.logger.log(Level.WARNING, e.toString(), e);
}
}
}
启动Springboot应用后,访问 http://127.0.0.1:8080/status/metrics ,后续可以放到Darshbord做成表格便于观看