前景回顾

【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

状态回执

上一节我们实现了消息的回执,但是存在一个问题。

同一个消息,可以被不同的 groupName 进行消费,所以回执是需要根据 groupName 进行分开的,这个上一节中遗漏了。

Broker 推送消息的调整

以前推送消息是直接推送,但是缺少 groupName 信息。

订阅列表获取

获取订阅列表的实现调整如下:

public List<ChannelGroupNameDto> getPushSubscribeList(MqMessage mqMessage) {
final String topicName = mqMessage.getTopic();
Set<ConsumerSubscribeBo> set = pushSubscribeMap.get(topicName);
if(CollectionUtil.isEmpty(set)) {
return Collections.emptyList();
} //2. 获取匹配的 tag 列表
final List<String> tagNameList = mqMessage.getTags();
Map<String, List<ConsumerSubscribeBo>> groupMap = new HashMap<>();
for(ConsumerSubscribeBo bo : set) {
String tagRegex = bo.getTagRegex();
if(RegexUtil.hasMatch(tagNameList, tagRegex)) {
String groupName = bo.getGroupName();
MapUtil.putToListMap(groupMap, groupName, bo);
}
} //3. 按照 groupName 分组之后,每一组只随机返回一个。最好应该调整为以 shardingkey 选择
final String shardingKey = mqMessage.getShardingKey();
List<ChannelGroupNameDto> channelGroupNameList = new ArrayList<>();
for(Map.Entry<String, List<ConsumerSubscribeBo>> entry : groupMap.entrySet()) {
List<ConsumerSubscribeBo> list = entry.getValue();
ConsumerSubscribeBo bo = RandomUtils.loadBalance(loadBalance, list, shardingKey);
final String channelId = bo.getChannelId();
BrokerServiceEntryChannel entryChannel = registerMap.get(channelId);
if(entryChannel == null) {
log.warn("channelId: {} 对应的通道信息为空", channelId);
continue;
}
final String groupName = entry.getKey();
ChannelGroupNameDto channelGroupNameDto = ChannelGroupNameDto.of(groupName,
entryChannel.getChannel());
channelGroupNameList.add(channelGroupNameDto);
}
return channelGroupNameList;
}

ChannelGroupNameDto 的定义如下:

public class ChannelGroupNameDto {

    /**
* 分组名称
*/
private String consumerGroupName; /**
* 通道
*/
private Channel channel; //get & set
}

消息主动推送

我们调整一下消息推送,每次推送完成,根据 groupName 进行状态的更新:

for(final ChannelGroupNameDto channelGroupNameDto : channelList) {
final Channel channel = channelGroupNameDto.getChannel();
final String consumerGroupName =channelGroupNameDto.getConsumerGroupName(); try {
// 更新状态为消费处理中
mqBrokerPersist.updateStatus(messageId, consumerGroupName, MessageStatusConst.TO_CONSUMER_PROCESS); String channelId = ChannelUtil.getChannelId(channel);
log.info("开始处理 channelId: {}", channelId);
//1. 调用
mqMessage.setMethodType(MethodType.B_MESSAGE_PUSH);
// 重试推送
MqConsumerResultResp resultResp = Retryer.<MqConsumerResultResp>newInstance()
.maxAttempt(pushMaxAttempt)
.callable(new Callable<MqConsumerResultResp>() {
@Override
public MqConsumerResultResp call() throws Exception {
MqConsumerResultResp resp = callServer(channel, mqMessage,
MqConsumerResultResp.class, invokeService, responseTime);
// 失败校验
if(resp == null
|| !ConsumerStatus.SUCCESS.getCode()
.equals(resp.getConsumerStatus())) {
throw new MqException(BrokerRespCode.MSG_PUSH_FAILED);
}
return resp;
}
}).retryCall(); //2. 更新状态
//2.1 处理成功,取 push 消费状态
if(MqCommonRespCode.SUCCESS.getCode().equals(resultResp.getRespCode())) {
mqBrokerPersist.updateStatus(messageId, consumerGroupName, resultResp.getConsumerStatus());
} else {
// 2.2 处理失败
log.error("消费失败:{}", JSON.toJSON(resultResp));
mqBrokerPersist.updateStatus(messageId, consumerGroupName, MessageStatusConst.TO_CONSUMER_FAILED);
} log.info("完成处理 channelId: {}", channelId);
} catch (Exception exception) {
log.error("处理异常");
mqBrokerPersist.updateStatus(messageId, consumerGroupName, MessageStatusConst.TO_CONSUMER_FAILED);
} }

消息消费者状态回执

ps: 这里 V0.1.1 分支漏写了,不过后面 v0.1.2 分支修正了。

