disruptor笔记之三:环形队列的基础操作(不用Disruptor类)

@Slf4j

public class StringEventHandler implements EventHandler {

public StringEventHandler(Consumer<?> consumer) {

this.consumer = consumer;

}

// 外部可以传入Consumer实现类,每处理一条消息的时候,consumer的accept方法就会被执行一次

private Consumer<?> consumer;

@Override

public void onEvent(StringEvent event, long sequence, boolean endOfBatch) throws Exception {

log.info(“sequence [{}], endOfBatch [{}], event : {}”, sequence, endOfBatch, event);

// 这里延时100ms,模拟消费事件的逻辑的耗时

Thread.sleep(100);

// 如果外部传入了consumer,就要执行一次accept方法

if (null!=consumer) {

consumer.accept(null);

}

}

}

  • 定义一个接口,外部通过调用接口的方法来生产消息,再放几个常量在里面后面会用到:

package com.bolingcavalry.service;

public interface LowLevelOperateService {

/**

  • 消费者数量

*/

int CONSUMER_NUM = 3;

/**

  • 环形缓冲区大小

*/

int BUFFER_SIZE = 16;

/**

  • 发布一个事件

  • @param value

  • @return

*/

void publish(String value);

/**

  • 返回已经处理的任务总数

  • @return

*/

long eventCount();

}

  • 以上就是公共代码了,接下来逐个实现之前规划的三个场景;

100个事件,单个消费者消费

  • 这是最简单的功能了,实现发布消息和单个消费者消费的功能,代码如下,有几处要注意的地方稍后提到:

package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.*;

import com.lmax.disruptor.BatchEventProcessor;

import com.lmax.disruptor.RingBuffer;

import com.lmax.disruptor.SequenceBarrier;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.atomic.AtomicLong;

import java.util.function.Consumer;

@Service(“oneConsumer”)

@Slf4j

public class OneConsumerServiceImpl implements LowLevelOperateService {

private RingBuffer ringBuffer;

private StringEventProducer producer;

/**

  • 统计消息总数

*/

private final AtomicLong eventCount = new AtomicLong();

private ExecutorService executors;

@PostConstruct

private void init() {

// 准备一个匿名类,传给disruptor的事件处理类,

// 这样每次处理事件时,都会将已经处理事件的总数打印出来

Consumer<?> eventCountPrinter = new Consumer() {

@Override

public void accept(Object o) {

long count = eventCount.incrementAndGet();

log.info(“receive [{}] event”, count);

}

};

// 创建环形队列实例

ringBuffer = RingBuffer.createSingleProducer(new StringEventFactory(), BUFFER_SIZE);

// 准备线程池

executors = Executors.newFixedThreadPool(1);

//创建SequenceBarrier

SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();

// 创建事件处理的工作类,里面执行StringEventHandler处理事件

BatchEventProcessor batchEventProcessor = new BatchEventProcessor<>(

ringBuffer,

sequenceBarrier,

new StringEventHandler(eventCountPrinter));

// 将消费者的sequence传给环形队列

ringBuffer.addGatingSequences(batchEventProcessor.getSequence());

// 在一个独立线程中取事件并消费

executors.submit(batchEventProcessor);

// 生产者

producer = new StringEventProducer(ringBuffer);

}

@Override

public void publish(String value) {

producer.onData(value);

}

@Override

public long eventC

必看视频!获取2024年最新Java开发全套学习资料 备注Java

ount() {

return eventCount.get();

}

}

  • 上述代码有以下几处需要注意:
  1. 自己创建环形队列RingBuffer实例

  2. 自己准备线程池,里面的线程用来获取和消费消息

  3. 自己动手创建BatchEventProcessor实例,并把事件处理类传入

  4. 通过ringBuffer创建sequenceBarrier,传给BatchEventProcessor实例使用

  5. 将BatchEventProcessor的sequence传给ringBuffer,确保ringBuffer的生产和消费不会出现混乱

  6. 启动线程池,意味着BatchEventProcessor实例在一个独立线程中不断的从ringBuffer中获取事件并消费;

  • 为了验证上述代码能否正常工作,我这里写了个单元测试类,如下所示,逻辑很简单,调用OneConsumerServiceImpl.publish方法一百次,产生一百个事件,再检查OneConsumerServiceImpl记录的消费事件总数是不是等于一百:

package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.LowLevelOperateService;

import lombok.extern.slf4j.Slf4j;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)

@SpringBootTest

@Slf4j

