Consumer

consumer pull message

订阅

在Consumer启动之前先将自己放到一个本地的集合中,再以后获取消费者的时候会用到,同时会将自己订阅的信息告诉broker

接收消息

consumer启动的时候会启动两个service:

RebalanceService:主要实现consumer的负载均衡,但是并不会直接发送获取消息的请求,而是构造request之后放到PullMessageService中,等待PullMessageService的线程取出执行

PullMessageService:主要负责从broker获取message,包含一个需要获取消息的请求队列(是阻塞的),并不断依次从队列中取出请求向broker send Request

执行时序图太大,截屏截不全,所以放在git上,在这里:RocketMQ.asta

从Broker pullMessage

在PullMessageService中通过netty发送pull消息的请求之后,Broker的remoteServer会收到request,然后在PullMessageProcessor中的processRequest处理,先会解析requestHeader,request中带了读取MessageStore的参数:

  • consumerGroup
  • topic
  • queueId
  • queueOffset
  • MaxMsgNums
  • subscriptionData(ConsumerManager中获取)

processRequest处理流程

  • 判断Broker当前是否允许接收消息
  • 找到subscriptionGroupConfig,subscriptionGroupTable,如果不存在当前的group则新增一个
  • 是否subscriptionGroupConfig.consumeEnable
  • 获取从TopicConfigManager.topicConfigTable获取topicConfig
  • topicConfig是否有读权限
  • 校验queueId是否在范围内
  • ConsumerManager.getConsumerGroupInfo获取ConsumerGroupInfo
  • consumerGroupInfo.findSubscriptionData查找subscriptionData
  • MessageStore.getMessage读取消息,从文件中读取消息,属于pullMessage的核心方法
  • 判断brokerRole,master和slave工作模式的哪一种
  • 判断MessageResult.status
  • 如果responseCode是SUSCESS,判断是使用heap还是非heap方式传输数据
  • 使用netty序列化response返回netty客户端

负载均衡

  1. // AllocateMessageQueueAveragely
  2. public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
  3. List<String> cidAll) {
  4. if (currentCID == null || currentCID.length() < 1) {
  5. throw new IllegalArgumentException("currentCID is empty");
  6. }
  7. if (mqAll == null || mqAll.isEmpty()) {
  8. throw new IllegalArgumentException("mqAll is null or mqAll empty");
  9. }
  10. if (cidAll == null || cidAll.isEmpty()) {
  11. throw new IllegalArgumentException("cidAll is null or cidAll empty");
  12. }
  13. List<MessageQueue> result = new ArrayList<MessageQueue>();
  14. if (!cidAll.contains(currentCID)) {
  15. log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}", //
  16. consumerGroup, //
  17. currentCID,//
  18. cidAll);
  19. return result;
  20. }
  21. int index = cidAll.indexOf(currentCID);
  22. int mod = mqAll.size() % cidAll.size();
  23. int averageSize =
  24. mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
  25. + 1 : mqAll.size() / cidAll.size());
  26. int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
  27. int range = Math.min(averageSize, mqAll.size() - startIndex);
  28. for (int i = 0; i < range; i++) {
  29. result.add(mqAll.get((startIndex + i) % mqAll.size()));
  30. }
  31. return result;
  32. }
  33. // RebalanceImpl
  34. private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet, final boolean isOrder) {
  35. boolean changed = false;
  36. // 去掉topic对应的无用MessageQueue(不包含在processQueueTable或者pullExpired)
  37. Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
  38. while (it.hasNext()) {
  39. Entry<MessageQueue, ProcessQueue> next = it.next();
  40. MessageQueue mq = next.getKey();
  41. ProcessQueue pq = next.getValue();
  42. if (mq.getTopic().equals(topic)) {
  43. if (!mqSet.contains(mq)) {
  44. pq.setDropped(true);
  45. if (this.removeUnnecessaryMessageQueue(mq, pq)) {
  46. it.remove();
  47. changed = true;
  48. log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
  49. }
  50. } else if (pq.isPullExpired()) {
  51. switch (this.consumeType()) {
  52. case CONSUME_ACTIVELY:
  53. break;
  54. case CONSUME_PASSIVELY:
  55. pq.setDropped(true);
  56. if (this.removeUnnecessaryMessageQueue(mq, pq)) {
  57. it.remove();
  58. changed = true;
  59. log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it",
  60. consumerGroup, mq);
  61. }
  62. break;
  63. default:
  64. break;
  65. }
  66. }
  67. }
  68. }
  69. List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
  70. for (MessageQueue mq : mqSet) {
  71. // 如果MessageQueue不在processQueueTable
  72. if (!this.processQueueTable.containsKey(mq)) {
  73. if (isOrder && !this.lock(mq)) {
  74. log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
  75. continue;
  76. }
  77. this.removeDirtyOffset(mq);
  78. ProcessQueue pq = new ProcessQueue();
  79. long nextOffset = this.computePullFromWhere(mq);
  80. // 如果message还有数据需要读
  81. if (nextOffset >= 0) {
  82. ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
  83. if (pre != null) {
  84. log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
  85. } else {
  86. log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
  87. PullRequest pullRequest = new PullRequest();
  88. pullRequest.setConsumerGroup(consumerGroup);
  89. pullRequest.setNextOffset(nextOffset);
  90. pullRequest.setMessageQueue(mq);
  91. pullRequest.setProcessQueue(pq);
  92. pullRequestList.add(pullRequest);
  93. changed = true;
  94. }
  95. } else {
  96. log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
  97. }
  98. }
  99. }
  100. // 将pullRequest放在pullRequestQueue中等待去取数据
  101. this.dispatchPullRequest(pullRequestList);
  102. return changed;
  103. }