public MqCommonResp consumerStatusAck(String messageId, ConsumerStatus consumerStatus) {
final MqConsumerUpdateStatusReq req = new MqConsumerUpdateStatusReq();
req.setMessageId(messageId);
req.setMessageStatus(consumerStatus.getCode());
final String traceId = IdHelper.uuid32();
req.setTraceId(traceId);
req.setMethodType(MethodType.C_CONSUMER_STATUS); // 添加 groupName
req.setConsumerGroupName(groupName); // 重试
return Retryer.<MqCommonResp>newInstance()
.maxAttempt(consumerStatusMaxAttempt)
.callable(new Callable<MqCommonResp>() {
@Override
public MqCommonResp call() throws Exception {
Channel channel = getChannel(null);
MqCommonResp resp = callServer(channel, req, MqCommonResp.class);
if(!MqCommonRespCode.SUCCESS.getCode().equals(resp.getRespCode())) {
throw new MqException(ConsumerRespCode.CONSUMER_STATUS_ACK_FAILED);
}
return resp;
}
}).retryCall();
}

消息状态回执时, req.setConsumerGroupName(groupName); 添加 groupName 信息。

小结

消息状态的回执精确到 groupName 之后,不同的 groupName 消费就可以相互独立,适用性更强更广。

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次重逢。

开源地址

The message queue in java.(java 简易版本 mq 实现) https://github.com/houbb/mq

拓展阅读

rpc-从零开始实现 rpc https://github.com/houbb/rpc

【mq】从零开始实现 mq-11-消费者消息回执添加分组信息 pull message ack groupName的更多相关文章

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

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  2. xmpp消息回执(6)

    原始地址:XMPPFrameWork IOS 开发(七)消息回执 请参考:XEP-0184协议 协议内容: 发送消息时附加回执请求 <message from='northumberland@s ...

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

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  4. MQ入门总结(一)消息队列概念和使用场景

    一.消息队列 消息即是信息的载体.为了让消息发送者和消息接收者都能够明白消息所承载的信息(消息发送者需要知道如何构造消息:消息接收者需要知道如何解析消息),它们就需要按照一种统一的格式描述消息,这种统 ...

  5. rabbit mq的php使用 php-amqplib操作消息队列

    rabbit mq的php使用 php-amqplib操作消息队列 有大神翻译的教程,非常清楚可以参考: https://xiaoxiami.gitbook.io/rabbitmq_into_chin ...

  6. 四大MQ比较及MQ详解

    消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之 一.当今市面上有很多主流的消息中间件,如老牌的Activ ...

  7. XMPP协议之消息回执解决方案

    苦恼中寻找方法 在开始做即时通信时就知道了消息回执这个概念,目的是解决通讯消息因为各种原因未送达对方而提供的一种保障机制.产生这个问题的原因主要是网络不稳定.服务器或者客户端一些异常导致没有接收到消息 ...

  8. Linux下安装mysql5.6.11(找点有用的信息太费劲)(转)

    Linux下安装mysql5.6.11(找点有用的信息太费劲) (2013-04-25 10:25:09)     1.申请阿里云Linux服务器 昨天在阿里云申请了一个免费试用5天的Linux云服务 ...

  9. Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token

    原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf?   因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...

随机推荐

  1. Citus 分布式 PostgreSQL 集群 - SQL Reference(手动查询传播)

    手动查询传播 当用户发出查询时,Citus coordinator 将其划分为更小的查询片段,其中每个查询片段可以在工作分片上独立运行.这允许 Citus 将每个查询分布在集群中. 但是,将查询划分为 ...

  2. Pycharm使用 Ctrl+滚轮 调整字体大小

    首先,打开File中的Settings 然后,点开Editor内的General 最后,在3 指向的位置勾选:Change font size (Zoom)with Ctrl+Mouse Whel 这 ...

  3. Python函数-导入模块的顺序及原理

    引入 当python导入模块,执行import语句时,到底进行了什么操作?按照python的文档,她执行了如下的操作: 第一步,创建一个新的module对象(它可能包含多个module) 第二步,把这 ...

  4. 7_根轨迹_Part1_“根”的作用

    这里的渐近线,应该是e^[**wn]/wd,忘记除wd了

  5. Exception Handling Considered Harmful

    异常处理被认为存在缺陷 Do, or do not. There is no try. - Yoda, The Empire Strikes Back (George Lucas) by Jason ...

  6. Java中if else条件判断语句的执行顺序

    学习目标: 掌握 if else 条件判断的使用 学习内容: 1.if语法 if(boolean表达式) { 语句体; } if后面的{}表示一个整体-代码块,称之为语句体,当boolean表达式为t ...

  7. 截取url传值

    // 页面传值 subStr(url) { var obj = {}; var str = url.split('?')[1]; var str2 = str.split('&'); cons ...

  8. Spring Boot配置文件加载顺序

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.通过spring.config.location改变配置文件的位置 二.外部配置加载顺序 1.使用命令行参数指定加 ...

  9. Hyperledger Fabric无排序组织以Raft共识算法启动多个Orderer服务、多组织共同运行维护Orderer服务

    前言 在Hyperledger Fabric无系统通道启动及通道的创建和删除中,我们已经完成了以无系统通道的方式启动 Hyperledger Fabric 网络,并将链码安装到指定通道.但目前为止,实 ...

  10. css 实现随风摆动

    无标题文档 @-webkit-keyframes open { 0% { -webkit-transform: rotateX(-120deg); } 25% { -webkit-transform: ...