前景回顾

【mq】从零开始实现 mq-01-生产者、消费者启动

【mq】从零开始实现 mq-02-如何实现生产者调用消费者?

【mq】从零开始实现 mq-03-引入 broker 中间人

【mq】从零开始实现 mq-04-启动检测与实现优化

【mq】从零开始实现 mq-05-实现优雅停机

【mq】从零开始实现 mq-06-消费者心跳检测 heartbeat

【mq】从零开始实现 mq-07-负载均衡 load balance

【mq】从零开始实现 mq-08-配置优化 fluent

【mq】从零开始实现 mq-09-消费者拉取消息 pull message

【mq】从零开始实现 mq-10-消费者拉取消息回执 pull message ack

【mq】从零开始实现 mq-11-消费者消息回执添加分组信息 pull message ack groupName

【mq】从零开始实现 mq-12-消息的批量发送与回执

批量消息

对于消息的发送,有时候可能需要一次发送多个,比如日志消息等。

批量操作可以提升性能。

本节老马就和大家一起添加一点批量特性。

消息的批量发送

生产者实现

接口定义

/**
* 同步发送消息-批量
* @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-消息的批量发送与回执的更多相关文章

  1. mq网络请求命令设计&消息的批量发送

    RemotingCommand: flag倒数第一位表示请求类型,0请求1返回.倒数第二位1.表示oneway 单条消息发送时,消息体的内容将保存在body种,批量消息发送,需要将多条消息体的内容存储 ...

  2. RocketMQ系列(六)批量发送与过滤

    今天我们再来看看RocketMQ的另外两个小功能,消息的批量发送和过滤.这两个小功能提升了我们使用RocketMQ的效率. 批量发送 以前我们发送消息的时候,都是一个一个的发送,这样效率比较低下.能不 ...

  3. MQ系列5:RocketMQ消息的发送模式

    MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 在之前的篇章中,我们学习了RocketMQ的原理, ...

  4. java框架之SpringBoot(12)-消息及整合RabbitMQ

    前言 概述 大多数应用中,可通过消息服务中间件来提升系统异步通信.扩展解耦的能力. 消息服务中两个重要概念:消息代理(message broker)和目的地(destination).当消息发送者发送 ...

  5. 【定时功能】消息的定时发送-基于RocketMQ

    一.功能介绍 要实现一个消息的定时发送功能,也就是让消息可以在某一天某一个时间具体节点进行发送.而我们公司的业务场景是类似短信的业务,而且数量不小,用户会进行号码.消息内容.定时发送时间等信息的提交. ...

  6. JAVA实现多线程处理批量发送短信、APP推送

    /** * 推送消息 APP.短信 * @param message * @throws Exception */ public void sendMsg(Message message) throw ...

  7. RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息 中,我们了解了 Producer 在发送消息的流程.这次我们再来具体下看消息的构成与其 ...

  8. 个人永久性免费-Excel催化剂功能第85波-灵活便捷的批量发送短信功能(使用腾讯云接口)

    微信时代的今天,短信一样不可缺席,大系统都有集成短信接口.若只是临时用一下,若能够直接在Excel上加工好内容就可以直接发送,这些假设在此篇批量群发短信功能中都为大家带来完美答案. 业务场景 不多说, ...

  9. 如何利用.NETCore向Azure EventHubs准实时批量发送数据?

    最近在做一个基于Azure云的物联网分析项目: .netcore采集程序向Azure事件中心(EventHubs)发送数据,通过Azure EventHubs Capture转储到Azure Blog ...

随机推荐

  1. Web存储之LocalStorage初探

    Web存储之LocalStorage初探 HTML5的发布和定稿为前端界带来巨大的变化,新增的API和特性给业务带来了更多可能性,让用户体验拥有了更可能的丰富. · HTML Geolocation ...

  2. 微信小程序HTTP接口请求封装

    1.方法封装(新建文件夹util,工具文件,在文件夹下创建request.js文件,用于对方法封装)request.js: var app = getApp(); //项目URL相同部分,减轻代码量, ...

  3. vue引入echarts

    效果图: 1.安装Echarts :     npm install echarts -S 或者使用国内的淘宝镜像: 安装: npm install -g cnpm --registry=https: ...

  4. 【转】shim.ChaincodeStubInterface用法

    作为记录 shim.ChaincodeStubInterface用法

  5. Struts2-day1总结

    1.Struts2的基本执行流程(详见我的博客) 2.Struts2的分模块开发 如果有多个项目的Struts.xml写在一起,容易造成数据混乱,所以可以使用分模块的方法,在项目下新建*.xml配置文 ...

  6. Spring-Bean的依赖注入分析-01

    ###我们先通过一个例子弄明白为什么要使用依赖注入### 1.创建业务层UserService接口及UserServiceImpl实现类(接口代码省略) public class UserServic ...

  7. 时间篇之centos7修复ntpq: read: Connection refused

    关于ntp同步时间, 由于是解决问题,所以理论性内容不多. 关于UTC NTP要提供准确的时间,就必须有准确的时间来源,那可以用格林尼治时间吗?答案是否定的. 因为格林尼治时间是以地球自转为基础的时间 ...

  8. Mycat管理

    Mycat 提供了类似数据库的管理监控方式,可以通过 mycat 端口 9066 执行相应的 SQL 语句进行管理,可以可以通过 navicat 软件进行远程连接管理,再使用 MySQL 命令行界面登 ...

  9. 【课程汇总】OpenHarmony 成长计划知识赋能第三期系列课程(附链接)

    OpenHarmony 开源开发者成长计划第三期知识赋能课程硬核程度再次升级,带领开发者上手 OpenHarmony 的标准系统应用开发,学习标准设备应用开发,使用 eTS UI 开发分布式应用样例, ...

  10. 字符串/16进制/ASCII码的转换

    1 /// <字符串转16进制格式,不够自动前面补零> 2 /// 假设文本框里面填写的是:01 02 03 04 05 06 3 /// Str获取的是01 02 03 04 05 06 ...