public class LowLeverOperateServiceImplTest {

@Autowired

@Qualifier(“oneConsumer”)

LowLevelOperateService oneConsumer;

private static final int EVENT_COUNT = 100;

private void testLowLevelOperateService(LowLevelOperateService service, int eventCount, int expectEventCount) throws InterruptedException {

for(int i=0;i<eventCount;i++) {

log.info(“publich {}”, i);

service.publish(String.valueOf(i));

}

// 异步消费,因此需要延时等待

Thread.sleep(10000);

// 消费的事件总数应该等于发布的事件数

assertEquals(expectEventCount, service.eventCount());

}

@Test

public void testOneConsumer() throws InterruptedException {

log.info(“start testOneConsumerService”);

testLowLevelOperateService(oneConsumer, EVENT_COUNT, EVENT_COUNT);

}

  • 注意,如果您是直接在IDEA上点击图标来执行单元测试,记得勾选下图红框中选项,否则可能出现编译失败:

在这里插入图片描述

  • 执行上述单元测试类,结果如下图所示,消息的生产和消费都符合预期,并且消费逻辑是在独立线程中执行的:

在这里插入图片描述

  • 继续挑战下一个场景;

100个事件,三个消费者,每个都独自消费这个100个事件

  • 这个场景在kafka中也有,就是三个消费者的group不同,这样每一条消息,这两个消费者各自消费一次;

  • 因此,100个事件,3个消费者每人都会独立消费这100个事件,一共消费300次;

  • 代码如下,有几处要注意的地方稍后提到:

package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.*;

import com.lmax.disruptor.BatchEventProcessor;

import com.lmax.disruptor.RingBuffer;

import com.lmax.disruptor.SequenceBarrier;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

import java.util.concurrent.Executor;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.atomic.AtomicLong;

import java.util.function.Consumer;

@Service(“multiConsumer”)

@Slf4j

public class MultiConsumerServiceImpl implements LowLevelOperateService {

private RingBuffer ringBuffer;

private StringEventProducer producer;

/**

  • 统计消息总数

*/

private final AtomicLong eventCount = new AtomicLong();

/**

  • 生产一个BatchEventProcessor实例,并且启动独立线程开始获取和消费消息

  • @param executorService

*/

private void addProcessor(ExecutorService executorService) {

// 准备一个匿名类,传给disruptor的事件处理类,

// 这样每次处理事件时,都会将已经处理事件的总数打印出来

Consumer<?> eventCountPrinter = new Consumer() {

@Override

public void accept(Object o) {

long count = eventCount.incrementAndGet();

log.info(“receive [{}] event”, count);

}

};

BatchEventProcessor batchEventProcessor = new BatchEventProcessor<>(

ringBuffer,

ringBuffer.newBarrier(),

new StringEventHandler(eventCountPrinter));

// 将当前消费者的sequence实例传给ringBuffer

ringBuffer.addGatingSequences(batchEventProcessor.getSequence());

// 启动独立线程获取和消费事件

executorService.submit(batchEventProcessor);

}

@PostConstruct

private void init() {

ringBuffer = RingBuffer.createSingleProducer(new StringEventFactory(), BUFFER_SIZE);

ExecutorService executorService = Executors.newFixedThreadPool(CONSUMER_NUM);

// 创建多个消费者,并在独立线程中获取和消费事件

for (int i=0;i<CONSUMER_NUM;i++) {

addProcessor(executorService);

}

// 生产者

producer = new StringEventProducer(ringBuffer);

}

@Override

public void publish(String value) {

producer.onData(value);

}

@Override

public long eventCount() {

return eventCount.get();

}

}

  • 上述代码和前面的OneConsumerServiceImpl相比差别不大,主要是创建了多个BatchEventProcessor实例,然后分别在线程池中提交;

  • 验证方法依旧是单元测试,在刚才的LowLeverOperateServiceImplTest.java中增加代码即可,注意testLowLevelOperateService的第三个参数是EVENT_COUNT * LowLevelOperateService.CONSUMER_NUM,表示预期的被消费消息数为300

@Autowired

@Qualifier(“multiConsumer”)

LowLevelOperateService multiConsumer;

@Test

public void testMultiConsumer() throws InterruptedException {

log.info(“start testMultiConsumer”);

testLowLevelOperateService(multiConsumer, EVENT_COUNT, EVENT_COUNT * LowLevelOperateService.CONSUMER_NUM);

}

  • 执行单元测试,如下图所示,一共消费了300个事件,并且三个消费者在不动线程:

在这里插入图片描述

100个事件,三个消费者共同消费这个100个事件

  • 本篇的最后一个实战是发布100个事件,然后让三个消费者共同消费100个(例如A消费33个,B消费33个,C消费34个);

  • 前面用到的BatchEventProcessor是用来独立消费的,不适合多个消费者共同消费,这种多个消费共同消费的场景需要借助WorkerPool来完成,这个名字还是很形象的:一个池子里面有很多个工作者,把任务放入这个池子,工作者们每人处理一部分,大家合力将任务完成;

