【mq】从零开始实现 mq-12-消息的批量发送与回执
前景回顾
【mq】从零开始实现 mq-02-如何实现生产者调用消费者?
【mq】从零开始实现 mq-03-引入 broker 中间人
【mq】从零开始实现 mq-06-消费者心跳检测 heartbeat
【mq】从零开始实现 mq-07-负载均衡 load balance
【mq】从零开始实现 mq-09-消费者拉取消息 pull message
【mq】从零开始实现 mq-10-消费者拉取消息回执 pull message ack
【mq】从零开始实现 mq-11-消费者消息回执添加分组信息 pull message ack groupName
批量消息
对于消息的发送,有时候可能需要一次发送多个,比如日志消息等。
批量操作可以提升性能。
本节老马就和大家一起添加一点批量特性。

消息的批量发送
生产者实现
接口定义
/**
* 同步发送消息-批量
* @param mqMessageList 消息类型
* @return 结果
* @since 0.1.3
*/
SendBatchResult sendBatch(final List<MqMessage> mqMessageList);
/**
* 单向发送消息-批量
* @param mqMessageList 消息类型
* @return 结果
* @since 0.1.3
*/
SendBatchResult sendOneWayBatch(final List<MqMessage> mqMessageList);
一次支持发送多个消息。
接口实现
生产者实现如下。
@Override
public SendBatchResult sendBatch(List<MqMessage> mqMessageList) {
final List<String> messageIdList = this.fillMessageList(mqMessageList);
final MqMessageBatchReq batchReq = new MqMessageBatchReq();
batchReq.setMqMessageList(mqMessageList);
String traceId = IdHelper.uuid32();
batchReq.setTraceId(traceId);
batchReq.setMethodType(MethodType.P_SEND_MSG_BATCH);
return Retryer.<SendBatchResult>newInstance()
.maxAttempt(maxAttempt)
.callable(new Callable<SendBatchResult>() {
@Override
public SendBatchResult call() throws Exception {
return doSendBatch(messageIdList, batchReq, false);
}
}).retryCall();
}
@Override
public SendBatchResult sendOneWayBatch(List<MqMessage> mqMessageList) {
List<String> messageIdList = this.fillMessageList(mqMessageList);
MqMessageBatchReq batchReq = new MqMessageBatchReq();
batchReq.setMqMessageList(mqMessageList);
String traceId = IdHelper.uuid32();
batchReq.setTraceId(traceId);
batchReq.setMethodType(MethodType.P_SEND_MSG_ONE_WAY_BATCH);
return doSendBatch(messageIdList, batchReq, true);
}
private SendBatchResult doSendBatch(List<String> messageIdList,
MqMessageBatchReq batchReq,
boolean oneWay) {
log.info("[Producer] 批量发送消息 messageIdList: {}, batchReq: {}, oneWay: {}",
messageIdList, JSON.toJSON(batchReq), oneWay);
// 以第一个 sharding-key 为准。
// 后续的会被忽略
MqMessage mqMessage = batchReq.getMqMessageList().get(0);
Channel channel = getChannel(mqMessage.getShardingKey());
//one-way
if(oneWay) {
log.warn("[Producer] ONE-WAY send, ignore result");
return SendBatchResult.of(messageIdList, SendStatus.SUCCESS);
}
MqCommonResp resp = callServer(channel, batchReq, MqCommonResp.class);
if(MqCommonRespCode.SUCCESS.getCode().equals(resp.getRespCode())) {
return SendBatchResult.of(messageIdList, SendStatus.SUCCESS);
}
throw new MqException(ProducerRespCode.MSG_SEND_FAILED);
}
ps: 这里和单个发送有一个区别,那就是对于 channel 的选择。因为只能选择一个,所以不能兼顾每一个消息的 sharding-key。
Broker 的处理
消息分发
// 生产者消息发送-批量
if(MethodType.P_SEND_MSG_BATCH.equals(methodType)) {
return handleProducerSendMsgBatch(channelId, json);
}
// 生产者消息发送-ONE WAY-批量
if(MethodType.P_SEND_MSG_ONE_WAY_BATCH.equals(methodType)) {
handleProducerSendMsgBatch(channelId, json);
return null;
}
具体实现
/**
* 处理生产者发送的消息
*
* @param channelId 通道标识
* @param json 消息体
* @since 0.1.3
*/
private MqCommonResp handleProducerSendMsgBatch(String channelId, String json) {
MqMessageBatchReq batchReq = JSON.parseObject(json, MqMessageBatchReq.class);
final ServiceEntry serviceEntry = registerProducerService.getServiceEntry(channelId);
List<MqMessagePersistPut> putList = buildPersistPutList(batchReq, serviceEntry);
MqCommonResp commonResp = mqBrokerPersist.putBatch(putList);
// 遍历异步推送
for(MqMessagePersistPut persistPut : putList) {
this.asyncHandleMessage(persistPut);
}
return commonResp;
}
这里对消息列表进行持久化保存。
演示的持久化策略如下:
@Override
public MqCommonResp putBatch(List<MqMessagePersistPut> putList) {
// 构建列表
for(MqMessagePersistPut put : putList) {
this.doPut(put);
}
MqCommonResp commonResp = new MqCommonResp();
commonResp.setRespCode(MqCommonRespCode.SUCCESS.getCode());
commonResp.setRespMessage(MqCommonRespCode.SUCCESS.getMsg());
return commonResp;
}
消息的批量ACK
说明
以前的实现方式是每一个消息消费完成之后,进行一次 ACK。
对于 pull 策略的消息消费,我们可以等当前批次结束,统一进行 ACK 回执。
消费实现
实现调整如下:
for(MqTopicTagDto tagDto : subscribeList) {
final String topicName = tagDto.getTopicName();
final String tagRegex = tagDto.getTagRegex();
MqConsumerPullResp resp = consumerBrokerService.pull(topicName, tagRegex, size);
if(MqCommonRespCode.SUCCESS.getCode().equals(resp.getRespCode())) {
List<MqMessage> mqMessageList = resp.getList();
if(CollectionUtil.isNotEmpty(mqMessageList)) {
List<MqConsumerUpdateStatusDto> statusDtoList = new ArrayList<>(mqMessageList.size());
for(MqMessage mqMessage : mqMessageList) {
IMqConsumerListenerContext context = new MqConsumerListenerContext();
final String messageId = mqMessage.getTraceId();
ConsumerStatus consumerStatus = mqListenerService.consumer(mqMessage, context);
log.info("消息:{} 消费结果 {}", messageId, consumerStatus);
// 状态同步更新
if(!ackBatchFlag) {
MqCommonResp ackResp = consumerBrokerService.consumerStatusAck(messageId, consumerStatus);
log.info("消息:{} 状态回执结果 {}", messageId, JSON.toJSON(ackResp));
} else {
// 批量
MqConsumerUpdateStatusDto statusDto = new MqConsumerUpdateStatusDto();
statusDto.setMessageId(messageId);
statusDto.setMessageStatus(consumerStatus.getCode());
statusDto.setConsumerGroupName(groupName);
statusDtoList.add(statusDto);
}
}
// 批量执行
if(ackBatchFlag) {
MqCommonResp ackResp = consumerBrokerService.consumerStatusAckBatch(statusDtoList);
log.info("消息:{} 状态批量回执结果 {}", statusDtoList, JSON.toJSON(ackResp));
statusDtoList = null;
}
}
} else {
log.error("拉取消息失败: {}", JSON.toJSON(resp));
}
}
如果 ackBatchFlag = false,则处理逻辑和以前一样。
如果 ackBatchFlag = true,则首先把消息放到 list 中,结束后统一执行。
broker 实现
消息分发
//消费者消费状态 ACK-批量
if(MethodType.C_CONSUMER_STATUS_BATCH.equals(methodType)) {
MqConsumerUpdateStatusBatchReq req = JSON.parseObject(json, MqConsumerUpdateStatusBatchReq.class);
final List<MqConsumerUpdateStatusDto> statusDtoList = req.getStatusList();
return mqBrokerPersist.updateStatusBatch(statusDtoList);
}
实现
默认的持久化实现,更新如下:
@Override
public MqCommonResp updateStatusBatch(List<MqConsumerUpdateStatusDto> statusDtoList) {
for(MqConsumerUpdateStatusDto statusDto : statusDtoList) {
this.doUpdateStatus(statusDto.getMessageId(), statusDto.getConsumerGroupName(),
statusDto.getMessageStatus());
}
MqCommonResp commonResp = new MqCommonResp();
commonResp.setRespCode(MqCommonRespCode.SUCCESS.getCode());
commonResp.setRespMessage(MqCommonRespCode.SUCCESS.getMsg());
return commonResp;
}
遍历每一个元素,进行状态的更新。
小结
异步和批量,是提升性能最常用的 2 种方式。
批量的实现相关来说是最简单,也是效果最显著的。
希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。
我是老马,期待与你的下次重逢。
开源地址
The message queue in java.(java 简易版本 mq 实现) https://github.com/houbb/mq
拓展阅读
rpc-从零开始实现 rpc https://github.com/houbb/rpc
【mq】从零开始实现 mq-12-消息的批量发送与回执的更多相关文章
- mq网络请求命令设计&消息的批量发送
RemotingCommand: flag倒数第一位表示请求类型,0请求1返回.倒数第二位1.表示oneway 单条消息发送时,消息体的内容将保存在body种,批量消息发送,需要将多条消息体的内容存储 ...
- RocketMQ系列(六)批量发送与过滤
今天我们再来看看RocketMQ的另外两个小功能,消息的批量发送和过滤.这两个小功能提升了我们使用RocketMQ的效率. 批量发送 以前我们发送消息的时候,都是一个一个的发送,这样效率比较低下.能不 ...
- MQ系列5:RocketMQ消息的发送模式
MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 在之前的篇章中,我们学习了RocketMQ的原理, ...
- java框架之SpringBoot(12)-消息及整合RabbitMQ
前言 概述 大多数应用中,可通过消息服务中间件来提升系统异步通信.扩展解耦的能力. 消息服务中两个重要概念:消息代理(message broker)和目的地(destination).当消息发送者发送 ...
- 【定时功能】消息的定时发送-基于RocketMQ
一.功能介绍 要实现一个消息的定时发送功能,也就是让消息可以在某一天某一个时间具体节点进行发送.而我们公司的业务场景是类似短信的业务,而且数量不小,用户会进行号码.消息内容.定时发送时间等信息的提交. ...
- JAVA实现多线程处理批量发送短信、APP推送
/** * 推送消息 APP.短信 * @param message * @throws Exception */ public void sendMsg(Message message) throw ...
- RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路
概述 在上一节 RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息 中,我们了解了 Producer 在发送消息的流程.这次我们再来具体下看消息的构成与其 ...
- 个人永久性免费-Excel催化剂功能第85波-灵活便捷的批量发送短信功能(使用腾讯云接口)
微信时代的今天,短信一样不可缺席,大系统都有集成短信接口.若只是临时用一下,若能够直接在Excel上加工好内容就可以直接发送,这些假设在此篇批量群发短信功能中都为大家带来完美答案. 业务场景 不多说, ...
- 如何利用.NETCore向Azure EventHubs准实时批量发送数据?
最近在做一个基于Azure云的物联网分析项目: .netcore采集程序向Azure事件中心(EventHubs)发送数据,通过Azure EventHubs Capture转储到Azure Blog ...
随机推荐
- H.265
Baseline支持I/P 帧,只支持无交错(Progressive)和CAVLC一般用于低阶或需要额外容错的应用,比如视频通话.手机视频等: Main支持I/P/B 帧,无交错(Progressiv ...
- JavaScript 小技巧 数组去重
const array = [1, 2, 3, 3, 5, 5, 1]; const uniqueArray = [...new Set(array)]; console.log(uniqueArra ...
- java中哪块代码或说什么代码应该放在try块中呢?
我怎么知道哪块代码可能出现问题,从而放在try块儿中呢?马 克-to-win:一个笨办法,开始时,你并不加try,但你发现,运行时,用户赋给除数一个0,所以程序在这崩溃了,于是你就把这块代码加个try ...
- 【Android开发】控件外边框自定义
1.在drawable里面新建自定义的资源文件shape <?xml version="1.0" encoding="utf-8"?> <sh ...
- 《头号玩家》AI电影调研报告(四)
五. 现实中对于技术的实现 1.自由变身 电影中,主人公借助"魔幻眼镜"让自己快速变成另一个人的模样,现实中我们一样也能做到! 在今年游戏开发者大会(GDC)上,一个名为" ...
- Make-learning
Make学习笔记 make是工具,Makefile是指导make工作的文件,而CMake则是生成Makefile的工具 要点: 终极目标是Makefile里面的第一个规则目标 目标下面的命令必须接的是 ...
- python入门-开始
1.为啥要学Python? 各种语言的优劣势对比视频版:https://www.bilibili.com/video/BV1y3411r7pX/?spm_id_from=autoNext 各种语言的优 ...
- MySQL---drop, delete, truncate的区别
drop, delete, truncate的区别 删除内容 drop直接删除整个表, 包含表结构和数据; truncate删除表中数据, 表结构及其列, 约束, 索引等不变, 再插入时自增id又从1 ...
- 【Azure 云服务】Azure Cloud Service (Extended Support) 云服务开启诊断日志插件 WAD Extension (Windows Azure Diagnostic) 无法正常工作的原因
问题描述 在Azure中国区上面创建一个云服务(外延支持)后,根据官方文档(在云服务(外延支持)中应用 Azure 诊断扩展: https://docs.azure.cn/zh-cn/cloud-se ...
- 十、包机制与JavaDoc
一.包机制 为了更好的组织类,Java提供了包机制,用于区别类名的命名空间. 包语句的语句格式为: package pkg1[. pkg2[. pkg3...]]; 一般使用公司域名倒置作为包名:例如 ...