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 其他支持的功能
- 生成Disruptor对象的时候可以指定单生产者和多生产者,也就是单线程和多线程的生产者。
- 支持多消费者,并且多消费者可以有顺序
Disruptor 为什么这么快
Disruptor 原理图:
RingBuffer 是个环形数组
Producter 向里面写入消息
消费者们通过 SequenceBarrier 来消费消息
消费者等待队列消息的时候可以执行 waitFor()的策略
- Disruptor 是怎么解决之前队列里面性能问题的
-
数组要比链表快,内存空间连续,对 CPU 友好
-
传统队列中在队列末尾多个生产者争抢一个尾标这种情况下总是需要加锁,但是Disruptor是每个生产者都会有自己的一个 Sequence,在生产者投放消息的过程中,先使用 CAS 申请空间,申请成功后根据返回的最大序列来投放消息。
-
伪共享的问题解决
每个线程要处理变量,需要先从主内存读取变量到本地中,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在写以后添加了写屏障、读之前又添加了读屏障