issue

多个consumer读取同一个consumerQueue的时候怎么记录每个读取的进度

每个consumer只要记住自己读取哪一个队列,以及offset就可以了

pull消息过程中的关键类

DefaultMQPushConsumerImpl

供DefaultMQPushConsumer调用,作为consumer的默认实现:

AllocateMessageQueueAveragely

这个类主要是consumer消费的负载均衡算法

  1. public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
  2. List<String> cidAll) {
  3. if (currentCID == null || currentCID.length() < 1) {
  4. throw new IllegalArgumentException("currentCID is empty");
  5. }
  6. if (mqAll == null || mqAll.isEmpty()) {
  7. throw new IllegalArgumentException("mqAll is null or mqAll empty");
  8. }
  9. if (cidAll == null || cidAll.isEmpty()) {
  10. throw new IllegalArgumentException("cidAll is null or cidAll empty");
  11. }
  12. List<MessageQueue> result = new ArrayList<MessageQueue>();
  13. if (!cidAll.contains(currentCID)) {
  14. log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}", //
  15. consumerGroup, //
  16. currentCID,//
  17. cidAll);
  18. return result;
  19. }
  20. // 基本原则,每个队列只能被一个consumer消费
  21. // 当messageQueue个数小于等于consume的时候,排在前面(在list中的顺序)的consumer消费一个queue,index大于messageQueue之后的consumer消费不到queue,也就是为0
  22. // 当messageQueue个数大于consumer的时候,分两种情况
  23. // 当有余数(mod > 0)并且index < mod的时候,当前comsumer可以消费的队列个数是 mqAll.size() / cidAll.size() + 1
  24. // 可以整除或者index 大于余数的时候,队列数为:mqAll.size() / cidAll.size()
  25. int index = cidAll.indexOf(currentCID);
  26. int mod = mqAll.size() % cidAll.size();
  27. int averageSize =
  28. mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
  29. + 1 : mqAll.size() / cidAll.size());
  30. int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
  31. int range = Math.min(averageSize, mqAll.size() - startIndex);
  32. for (int i = 0; i < range; i++) {
  33. result.add(mqAll.get((startIndex + i) % mqAll.size()));
  34. }
  35. return result;
  36. }

