多线程,设计模式,Netty 实战,带你手写一个分布式消息队列
开题
什么是 MQ ?
MQ 就是 Message Queue 的简写,消息队列中间件。用过 MQ 的同学应该都知道,MQ 的主要作用是应用程序的解藕和异步。如果我们在更高的层面去想,MQ 是不是可以理解为一种应用层的通信协议,它实现了多个应用之间的异步通信,同时 MQ 对于用户屏蔽了底层通信协议的实现,我们不管它的 HTTP,TCP 还是 Netty,最终实现的就是一个生产者/消费者模型。
MQ 有什么作用?
异步处理
思考一下,如果我们需要写一个注册流程,注册成功需要发送短信和邮件。如果我们把整个流程写成同步操作,整个接口就会变得很慢,而且这个接口也会变得脆弱,发短信和发邮件这两个非核心业务很容易影响到注册这个核心业务。
MQ 如何在这个流程中发挥作用?
发送邮件和短信完全可以异步处理,注册成功,发个消息给消息中间件后直接返回,让中间件去异步地发邮件和短信。
应用解藕
对于我们的应用来说,我们想让它们实现的是低耦合、高内聚。系统的耦合性越高就越容易出错。
比如在我们的分布式系统中,订单服务和库存服务分别部署在不同的服务器上。用户下单调用订单服务后通过 RPC 调用库存服务,如果这时库存系统挂了,我们的订单是不是就失败了?这样的用户体验一定非常不好。
MQ 此时又能发挥作用,通过发布订阅模式,可以实现订单服务和库存服务的解藕。订单服务的成功与失败不再依赖于库存服务。
我们的系统可能会在某个时间段内请求瞬间猛涨,会让系统崩溃。此时 MQ 可以将大量请求缓存起来,分散到很长一段时间处理,避免大量的请求直接到达后端系统。这样可以提高系统可用和稳定性。
数据分发
MQ 有哪些流派?
总体分为两种:
1. 有 Broker 的 MQze-adjust:none;-webkit-font-smoothing:antialiased;box-sizing:border-box;margin:0px 0px 1.1em;outline:0px;">Kafka、ActiveMQ、RabbitMQ 都属于这种类型的 MQ。
2. 没有 Broker 的 MQ
上一节我们说过,如果从更高的层面上来思考,MQ 就是一种应用层的通信协议,所以只需要互相通信的两个系统即可。无 Broker Server 的 MQ 的代表就是 ZeroMQ。每个系统即是生产者又是消费者。MQ 是更高级的 Socket,它是用来解决通信问题的。
实战
设计架构
通过上一节我们知道,目前市场上的主流 MQ 还是比较多的,有的是有 Borker Server 的,有的是没有 Broker Server 的。我们的 MQ 采取有 Borker Server 这种模式来实现。消息的推送采取 Push 模型(就是服务端主动推送消息给消费者,感兴趣的同学也可以自己了解以下 Pull 模型,目前主流的 MQ 支持 Push 和 Pull 两个模型,而且 Pull 模型用的比较多)。
既然说是分布式消息队列,那么系统之间的通信怎样实现?相信很多同学已经知道了,使用优秀的 NIO 框架 Netty。
下面是我们这个分布式消息队列的主要模块:
- Producer:生产者,负责发送消息。
- Consumer:消费者,负责接收消息。
- Broker Server:负责消息调度。
- Netty:通信模块。
- Spring:与 Spring 整合模块。
特点
我们的 MQ 有哪些特点?
- 支持多生产者、多消费者模式,支持一对多、多对多等模式。
- 支持多个工作队列互相隔离。
- 支持路由键、广播等多种模式。
- 网络通信基于 Netty 编写。
- 基于 Broker 模型开发,消息派发采取 Push 模式,消息派发支持轮询等模式。
- 消息派发顺序严格,支持缓存派发。
- 支持动态增加生产者、消费者。
结构图
- 生产者发送消息
- Broker Server 收到消息,将消息进行持久化,在存储中新增一条记录
- 返回 Ack 给生产者
- Broker Server push 消息给对应的消费者,然后等待消费者返回 Ack
- 如果消息消费者在指定时间内返回 Ack,那么 MQ 会认为消息消费成功,在存储中删除消息,即执行第 6 步。如果 MQ 在指定时间内没有收到 Ack,则认为消息消费失败,会尝试重新 push 消息,重复执行 4,5,6 步骤(目前持久化还未实现)。family:“Hiragino Sans GB”, “Microsoft YaHei”;font-weight:300;line-height:1.1;font-size:1em;margin:1.5em 0px;text-align:start;outline:0px;">缺点
- 目前没有持久化相关模块,我们的 MQ 一但挂了,消息很有可能丢失。持久化可以选择数据库,但更好的方法是直接刷盘,写入到磁盘文件中,毕竟数据库也是写文件。
- 对于一个 MQ 来说高可用性也是必须要有的,如果我们的 Broker Server 挂了整个系统也就挂了,那么一个主从模型是必须要有的。
- 我们目前的 MQ 是没有名称服务模块的,这个模块不但能够为生产者消费者提供名称服务,也能作为主从架构的注册中心。
- 虽然我们把功能实现了,但是它与市场上的主流经过生产检验的框架还相差甚远。目前也无法保证其安全性与可靠性。
目的
由于时间仓促加上自身技术的局限,这个系统肯定有很多不完美和错误的地方。加上没有专业的测试人员和真正的生产环境的检测,我们的系统仍然有许多要改进的地方。虽然现在距离那些主流框架差距很大,但是通过实战掌握多线程、设计模式、Netty 和 MQ 的原理,这个框架就非常有意义了。
目前大部分程序员只会 CURD,如何提高?纸上得来终觉浅,得知此事要躬行。
运行结果
话不多说,让我们来看看实际的项目运行结果。
我们的 MQ 有四种工作模式。
1. 简单队列工作模式
MQ 里面可以有多个简单队列。如果有一个消费者,消息会被这个消费者所消费,如果有多个消费者,会依次轮询进行消费。
2. 发布订阅模式
发布订阅模式的直观效果是,这个队列里面的所有消费者都会消费这个消息。需要连接 Exchange,Exchange 可以连接多个消费者,整个 Exchange 相当于一个集群。
3. 直接路由模式t-font-smoothing:antialiased;box-sizing:border-box;margin:0px 0px 1.1em;outline:0px;">
**
4. Topic 模式(正则路由)
Topic 模式在路由模式的基础上行对路径进行了模糊匹配。需要连接 Exchange。
运行
1. 首先启动 Broker Server 服务器
**
2. 按照简单队列工作模式启动两个消费者
ed;box-sizing:border-box;font-weight:bold;outline:0px;"\>import com.paul.mq.consumer.ReceiveMessageCallBack;
import com.paul.mq.entity.Exchange;
import com.paul.mq.entity.Message;
public class Consumer1 {
public static ReceiveMessageCallBack callBack = new ReceiveMessageCallBack(){
@Override
public void onCallBack(Message message) {
System.out.printf("PaulMQConsumer1 收到消息编号:%s,消息内容:%s\\n", message.getMsgId(), new String(message.getBody()));
}
};
public static void main(String[] args){
PaulMQConsumer consumer = new PaulMQConsumer("0.0.0.0",8092,"phonequeue",null,callBack);
consumer.init();
consumer.start();
}
}
public class Producer1 {
public static void main(String[] args){
PaulMQProducer producer = new PaulMQProducer("0.0.0.0",8092);
producer.init();
producer.start();
System.out.println("开始发送数据");
Message message = new Message();
String str = "Hello PaulMQ From Producer1[" + 1 + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.produce(WorkMode.WORKER_ROBIN,"phonequeue",null,message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("PaulMQProducer1 生产消息发送成功得到反馈\\n", result.getMsgId());
}
System.out.println("发送数据结束");
producer.stop();
}
}
启动两次生产者过后,两个消费者各收到一次生产者发送的信息,我们的简单队列工作模式没有问题。
3. 发布订阅模式启动三个消费者
public Consumer2 {
public @Override
onCallBack(Message message) {
System.out.printf("PaulMQConsumer2-3 收到消息编号:%s,消息内容:%s\\n", message.getMsgId(), new String(message.getBody()));
}
};
public static void main(String[] args){
Exchange e = new Exchange();
e.setName("exchange\_sub");
PaulMQConsumer consumer = new PaulMQConsumer("0.0.0.0",8092,"",e,callBack);
consumer.init();
consumer.start();
}
}
生产者使用 EXCHANGE_FANOUT 工作模式。
public class Producer2 {
public static void main(String[] args){
PaulMQProducer producer = new PaulMQProducer("0.0.0.0",8092);
producer.init();
producer.start();
Exchange e = new Exchange();
e.setName("exchange\_sub");
System.out.println("开始发送数据");
Message message = new Message();
String str = "Hello PaulMQ From Producer1[" + 1 + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.produce(WorkMode.EXCHANGE_FANOUT,"",e,message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("PaulMQProducer1 生产消息发送成功得到反馈\\n", result.getMsgId());
}
System.out.println("发送数据结束");
producer.stop();
}
}
所有订阅的消费者都收到了消息。
4. 直接路由模式启动两个消费者
两个消费者分别使用“insert”和“update”路由键启动。
public public static ReceiveMessageCallBack callBack = public (Message message) { System.out.printf("PaulMQConsumer2-3 收到消息编号:%s,消息内容:%s\\n", message.getMsgId(), public static void main(String[] args){ Exchange e = new Exchange(); e.setName("exchange\_sub"); e.setRegrex("insert"); PaulMQConsumer consumer = new PaulMQConsumer("0.0.0.0",8092,"",e,callBack); consumer.init(); consumer.start(); } }
生产者使用 EXCHANGE_DIRECT 工作模式。
public class Producer3 {
public static void main(String[] args){
PaulMQProducer producer = new PaulMQProducer("0.0.0.0",8092);
producer.init();
producer.start();
Exchange e = new Exchange();
e.setName("exchange\_sub");
e.setRegrex("insert");
System.out.println("开始发送数据");
Message message = new Message();
String str = "Hello PaulMQ From Producer1[" + 1 + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.produce(WorkMode.EXCHANGE_DIRECT,"",e,message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("PaulMQProducer1 生产消息发送成功得到反馈\\n", result.getMsgId());
}
System.out.println("发送数据结束");
producer.stop();
}
}
只有与生产者路由键相同的消费者收到了消息。
5. Topic 模式启动两个消费者
两个消费者分别使用正则表达式:
public class Consumer4 {
public static ReceiveMessageCallBack callBack = new ReceiveMessageCallBack(){
@Override
public void "PaulMQConsumer4-1 收到消息编号:%s,消息内容:%s\\n", message.getMsgId(), public static void new Exchange(); exchange.setName("Exchanger"); exchange.setRegrex(".\*update.\*"); PaulMQConsumer consumer = new PaulMQConsumer("0.0.0.0",8092,"",exchange,callBack); consumer.init(); consumer.start(); } }
生产者使用 EXCHANGE_TOPIC 模式启动,路由键是“m.update”。
public class Producer4 {
public static void main(String[] args){
PaulMQProducer producer = new PaulMQProducer("127.0.0.1",8092);
producer.init();
producer.start();
System.out.println("开始发送数据");
Exchange exchange = new Exchange();
exchange.setName("Exchanger");
exchange.setRegrex("m.update");
Message message = new Message();
String str = "Hello PaulMQ From Producer1[" + 1 + "]";
message.setBody(str.getBytes());
ProducerAckMessage result = producer.produce(WorkMode.EXCHANGE_TOPIC,"",exchange,message);
if (result.getStatus() == (ProducerAckMessage.SUCCESS)) {
System.out.printf("PaulMQProducer1 生产消息发送成功得到反馈\\n", result.getMsgId());
}
System.out.println("发送数据结束");
producer.stop();
}
}
只有正则表达式匹配成功的消费者收到了消息。
具体实现
- broker:Broker Server 模块,负责管理生产者和消费者,以及消息路由,接收和中转生产者和消费者发送的消息。是整个分布式消息队列最为核心的模块。
- common:通用组件。
- consumer:消费者对应的模块,主要是消费 Broker Server 发送过来的消息。
- core:核心消息派发模块,多线程操作相关类。
- entity:项目用到的所有实体类都在这个包中。
- netty:整个分布式消息队列的核心通讯模块,封装了 Netty 网络通信相关类。
- producer:生产者对应的模块,负责与 Broker Server 进行通信以及消息的发送。
- serialize:Netty 网络通信的序列化。
- test:测试相关模块,包含我们写的分布式队列的多种模式。
- util:项目工具类。
代码介绍
1. 首先我们想,既然是分布式消息队列,那么我们的生产者和消费者都需要与 Broker Server 进行通信,生产者和消费者都是客户端,Broker Server 是服务端。我们把客户端的代码先抽象出来,这样生产者和消费者只要继承这个类就可以与 Broker Server 进行通信了。所有的服务启动都包含初始化、开始和停止三个步骤,我们把这三个函数抽象出来。
public interface void initvoid start(); void er-box;margin:0px 0px 1.1em;outline:0px;"\>Netty 客户端启动是也是启动服务,所以需要继承这个接口。NettyClent 是 Netty 客户端连接类,关于 Netty 的代码这里就不做过多介绍了,如果不熟的可以看我以前的 Netty 实战的 Chat。这个类主要注意两个点,第一个是对应的 handler 需要我们自己实现和传入。第二个是我们通过 CallBack 这个类使得 Netty 的异步操作变为同步。
```
/\*\* \* netty 客户端,producer 和 consumer 都需要使用这个类连接 netty 服务端 \* @author swang18 \* \*/
public class NettyClient implements MQServer {
//服务端地址
private SocketAddress remoteAddr;
//客户端的 netty handler,外部传入
private ChannelInboundHandlerAdapter clientHandler;
//连接成功后的 channel
private Channel channel;
//是否连接成功
private boolean connected = false;
//发送消息的超时时间
private int timeout = 10*1000;
//每个请求的回调,通过这个保存所有请求,CallBack 将 netty 的异步调用变为同步
Map<String,CallBack<Object>> callBackMap = new ConcurrentHashMap<String, CallBack<Object>>();
private static KryoCodecUtil util = new KryoCodecUtil(KryoPoolFactory.getKryoPoolInstance());
private Bootstrap bootstrap;
private EventLoopGroup eventLoopGroup;
public NettyClient(String host,Integer port){
remoteAddr = new InetSocketAddress(host,port);
}
//初始化 netty 连接配置
public void init() {
//功能描述:检查boolean是否为真。 用作方法中检查参数
//失败时抛出的异常类型: IllegalArgumentException
if(null == clientHandler){
throw new RuntimeException("Client Handler is Null!");
}
bootstrap = new Bootstrap();
eventLoopGroup = new NioEventLoopGroup();
try{
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MessageObjectEncoder(util));
pipeline.addLast(new MessageObjectDecoder(util));
pipeline.addLast(clientHandler);
}
});
}catch (Exception e){
e.printStackTrace();
}
}
// 客户端(生产者/消费者)连接服务端 broker server
public void start() {
init();
ChannelFuture channelFuture = null;
try {
channelFuture = bootstrap.connect(this.remoteAddr).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
channel = future.channel();
}
});
System.out.println("connect broker server succeess,borker server ip address:" + this.remoteAddr.toString());
connected = true;
}
public void stop() {
if(null != channel){
try {
//关闭 channel
channel.close().sync();
eventLoopGroup.shutdownGracefully();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//返回连接成功后的 channel
public Channel getChannel(){
return channel;
}
// 返回是否连接成功
public boolean isConnected(){
return connected;
}
// 返回当前 channel 的所有 request 的 callbackMap 集合
public Map<String,CallBack<Object>> getCallBackMap(){
return callBackMap;
}
public boolean checkCallBack(String key){
return callBackMap.containsKey(key);
}
public CallBack\<Object\> removeCallBack(String key){
if(null == key){
return null;
}else{
if(callBackMap.containsKey(key)){
return callBackMap.remove(key);
}else{
return null;
}
}
}
public void setMessageHandle(ChannelInboundHandlerAdapter clientHandler) {
this.clientHandler = clientHandler;
}
public int getTimeout() {
return timeout;
}
}
```
2\. 我们还需要思考一个问题,那就是在我们的系统中,如果启动的 Netty 连接过多是不是把资源都消耗净了,我们是不是需要一个类似于数据库连接池的组件,我们可以把它称为 Netty 连接池,负责建立连接,不至于把资源耗尽。注意单例模式的写法和对象池的构建。
```
/\*\* \* 通过 common pool 的 GenericObjectPool 构建 nettyClient 的对象池,当然也可以叫连接池。 \* 连接池使用单例模式就可以 \*/
public class NettyClientPool extends GenericObjectPool\<NettyClient\> {
private static volatile NettyClientPool nettyClientPool;
private static String configProperties = "com/paul/mq/netty/mq.connectpool.properties";
// 服务端地址 ip 和端口
private static String host;
private static Integer port;
public static NettyClientPool getInstance(){
if(nettyClientPool == null){
synchronized (NettyClientPool.class){
if(nettyClientPool == null){
nettyClientPool = new NettyClientPool();
}
}
}
return nettyClientPool;
}
private NettyClientPool(){
Properties properties = null;
try {
properties = new Properties();
InputStream inputStream = NettyClientPool.class.getClassLoader().getResourceAsStream(configProperties);
properties.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
int maxActive = Integer.parseInt(properties.getProperty("maxActive"));
int minIdle = Integer.parseInt(properties.getProperty("minIdle"));
int maxIdle = Integer.parseInt(properties.getProperty("maxIdle"));
int maxWait = Integer.parseInt(properties.getProperty("maxWait"));
int sessionTimeOut = Integer.parseInt(properties.getProperty("sessionTimeOut"));
System.out.printf("NettyClientPool[maxActive=%d,minIdle=%d,maxIdle=%d,maxWait=%d,sessionTimeOut=%d]\\n", maxActive, minIdle, maxIdle, maxWait, sessionTimeOut);
this.setMaxActive(maxActive);
this.setMaxIdle(maxIdle);
this.setMinIdle(minIdle);
this.setMaxWait(maxWait);
this.setTestOnBorrow(false);
this.setTestOnReturn(false);
this.setTimeBetweenEvictionRunsMillis(10 * 1000);
this.setNumTestsPerEvictionRun(maxActive + maxIdle);
this.setMinEvictableIdleTimeMillis(30 * 60 * 1000);
this.setTestWhileIdle(true);
this.setFactory(new NettyClientPoolableObject(host,port));
}
public NettyClient borrow(){
assert nettyClientPool != null;
try {
return nettyClientPool.borrowObject();
} catch (Exception e) {
System.out.printf("get netty connection throw the error from netty connection pool, error message is %s\\n",
e.getMessage());
}
return null;
}
public void restore(){
assert nettyClientPool != null;
try {
nettyClientPool.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//单例模式,通过 set 方法给 NettyClient 必要的函数 host 和 port 赋值
public static void setServerAddress(String nhost,Integer nport) {
host = nhost;
port = nport;
}
}
```
3\. 有了上面这两个类,我们 Netty 客户端的连接功能就能实现了,下面我们构建一个连接类,这个类通过上面两个类的引用来建立连接,同时这个类还定义了一些向服务端发送消息的方法。生产者和消费者只要继承这个类就可以了,生产者和消费者需要自己实现自己的 handler。
```
/\*\* \* \* 通过管理 NettyClientPool 来获取 netty 连接,并且定义向 broker server 发送消息的方法 \* 生产者,消费者都需要继承这个类 \* \*/
public class NettyConnector {
private NettyClient nettyClient;
private NettyClientPool nettyClientPool;
public NettyConnector(String host,Integer port){
NettyClientPool.setServerAddress(host,port);
this.nettyClientPool = NettyClientPool.getInstance();
this.nettyClient = nettyClientPool.borrow();
}
public NettyClient getNettyClient(){
return nettyClient;
}
public void closeNettyClientPool(){
nettyClientPool.restore();
}
//同步方式发送消息
public void sendSyncMessage(RequestMessage message){
Channel channel = nettyClient.getChannel();
if(null == channel){
return;
}
Map<String,CallBack<Object>> callBackMap = nettyClient.getCallBackMap();
//将当前这个 request 也放到这个 channel 对应的 callbackMap 中
final CallBack<Object> callBack = new CountDownCallBack<Object>();
callBack.setRequestId(message.getMsgId());
callBackMap.put(message.getMsgId(),callBack);
try {
ChannelFuture channelFuture = channel.writeAndFlush(message).sync();
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if(!channelFuture.isSuccess()){
callBack.setReason(channelFuture.cause());
}
}
});
//因为这个方法是 void ,所以不需要 setMessageResult了,只需要在异常的时候 setReason 就可以了。
} catch (InterruptedException e) {
Logger.getLogger(NettyConnector.class.getName()).log(Level.SEVERE, null, e);
}
}
//异步方式发送消息
public Object sendAsyncMessage(RequestMessage message){
Channel channel = nettyClient.getChannel();
if(null == channel){
return null;
}
Map<String,CallBack<Object>> callBackMap = nettyClient.getCallBackMap();
//将当前这个 request 也放到这个 channel 对应的 callbackMap 中
final CallBack<Object> callBack = new CountDownCallBack<Object>();
callBack.setRequestId(message.getMsgId());
callBackMap.put(message.getMsgId(),callBack);
ChannelFuture channelFuture = channel.writeAndFlush(message);
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if(!channelFuture.isSuccess()){
callBack.setReason(channelFuture.cause());
}
}
});
// setMessageResult 函数不在这里设置
// 异步操作,通过加锁的方式获取 messageResult
Object result = callBack.getMessageResult(nettyClient.getTimeout(), TimeUnit.MILLISECONDS);
callBackMap.remove(message.getMsgId());
return result;
}
}
```
4\. 有了上面的连接类,我们的生产者核心类和消费者核心类就能够构建出来了。生产者核心类封装了一个发送消息的 produce 方法。消费者核心类提供了向 Broker Server 注册和取消注册的两个方法,注册方法用于向 Broker Server 表明自己需要监听的队列和模式等等,取消注册则相反。
```
/\*\* \* \* 生产者核心类,因为要与 broker server 建立连接,所以继承 NettyConnector \* \*/
public class PaulMQProducer extends NettyConnector implements MQServer{
//生产者是否连接到了 borker 服务端
private boolean isConnect = false;
//是否正在运行
private boolean isRunning = false;
private String host;
private Integer port;
//producer 实例 的 messageId
private AtomicLong msgId = new AtomicLong(0L);
public PaulMQProducer(String host, Integer port) {
super(host, port);
this.host = host;
this.port = port;
}
public void init() {
//设置 nettyClient 的 handler
super.getNettyClient().setMessageHandle(new ProducerNettyHandler(this));
}
public void start() {
//建立与 broker server 的连接
super.getNettyClient().start();
isConnect = true;
isRunning = true;
}
public void stop() {
if(isRunning){
isRunning = false;
super.getNettyClient().stop();
super.closeNettyClientPool();
}
}
public ProducerAckMessage produce(WorkMode mode,String queue,Exchange exchange,Message message){
if(!isConnect || !isRunning){
ProducerAckMessage ack = new ProducerAckMessage();
ack.setStatus(ProducerAckMessage.FAIL);
return ack;
}
String id = String.valueOf(msgId.incrementAndGet());
message.setQueue(queue);
message.setExchange(exchange);
message.setMode(mode);
message.setTimeStamp(System.currentTimeMillis());
message.setMsgId(id);
//封装到 netty 传输的 request
RequestMessage request = new RequestMessage();
request.setMsgId(id);
request.setMessage(message);
request.setMessageType(MessageType.MESSAGE);
request.setSourceType(SourceType.PRODUCER);
ResponseMessage response = (ResponseMessage)sendAsyncMessage(request);
if (response == null) {
ProducerAckMessage ack = new ProducerAckMessage();
ack.setStatus(ProducerAckMessage.FAIL);
return ack;
}
ProducerAckMessage result = (ProducerAckMessage) response.getMessage();
return result;
}
}
```
消费者核心类,这里需要注意,Consumer 比 Producer 多了一个 ReceiveMessageCallBack 的接口,这个接口从名字就能想象出功能,就是消费者接收到消息后的处理类,这是个接口,需要使用者自己决定接收到消息后如何处理。
```
public class PaulMQConsumer extends NettyConnector implements MQServer{
// broker server 远程地址
private String host;
private Integer port;
private String queue;
private ReceiveMessageCallBack messageCakkBack;;
private String consumerId = "";
private boolean isRunning = false;
private Exchange exchange;
public PaulMQConsumer(String host,Integer port,String queue,Exchange exchange,ReceiveMessageCallBack messageCakkBack) {
//通过父类构建 netty 连接
super(host,port);
this.host = host;
this.port = port;
this.queue = queue;
this.exchange = exchange;
this.messageCakkBack = messageCakkBack;
}
public void init() {
//设置 nettyclient 的 handler
super.getNettyClient().setMessageHandle(new ConsumerNettyHandler(this,messageCakkBack));
Joiner joiner = Joiner.on("@").skipNulls();
consumerId = joiner.join(queue, UUID.randomUUID().toString());
}
public void start() {
super.getNettyClient().start();
register();
isRunning = true;
}
public void stop() {
if(isRunning){
unRegister();
}
}
private void unRegister() {
UnRegisterMessage unsub = new UnRegisterMessage();
unsub.setConsumerId(consumerId);
RequestMessage request = new RequestMessage();
request.setMessageType(MessageType.UNREGISTER);
request.setMsgId(UUID.randomUUID().toString());
request.setMessage(unsub);
sendAsyncMessage(request);
super.getNettyClient().stop();
super.closeNettyClientPool();
isRunning = false;
}
private void register(){
RequestMessage requestMessage = new RequestMessage();
requestMessage.setMsgId(UUID.randomUUID().toString());
requestMessage.setMessageType(MessageType.REGISTER);
requestMessage.setSourceType(SourceType.CONSUMER);
RegisterMessage sub = new RegisterMessage();
sub.setExchange(exchange);
sub.setQueue(queue);
sub.setConsumerId(consumerId);
requestMessage.setMessage(sub);
sendAsyncMessage(requestMessage);
}
}
```
5\. 有了客户端,下面给出 Netty 服务端的逻辑。
```
public class PaulMQBroker extends CoreServer implements MQServer{
private Integer port;
private EventLoopGroup boss;
private EventLoopGroup worker;
private ServerBootstrap bootstrap;
private BrokerNettyHandler handler;
public PaulMQBroker(Integer port){
this.port = port;
}
public void init() {
final KryoCodecUtil util = new KryoCodecUtil(KryoPoolFactory.getKryoPoolInstance());
handler = new BrokerNettyHandler().buildProcessConsumer(new ConsumerMessageListenerImpl()).buildProcessProducer(new ProducerMessageListenerImpl());
boss = new NioEventLoopGroup(1);
worker = new NioEventLoopGroup();
bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
ChannelPipeline pipeline = arg0.pipeline();
pipeline.addLast(new MessageObjectEncoder(util));
pipeline.addLast(new MessageObjectDecoder(util));
pipeline.addLast(handler);
}
});
super.init();
}
public void start() {
System.out.printf("broker server ip:[%s]\\n", "0.0.0.0");
try {
ChannelFuture sync = this.bootstrap.bind(port).sync();
super.start();
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void stop() {
super.stop();
boss.shutdownGracefully();
worker.shutdownGracefully();
}
public static void main(String[] args){
PaulMQBroker server = new PaulMQBroker(8092);
server.init();
server.start();
}
}
```
6\. 写完上面的代码后,Netty 连接和通信只剩下最后一部分代码了,那就 ChannelHandler、生产者、消费者和 Broker Server 都需要这个 handler,这个 handler 用于发送和接收消息,而需要我们实现的就是接收到消息后的逻辑。既然有共同点,那么还是需要抽象出来。这里使用了静态代理,并且传入了子类 handler 的引用,目的是这个类接收到消息后,交给具体的子类(生产者 handler,消费者 handler,Broker Server 的 handler)去处理逻辑。
```
/\*\* \* \* netty 连接的 handler 的模板类,所有 handler 都需要继承这个类,并且实现 before,handle,after 方法来处理业务逻辑 \* \* @param \<T\> \*/
public class NettyHandler\<T\> extends SimpleChannelInboundHandler\<T\> implements MessageProcessor,MessageProxy{
public final static String proxyMappedName = "handle";
//handler 是针对 nettyclient的,所以我们需要 nettyclient 的引用,如果需要拿 nettyclient 的引用,
// 我们就需要 NettyConnector
// 子类需要继承这些属性,所以设置为 protected
protected NettyConnector connector;
protected NettyClient nettyClient;
// 这个引用是想让子类把自己的实例传入,好让 handleMessage 方法做代理
protected NettyHandler<T> subHandler;
//consumer 接收到消息的回调方法,producer 不需要实现
protected ReceiveMessageCallBack callBack;
//netty 传输层面的异常
protected Throwable cause;
//服务端 handler 使用的构造方法,不用传参数
public NettyHandler(){
}
//客户端构造需要传 connector
public NettyHandler(NettyConnector connector){
this(connector, null);
}
public NettyHandler(NettyConnector connector,ReceiveMessageCallBack callBack){
this.connector = connector;
this.nettyClient = connector.getNettyClient();
this.callBack = callBack;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, T msg) throws Exception {
//netty 读到了消息,要将这个消息转给它实现的子类去实现
ProxyFactory proxyFactory = new ProxyFactory(subHandler);
// 根据方法名做切面
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.addMethodName(proxyMappedName);
//静态代理,保证 before handle after 的方法执行顺序
advisor.setAdvice(new NettyHandlerInterceptor(subHandler,msg));
proxyFactory.addAdvisor(advisor);
//执行子类的 handler 方法
MessageProcessor processor = (MessageProcessor)proxyFactory.getProxy();
processor.handle(ctx,msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
this.cause = cause;
}
public void setSubHandler(NettyHandler\<T\> subHandler) {
this.subHandler = subHandler;
}
public Throwable getCause() {
return cause;
}
//下面三个函数是留给子类去实现的
public void handle(ChannelHandlerContext ctx, Object msg) {
}
public void beforeHandle(Object msg) {
}
public void afterHandle(Object msg) {
}
}
```
生产者 handler。
```
public class ProducerNettyHandler extends NettyHandler\<Object\>{
public ProducerNettyHandler(NettyConnector connector) {
super(connector);
//将自己传给 NettyHandler 做代理
super.setSubHandler(this);
}
@Override
public void handle(ChannelHandlerContext ctx, Object msg) {
System.out.println("producer receive message from broker server, in process ........");
// netty 客户端与服务端的通信使用的 RequestMessage 和 ResponseMessage,此处收到的是服务端的返回
String msgId = ((ResponseMessage)msg).getMsgId();
if(!nettyClient.checkCallBack(msgId)){
//如果不存在,说明这个消息已经处理过了
return;
}
//从请求列表中删除,表明这个请求已经处理完毕
CallBack<Object> callBack = nettyClient.removeCallBack(msgId);
if(null == callBack){
return;
}
//生产者收到的是 broker 回复的应答消息, 将返回信息填充到 ballback 返回给生产者
if(null != cause){
callBack.setReason(cause);
}else{
callBack.setMessageResult(msg);
}
}
@Override
public void beforeHandle(Object msg) {
System.out.println("producer receive message from broker server, start process ........");
}
@Override
public void afterHandle(Object msg) {
System.out.println("producer receive message from broker server, end process");
}
}
```
```
//消费者 handler
public class ConsumerNettyHandler extends NettyHandler\<Object\>{
public ConsumerNettyHandler(NettyConnector connector,ReceiveMessageCallBack callBack) {
super(connector,callBack);
super.setSubHandler(this);
}
@Override
public void handle(ChannelHandlerContext ctx, Object msg) {
System.out.println("consumer receive message from broker server, in process ........");
//服务端发送给客户端的叫 response,客户端发送给服务端的叫 request
//此处可能收到两种类型的消息
//1.consumer 给服务端发送订阅消息后,服务端的返回
//2.broker server 将 producer 的 message 传给 consuemr
ResponseMessage response = ((ResponseMessage) msg);
String key = response.getMsgId();
if(!nettyClient.checkCallBack(key) && callBack!=null){
//condition1, callback 里面没有,说明不是 consumer 主动发送给 broker 的消息,所以为 condition 2
ConsumerAckMessage result = null;
if (response.getMessage() instanceof Message) {
callBack.onCallBack((Message)response.getMessage());
result = new ConsumerAckMessage();
result.setAck("SUCCESS");
result.setStatus(200);
result.setMsgId(((Message)response.getMessage()).getMsgId());
}
//消费端收到了 producer 发送的信息,要返回应答给服务端,表明消费端接收消息成功
if(result != null){
RequestMessage request = new RequestMessage();
request.setMsgId(key);
request.setMessage(result);
request.setMessageType(MessageType.MESSAGE);
request.setSourceType(SourceType.CONSUMER);
ctx.writeAndFlush(request);
}
}else{
//condition1
System.out.println("consumer register successful");
}
}
@Override
public void beforeHandle(Object msg) {
System.out.println("consumer receive message from broker server, start process ........");
}
@Override
public void afterHandle(Object msg) {
System.out.println("consumer receive message from broker server, end process");
}
}
```
Borker Server 的 handler。
```
/\* \* 服务端的 nettyhandler 只有一个实例,此处需要注意多线程情况处理 \* \*/
@ChannelHandler.Sharable
public class BrokerNettyHandler extends NettyHandler\<Object\>{
//borker 服务端接收到生产者消费者消息后的处理方法需要做线程安全处理,可以再方法加 synchronized
//此处使用 CAS 获取对象
private AtomicReference<ProducerMessageListener> processProducer;
private AtomicReference<ConsumerMessageListener> processConsumer;
public BrokerNettyHandler() {
super.setSubHandler(this);
}
public BrokerNettyHandler buildProcessProducer(ProducerMessageListener processProducer){
this.processProducer = new AtomicReference<ProducerMessageListener>(processProducer);
return this;
}
public BrokerNettyHandler buildProcessConsumer(ConsumerMessageListener processConsumer){
this.processConsumer = new AtomicReference<ConsumerMessageListener>(processConsumer);
return this;
}
@Override
public void handle(ChannelHandlerContext ctx, Object msg) {
//服务端接收到的 requestMessage
RequestMessage request = (RequestMessage)msg;
ResponseMessage response = new ResponseMessage();
response.setMsgId(request.getMsgId());
response.setSourceType(SourceType.BROKER);
//交给 processor 去处理
BrokerProcessorContext context = new BrokerProcessorContext(request,response,ctx);
context.setProcessConsumer(processConsumer.get());
context.setProcessProducer(processProducer.get());
context.invoke();
}
@Override
public void beforeHandle(Object msg) {
System.out.println("broker server receive message, start process ........");
}
@Override
public void afterHandle(Object msg) {
System.out.println("broker server receive message, start process ........");
}
}
```
7\. 通过上面的代码我们可以实现生产者,消费者与 Broker Server 之间的互相通信了。下面我们来看看 Broker Server 接收到生产者发送的消息后如何处理。生产者发送的消息就使用 ProducerMessageProcessor 来处理,消费者给 BrokerServer 发送的消息分为三种,应答消息使用 ConsumerMessageProcessor 来处理,注册消息使用 RegisterMessageProcessor,取消注册消息使用 UnRegisterMessageProcessor 来处理。
```
public class BrokerProcessorContext {
private RequestMessage request;
private ResponseMessage response;
private ChannelHandlerContext channelHandler;
private ProducerMessageListener processProducer;
private ConsumerMessageListener processConsumer;
private BrokerProcessor processor;
public BrokerProcessorContext(RequestMessage request,ResponseMessage response,ChannelHandlerContext channelHandler){
this.request = request;
this.response = response;
this.channelHandler = channelHandler;
}
public void setProcessProducer(ProducerMessageListener processProducer) {
this.processProducer = processProducer;
}
public void setProcessConsumer(ConsumerMessageListener processConsumer) {
this.processConsumer = processConsumer;
}
public void invoke(){
switch(request.getMessageType()){
case MESSAGE:
//收到 message 类型得消息后对应得 processor
processor = request.getSourceType() == SourceType.PRODUCER ? new ProducerMessageProcessor():new ConsumerMessageProcessor();
break;
case REGISTER:
processor = new RegisterMessageProcessor();
break;
case UNREGISTER:
processor = new UnRegisterMessageProcessor();
break;
default:
break;
}
processor.setChannelHandler(channelHandler);
processor.setHookConsumer(processConsumer);
processor.setHookProducer(processProducer);
processor.messageDispatch(request, response);
}
}
```
8\. Broker Server 接收到生产者到消息后要如何处理那,很简单,直接将消息放入一个线程安全的队列中,并且根据收到消息的数量释放对应数量的信号量。我们会有线程等待这个信号量,然后从这个队列中拿消息然后发送给消费端。这里会根据消息消费模式来决定发送给哪些消费者。
```
/\*\* \* broker server 接收到了生产者发送的消息,后续的处理工作交给这个类 \* @author swang18 \* \*/
public class ProducerMessageListenerImpl implements ProducerMessageListener{
private List<String> consumerSet = new ArrayList<String>();
/\*\* \* 查看是否有对应的消费者 \* @param msg \* @param requestId \* @return \*/
private boolean checkCluster(Message msg,String requestId){
if(consumerSet.size() <= 0){
System.out.println("No active consumer exist for this queue: " + msg.getQueue() + "for exchange: " + msg.getExchange());
//给生产者返回信息
ProducerAckMessage ack = new ProducerAckMessage();
ack.setMsgId(msg.getMsgId());
ack.setAck(requestId);
ack.setStatus(ProducerAckMessage.SUCCESS);
//放入队列中
AckMessageTaskQueue.pushAck(ack);
//释放生产者应答消息信号量
SemaphoreCache.release(SemaphoreConfig.ACKMESSAGE.value);
return false;
}else{
return true;
}
}
private void dispatchMessage(Message msg){
List<MessageDispatchTask> list = new ArrayList<>();
for(int i=0;i<consumerSet.size();i++){
MessageDispatchTask task = new MessageDispatchTask();
task.setConsumerId(consumerSet.get(i));
task.setMessage(msg);
list.add(task);
}
MessageTaskQueue.pushMessages(list);
//放入 task 池中
//释放多个信号量
for(int i=0;i<list.size();i++){
SemaphoreCache.release(SemaphoreConfig.PRODUCERMESSAGE.value);
}
}
private void taskAck(Message msg, String requestId) {
try {
Joiner joiner = Joiner.on("@").skipNulls();
String key = joiner.join(requestId, msg.getMsgId());
AckMessageCache.getInsance().appendMessage(key);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void processProducerMessage(Message msg, String requestId, Channel channel) {
ProducerCache.add(requestId, channel);
WorkMode mode = msg.getMode();
if(mode.equals(WorkMode.WORKER_ROBIN)){
String consumerId = ConsumerContext.nextConsumer(msg.getQueue());
if(null!=consumerId){
consumerSet.add(consumerId);
}
}else if(mode.equals(WorkMode.EXCHANGE_FANOUT)){
consumerSet.addAll(ConsumerContext.getByClusters(msg.getExchange().getName()).getQueuelist());
}else if(mode.equals(WorkMode.EXCHANGE_DIRECT)){
Map<String,String> m = ConsumerContext.getByClusters(msg.getExchange().getName()).getRegrexMap();
for(Entry<String,String> entry:m.entrySet()){
if(entry.getValue().equals(msg.getExchange().getRegrex())){
consumerSet.add(entry.getKey());
}
}
}else if(mode.equals(WorkMode.EXCHANGE_TOPIC)){
Map<String,String> m = ConsumerContext.getByClusters(msg.getExchange().getName()).getRegrexMap();
for(Entry<String,String> entry:m.entrySet()){
String pattern = entry.getValue();
String producer_rule = msg.getExchange().getRegrex();
if(Pattern.matches(pattern, producer_rule)){
consumerSet.add(entry.getKey());
}
}
}else{
System.out.println("wrong mode !!!");
}
if(checkCluster(msg,requestId)){
dispatchMessage(msg);
//taskAck
taskAck(msg,requestId);
consumerSet.clear();
}else{
//没有消费者,直接返回
return;
}
}
}
```
9\. Broker Server 接收到注册消息后,会根据消费者的接收模式来加入到我们的消费者管理模块中。
```
/\*\* \* \* broker 接收到 conumser 的订阅消息的 listener 接口的实现类 \* \*/
public class ConsumerMessageListenerImpl implements ConsumerMessageListener{
public void processConsumerMessage(RegisterMessage msg, ChannelData channel) {
System.out.println("receive sub message from consumer, exchange:" + msg.getExchange() + " queue:" + msg.getQueue()
+ " clientId:" + channel.getClientId());
//
if(msg.getExchange() == null){
//没有 exchange, 所以直接使用 队列名
ConsumerContext.addQueue(msg, channel);
}else{
//将 consumer 加入到消费者集群中
ConsumerContext.addClusters(msg, channel);
}
}
}
```
10\. 我们需要一个类来管理所有消费者,方便 Broker Server 在接收到生产者的消息后选择出要发送的消费者,不同的模式发送的消费者不同。
```
/\* \* 消费者的管理类 \* \*/
public class ConsumerContext {
private static int next = 0;
// 没有 exchange 的 concumer, \<queue, List\<ConcumerId\>\>
private static final Map<String,List<String>> queueList = new ConcurrentHashMap<String,List<String>>();
// 没有 exchange 的 concumer clientId 和 channelData 的对应关系
private final static ConcurrentHashMap<String,ChannelData> queuechannelMap =
new ConcurrentHashMap<String,ChannelData>();
private static final CopyOnWriteArrayList<ClustersRelation> relationList = new CopyOnWriteArrayList<ClustersRelation>();
/\*\* \* 根据 exchangeName 获取 consumerClusters 实例 \* @param exchangeName \* @return \*/
public static ConsumerClusters getByClusters(final String exchangeName){
Predicate predicate = new Predicate(){
public boolean evaluate(Object arg0) {
String exchange = ((ClustersRelation) arg0).getExchangeName();
return exchange.compareTo(exchangeName) == 0;
}
};
Iterator iterator = new FilterIterator(relationList.iterator(), predicate);
ClustersRelation relation = null;
while (iterator.hasNext()) {
relation = (ClustersRelation) iterator.next();
break;
}
return (relation != null) ? relation.getClusters() : null;
}
public static void addClusters(RegisterMessage msg, ChannelData channelData){
ConsumerClusters clu = getByClusters(msg.getExchange().getName());
if(null == clu){
ConsumerClusters clusters = new ConsumerClusters(msg.getExchange().getName());
clusters.addChannelData(channelData.getClientId(), channelData, msg.getExchange());
relationList.add(new ClustersRelation(msg.getExchange().getName(),clusters));
}else{
//channelData 对应的 clientid 已经存在集群中了,更新 channelData 信息
clu.removeChannelData(channelData.getClientId());
clu.addChannelData(channelData.getClientId(), channelData, msg.getExchange());
}
}
public static void removeClusters(String clientId){
for(int i=0;i<relationList.size();i++){
ConsumerClusters clu = relationList.get(i).getClusters();
if(clu.findChannelData(clientId)!=null){
clu.removeChannelData(clientId);
}
//没有活跃的 consuemr 了,再关系表中去掉这个 cluster
if(clu.getChannelMap().size() == 0){
relationList.remove(clu);
}
}
}
public static List\<String\> getQueue(String queueName){
for(Entry entry:queueList.entrySet()){
System.out.println("key:" + entry.getKey());
List<String> l = (List<String>)entry.getValue();
System.out.println("value:" + l.get(0));
}
return queueList.get(queueName);
}
public static ChannelData getChannel(String queueName){
return queuechannelMap.get(queueName);
}
public static void addQueue(RegisterMessage msg, ChannelData channelData){
List<String> l = getQueue(msg.getQueue());
if(null==l){
l = new ArrayList<String>();
}
if(!l.contains(msg.getConsumerId())){
l.add(msg.getConsumerId());
}
queueList.put(msg.getQueue(), l);
queuechannelMap.put(msg.getConsumerId(),channelData);
}
public static void removeQueue(String clientId){
List<String> queueList = getQueue(clientId);
if(null != queueList){
queueList.remove(clientId);
}
queuechannelMap.remove(clientId);
}
//负载均衡,根据连接到broker的顺序,依次投递消息给消费者。这里的均衡算法直接采用
//轮询调度(Round-Robin Scheduling),后续可以加入:加权轮询、随机轮询、哈希轮询等等策略。
public static String nextConsumer(String queueName){
List<String> l = getQueue(queueName);
if(null == l){
return null;
}else{
return l.get(next++%l.size());
}
}
}
```
11\. 上面我们说过,生产者发送的消息会放到一个队列中,通过这个队列来保证消息的顺序性。并且会有线程通过获取信号量的方式不断监听这个队列来发送消息给消费者。
```
/\*\* \* 存放生产者发送消息的队列 \* @author swang18 \* \*/
public class MessageTaskQueue {
//整个 MQ 只有一个核心队列
private static ConcurrentLinkedQueue<MessageDispatchTask> ackQueue = new ConcurrentLinkedQueue<MessageDispatchTask>();
public static boolean pushMessage(MessageDispatchTask message){
return ackQueue.offer(message);
}
public static boolean pushMessages(List\<MessageDispatchTask\> messages){
return ackQueue.addAll(messages);
}
public static MessageDispatchTask getMessage(){
return ackQueue.poll();
}
}
```
监听线程,这个线程只负责将消息从队列中取出,会创建新的线程去发送消息到消费端。这个线程在 Broker Server 启动时会添加到线程池中,循环监听。
```
/\*\* \* 线程循环监听 MessageTaskQueue,查看是否有生产者将消息放入到 Queue 中, \* 如果有通过 SendMessage Queue 将其发送出去 \* @author swang18 \* \*/
public class SendMessageController implements Callable\<Void\>{
private volatile boolean stoped = false;
private AtomicBoolean flushTask = new AtomicBoolean(false);
//多个线程同时操作,使用 Threadlocal 防止出现线程安全问题
private ThreadLocal<ConcurrentLinkedQueue<MessageDispatchTask>> requestCacheList = new ThreadLocal<ConcurrentLinkedQueue<MessageDispatchTask>>(){
protected ConcurrentLinkedQueue\<MessageDispatchTask\> initialValue() {
return new ConcurrentLinkedQueue<MessageDispatchTask>();
}
};
private final Timer timer = new Timer("SendMessageTaskMonitor",true);
public void stop(){
stoped = true;
}
public boolean isStoped(){
return stoped;
}
@Override
public Void call() throws Exception {
//多个任务一起提交
int commitNumber = 1;
// 获取信号量,线程刚启动时 pending 在这
ConcurrentLinkedQueue<MessageDispatchTask> queue = requestCacheList.get();
SendMessageCache sendCache = SendMessageCache.getInstance();
while(!stoped){
//正常情况下,线程启动都会停到这,因为没有发送消息释放这个信号量
SemaphoreCache.acquire(SemaphoreConfig.PRODUCERMESSAGE.value);
MessageDispatchTask task = MessageTaskQueue.getMessage();
queue.add(task);
if(queue.size() == 0){
//目前没有任务,让线程休眠一会再接着执行
Thread.sleep(5000);
continue;
}
//当前任务队列里面有任务,如果答到了 5 个 或者 flushTask 为 true 时可以提交
if(queue.size() > 0 &&(queue.size() % commitNumber == 0 || flushTask.get() == true)){
sendCache.commit(queue);
queue.clear();
flushTask.compareAndSet(true, false);
}
timer.scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
flushTask.compareAndSet(false, true);
}
}, 1000, 3000);
}
return null;
}
}
```
13\. 应答消息的发送与发送消息到消费者是同样的道理。
### 写在最后
我们通过自己造的轮子我们已经了解了 MQ 的结构原理。通过自己编写代码的方式让自己在技术上更加精进。写这篇文章的初衷是看了 RabbitMQ、RocketMQ 等消息队列的原理后总觉得不够,所以就在前人的基础上自己实现了一个 MQ。其实自己写框架的意义在于,不是使用别人的轮子,而是自己造轮子,这样才能在更高的层面上去思考、去进步。 答疑QQ 群:725758660 最后,由于时间仓促,这个 MQ 还有很多不足,我会继续完善,当然更希望大家能够一起提出问题和改进的意见,觉得不错的请在 GitHub 上给个 start。GitHub 地址:
> [PaulMQ](https://github.com/PaulWang92115/PaulMQ)。
我还有其他手写框架的文章以及 Netty 和 JVM 的文章,大家可以按需观看。
---
欢迎关注我的公众号,回复关键字“Java” ,将会有大礼相送!!! 祝各位面试成功!!!