  • 传入WorkerPool的消费者需要实现WorkHandler接口,于是新增一个实现类:

package com.bolingcavalry.service;

import com.lmax.disruptor.WorkHandler;

import lombok.extern.slf4j.Slf4j;

import java.util.function.Consumer;

@Slf4j

public class StringWorkHandler implements WorkHandler {

public StringWorkHandler(Consumer<?> consumer) {

this.consumer = consumer;

}

// 外部可以传入Consumer实现类,每处理一条消息的时候,consumer的accept方法就会被执行一次

private Consumer<?> consumer;

@Override

public void onEvent(StringEvent event) throws Exception {

log.info(“work handler event : {}”, event);

// 这里延时100ms,模拟消费事件的逻辑的耗时

Thread.sleep(100);

// 如果外部传入了consumer,就要执行一次accept方法

if (null!=consumer) {

consumer.accept(null);

}

}

}

  • 新增服务类,实现共同消费的逻辑,有几处要注意的地方稍后会提到:

package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.*;

import com.lmax.disruptor.*;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.atomic.AtomicLong;

import java.util.function.Consumer;

@Service(“workerPoolConsumer”)

@Slf4j

public class WorkerPoolConsumerServiceImpl implements LowLevelOperateService {

private RingBuffer ringBuffer;

private StringEventProducer producer;

/**

  • 统计消息总数

*/

private final AtomicLong eventCount = new AtomicLong();

@PostConstruct

private void init() {

ringBuffer = RingBuffer.createSingleProducer(new StringEventFactory(), BUFFER_SIZE);

ExecutorService executorService = Executors.newFixedThreadPool(CONSUMER_NUM);

StringWorkHandler[] handlers = new StringWorkHandler[CONSUMER_NUM];

// 创建多个StringWorkHandler实例,放入一个数组中

for (int i=0;i < CONSUMER_NUM;i++) {

handlers[i] = new StringWorkHandler(o -> {

long count = eventCount.incrementAndGet();

log.info(“receive [{}] event”, count);

});

}

// 创建WorkerPool实例,将StringWorkHandler实例的数组传进去,代表共同消费者的数量

WorkerPool workerPool = new WorkerPool<>(ringBuffer, ringBuffer.newBarrier(), new IgnoreExceptionHandler(), handlers);

// 这一句很重要,去掉就会出现重复消费同一个事件的问题

ringBuffer.addGatingSequences(workerPool.getWorkerSequences());

workerPool.start(executorService);

// 生产者

producer = new StringEventProducer(ringBuffer);

}

@Override

public void publish(String value) {

producer.onData(value);

}

@Override

public long eventCount() {

return eventCount.get();

}

}

  • 上述代码中,要注意的有以下两处:
  1. StringWorkHandler数组传入给WorkerPool后,每个StringWorkHandler实例都放入一个新的WorkProcessor实例,WorkProcessor实现了Runnable接口,在执行workerPool.start时,会将WorkProcessor提交到线程池中;

  2. 和前面的独立消费相比,共同消费最大的特点在于只调用了一次ringBuffer.addGatingSequences方法,也就是说三个消费者共用一个sequence实例;

  • 验证方法依旧是单元测试,在刚才的LowLeverOperateServiceImplTest.java中增加代码即可,注意testWorkerPoolConsumer的第三个参数是EVENT_COUNT,表示预期的被消费消息数为100

@Autowired

@Qualifier(“workerPoolConsumer”)

LowLevelOperateService workerPoolConsumer;

@Test

public void testWorkerPoolConsumer() throws InterruptedException {

log.info(“start testWorkerPoolConsumer”);

testLowLevelOperateService(workerPoolConsumer, EVENT_COUNT, EVENT_COUNT);

}

面试准备+复习分享:

为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦

秋招|美团java一面二面HR面面经,分享攒攒人品

{

return eventCount.get();

}

}

  • 上述代码中,要注意的有以下两处:
  1. StringWorkHandler数组传入给WorkerPool后,每个StringWorkHandler实例都放入一个新的WorkProcessor实例,WorkProcessor实现了Runnable接口,在执行workerPool.start时,会将WorkProcessor提交到线程池中;

  2. 和前面的独立消费相比,共同消费最大的特点在于只调用了一次ringBuffer.addGatingSequences方法,也就是说三个消费者共用一个sequence实例;

  • 验证方法依旧是单元测试,在刚才的LowLeverOperateServiceImplTest.java中增加代码即可,注意testWorkerPoolConsumer的第三个参数是EVENT_COUNT,表示预期的被消费消息数为100

@Autowired

@Qualifier(“workerPoolConsumer”)

LowLevelOperateService workerPoolConsumer;

@Test

public void testWorkerPoolConsumer() throws InterruptedException {

log.info(“start testWorkerPoolConsumer”);

testLowLevelOperateService(workerPoolConsumer, EVENT_COUNT, EVENT_COUNT);

}

面试准备+复习分享:

为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦

[外链图片转存中…(img-5UHKCrQd-1716406926350)]

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值