RocketMQ源码 — 三、 Consumer 接收消息过程的更多相关文章

  1. RocketMQ源码 — 三、 Producer消息发送过程

    Producer 消息发送 producer start producer启动过程如下图 public void start(final boolean startFactory) throws MQ ...

  2. RocketMQ源码 — 四、 Consumer 接收消息过程

    Consumer consumer pull message 订阅 在Consumer启动之前先将自己放到一个本地的集合中,再以后获取消费者的时候会用到,同时会将自己订阅的信息告诉broker 接收消 ...

  3. rocketmq源码分析2-broker的消息接收

    broker消息接收,假设接收的是一个普通消息(即没有事务),此处分析也只分析master上动作逻辑,不涉及ha. 1. 如何找到消息接收处理入口 可以通过broker的监听端口10911顺藤摸瓜式的 ...

  4. rocketmq源码分析4-事务消息实现原理

    为什么消息要具备事务能力 参见还是比较清晰的.简单的说 就是在你业务逻辑过程中,需要发送一条消息给订阅消息的人,但是期望是 此逻辑过程完全成功完成之后才能使订阅者收到消息.业务逻辑过程 假设是这样的: ...

  5. RocketMQ源码 — 六、 RocketMQ高可用(1)

    高可用究竟指的是什么?请参考:关于高可用的系统 RocketMQ做了以下的事情来保证系统的高可用 多master部署,防止单点故障 消息冗余(主从结构),防止消息丢失 故障恢复(本篇暂不讨论) 那么问 ...

  6. RocketMQ源码详解 | Consumer篇 · 其一:消息的 Pull 和 Push

    概述 当消息被存储后,消费者就会将其消费. 这句话简要的概述了一条消息的最总去向,也引出了本文将讨论的问题: 消息什么时候才对被消费者可见? 是在 page cache 中吗?还是在落盘后?还是像 K ...

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

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

  8. RocketMQ 源码分析 —— Message 发送与接收

    1.概述 Producer 发送消息.主要是同步发送消息源码,涉及到 异步/Oneway发送消息,事务消息会跳过. Broker 接收消息.(存储消息在<RocketMQ 源码分析 —— Mes ...

  9. RocketMQ源码详解 | Broker篇 · 其一:线程模型与接收链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其二:消息组成.发送链路 中,我们终于将消息发送出了 Producer,在短暂的 tcp 握手后,很快它就会进入目的 Broker ...

随机推荐

  1. input输入框和 pure框架中的 box-sizing 值问题

    在使用pureCSS框架的时候,遇到一个问题. input输入框,我给他们设置了宽度和padding值,我发现,在火狐和谷歌上面发现,增加padding值并不会影响最终的宽度,而在IE6 7下则会影响 ...

  2. extJS4.2.0 tabPanel学习(三)

    了解添加tab的函数 这里设置为自动添加,菜单是从后台获取的数据,前台进行双击的时候,添加tab页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...

  3. css3选择器总结--强大如jquery

    最近发现,阿里的笔试考了许多css3的知识,像query media.box-flex等等.主要是移动浏览器的开发,让html5和css3如虎添翼,再也不用担心兼容了.so总结一下css3的选择器: ...

  4. clip实现圆环进度条

    效果主要通过clip和transform:rotate实现 把圆环分为左右两个部分,通过角度旋转对图片剪切旋转角度<=180度的时候之旋转右边,当大于180度时右边固定旋转180度的同时旋转左边 ...

  5. python数据类型之 dict(map)

    字典  一.创建字典  方法①:  >>> dict1 = {}  >>> dict2 = {'name': 'earth', 'port': 80}  >& ...

  6. php中使用mysql_fetch_array输出数组至页面中展示

    用的是CI框架,很好的MVC结构 在Model层 public function showProteinCategory(){ $sql = "SELECT DISTINCT protein ...

  7. Android实时监听网络状态

    Android实时监听网络状态(1)   其实手机在网络方面的的监听也比较重要,有时候我们必须实时监控这个程序的实时网络状态,android在网络断开与连接的时候都会发出广播,我们通过接收系统的广播就 ...

  8. SQL语句详细汇总

    SQL语句详细汇总 | 浏览:3061 | 更新:2013-06-10 19:50 一.基础 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 d ...

  9. ios 设置屏幕方向的两种方法

    第一种:通过人为的办法改变view.transform的属性. 具体办法: view.transform一般是View的旋转,拉伸移动等属性,类似view.layer.transform,区别在于Vi ...

  10. Golang测试技术

    本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,即介绍使用Golang开发 时会使用到的 ...