概述

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

Message

在 RocketMQ 的使用中,Message 类是在发送消息时必须用到的,其中 body 即是消息的存放位置,还有的就是消息的 标识(flag) 和 属性(properties)

  1. public class Message {
  2. private String topic;
  3. private int flag;
  4. private Map<String, String> properties;
  5. private byte[] body;
  6. private String transactionId;
  7. }

消息的标识(flag)

变量名 含义
COMPRESSED_FLAG 压缩消息。消息为批量的时候,就会进行压缩,默认使用5级的 zip
MULTI_TAGS_FLAG 有多个 tag。
TRANSACTION_NOT_TYPE 事务为未知状态。当 Broker 回查 Producer 的时候,如果为 Commit 应该提交,为 Rollback 应该回滚,为 Unknown 时应该继续回查
TRANSACTION_PREPARED_TYPE 事务的运行状态。当前消息是事务的一部分
TRANSACTION_COMMIT_TYPE 事务的提交消息。要求提交事务
TRANSACTION_ROLLBACK_TYPE 事务的回滚消息。要求回滚事务
BORNHOST_V6_FLAG 生成该消息的 host 是否 ipv6 的地址
STOREHOSTADDRESS_V6_FLAG 持久化该消息的 host 是否是 ipv6 的地址

消息的属性(properties)

而消息的 properties 较多,只摘了一小段

属性 含义
KEYS 消息的 Key。服务器会通过 key 设置索引,应用可以通过 Topic 和 Key 来查找这条消息以及被谁消费
TAGS 消息的子类型,可以根据 tag 选择性消费
DELAY 延迟消息的延迟级别(共16级,理论上可以有18级)
RETRY_TOPIC 需要重试的 Topic(在 Broker 中会存放到 SCHEDULE_TOPIC_XXXX Topic,其中有 18 个 queue,对应 18 个重试延迟)
REAL_TOPIC 真实的 Topic (RocketMQ 经常使用更换目的 Topic 的"把戏",如事务消息和延时消息,这个字段记录了真正的 Topic)
PGROUP 生产者组
MAX_OFFSET\MIN_OFFSET 在 pull 中的最大偏移量和最小偏移量
TRANSFER_FLAG 事务有关标识
MSG_TYPE 消息类型,是否为回复消息
BUYER_ID 嗯...买家ID?

当然,这只是在生产者中的消息的样子,在 Broker 和消费者的眼中中,它是这样的

  1. public class MessageExt extends Message {
  2. private static final long serialVersionUID = 5720810158625748049L;
  3. private String brokerName;
  4. private int queueId;
  5. // 存盘的大小
  6. private int storeSize;
  7. // 在 ConsumerQueue 中的偏移量
  8. private long queueOffset;
  9. private int sysFlag;
  10. // 消息创建时间
  11. private long bornTimestamp;
  12. // 创建地址
  13. private SocketAddress bornHost;
  14. // 存盘时间
  15. private long storeTimestamp;
  16. private SocketAddress storeHost;
  17. private String msgId;
  18. // 在 commitLog 中的偏移量
  19. private long commitLogOffset;
  20. // crc 校验
  21. private int bodyCRC;
  22. // 消费重试次数
  23. private int reconsumeTimes;
  24. private long preparedTransactionOffset;
  25. }

消息的包装

那么,producer 生成了这样的消息后,会直接将其发出去吗?

让我们继续跟踪上一篇没讲完的内容

MQClientAPIImpl#sendMessage

  1. long beginStartTime = System.currentTimeMillis();
  2. RemotingCommand request = null;
  3. String msgType = msg.getProperty(MessageConst.PROPERTY_MESSAGE_TYPE);
  4. // 是否为 reply 消息
  5. boolean isReply = msgType != null && msgType.equals(MixAll.REPLY_MESSAGE_FLAG);
  6. if (isReply) {
  7. // 是 smart 消息则加上请求头
  8. if (sendSmartMsg) {
  9. SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
  10. request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE_V2, requestHeaderV2);
  11. } else {
  12. request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE, requestHeader);
  13. }
  14. } else {
  15. if (sendSmartMsg || msg instanceof MessageBatch) {
  16. SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
  17. request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
  18. } else {
  19. request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
  20. }
  21. }
  22. request.setBody(msg.getBody());
  23. /* -- pass -- */

在这里,我们可以看到在这又加了层套娃(只保留了body),然后才发送

RemotingCommand 的具体属性如下

  1. private int code;
  2. private LanguageCode language = LanguageCode.JAVA;
  3. private int version = 0;
  4. private int opaque = requestId.getAndIncrement();
  5. private int flag = 0;
  6. private String remark;
  7. private HashMap<String, String> extFields;
  8. private transient CommandCustomHeader customHeader;
  9. private transient byte[] body;

我们还在他的方法中找到了一个叫 encode 的方法,并且返回的是 ByteBuffer 。因此这就是实际发送的消息。

  1. public ByteBuffer encode() {
  2. // 1> header length size
  3. int length = 4;
  4. // 2> header data length
  5. byte[] headerData = this.headerEncode();
  6. length += headerData.length;
  7. // 3> body data length
  8. if (this.body != null) {
  9. length += body.length;
  10. }
  11. ByteBuffer result = ByteBuffer.allocate(4 + length);
  12. // length
  13. result.putInt(length);
  14. // header length
  15. result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));
  16. // header data
  17. result.put(headerData);
  18. // body data;
  19. if (this.body != null) {
  20. result.put(this.body);
  21. }
  22. result.flip();
  23. return result;
  24. }

消息的结构

具体的消息结构如下图:

其中每个字段在 Request 和 Response 中都有不同的含义

code

在 Request 中,为请求的操作码

  1. public class RequestCode {
  2. // 发送消息
  3. public static final int SEND_MESSAGE = 10;
  4. // 拉取消息
  5. public static final int PULL_MESSAGE = 11;
  6. // 查询消息(所在topic, 需要的 key, 最大数量, 开始偏移量, 结束偏移量)
  7. public static final int QUERY_MESSAGE = 12;
  8. // 查询 Broker 偏移量(未使用)
  9. public static final int QUERY_BROKER_OFFSET = 13;
  10. /*
  11. * 查询消费者偏移量
  12. * 消费者会将偏移量存储在内存中,当使用主从架构时,会默认由主 Broker 负责读于写
  13. * 为避免消息堆积,堆积消息超过指定的值时,会由从服务器来接管读,但会导致消费进度问题
  14. * 所以主从消费进度的一致性由 从服务器主动上报 和 消费者内存进度优先 来保证
  15. */
  16. // 查询消费者自己的偏移量
  17. public static final int QUERY_CONSUMER_OFFSET = 14;
  18. // 提交自己的偏移量
  19. public static final int UPDATE_CONSUMER_OFFSET = 15;
  20. // 创建或更新Topic
  21. public static final int UPDATE_AND_CREATE_TOPIC = 17;
  22. // 获取所有的Topic信息
  23. public static final int GET_ALL_TOPIC_CONFIG = 21;
  24. /* unused */
  25. public static final int GET_TOPIC_CONFIG_LIST = 22;
  26. public static final int GET_TOPIC_NAME_LIST = 23;
  27. // 更新 Broker 配置
  28. public static final int UPDATE_BROKER_CONFIG = 25;
  29. // 获取 Broker 配置
  30. public static final int GET_BROKER_CONFIG = 26;
  31. public static final int TRIGGER_DELETE_FILES = 27;
  32. // 获取 Broker 运行时信息
  33. public static final int GET_BROKER_RUNTIME_INFO = 28;
  34. // 通过时间戳查找偏移量
  35. public static final int SEARCH_OFFSET_BY_TIMESTAMP = 29;
  36. // 获取最大偏移量
  37. public static final int GET_MAX_OFFSET = 30;
  38. // 获取最小偏移量
  39. public static final int GET_MIN_OFFSET = 31;
  40. //
  41. public static final int GET_EARLIEST_MSG_STORETIME = 32;
  42. /* 由 Broker 处理 */
  43. // 通过消息ID查询消息
  44. public static final int VIEW_MESSAGE_BY_ID = 33;
  45. // 心跳消息
  46. public static final int HEART_BEAT = 34;
  47. // 注销客户端
  48. public static final int UNREGISTER_CLIENT = 35;
  49. // 报告消费失败(一段时间后重试) (Deprecated)
  50. public static final int CONSUMER_SEND_MSG_BACK = 36;
  51. // 事务结果(可能是 commit 或 rollback)
  52. public static final int END_TRANSACTION = 37;
  53. // 通过消费者组获取消费者列表
  54. public static final int GET_CONSUMER_LIST_BY_GROUP = 38;
  55. // 检查事务状态; Broker对于事务的未知状态的回查操作
  56. public static final int CHECK_TRANSACTION_STATE = 39;
  57. // 通知消费者的ID已经被更改
  58. public static final int NOTIFY_CONSUMER_IDS_CHANGED = 40;
  59. // 批量锁定 Queue (rebalance使用)
  60. public static final int LOCK_BATCH_MQ = 41;
  61. // 解锁 Queue
  62. public static final int UNLOCK_BATCH_MQ = 42;
  63. // 获得该 Broker 上的所有的消费者偏移量
  64. public static final int GET_ALL_CONSUMER_OFFSET = 43;
  65. // 获得延迟 Topic 上的偏移量
  66. public static final int GET_ALL_DELAY_OFFSET = 45;
  67. // 检查客户端配置
  68. public static final int CHECK_CLIENT_CONFIG = 46;
  69. // 更新或创建 ACL
  70. public static final int UPDATE_AND_CREATE_ACL_CONFIG = 50;
  71. // 删除 ACL 配置
  72. public static final int DELETE_ACL_CONFIG = 51;
  73. // 获取 Broker 集群的 ACL 信息
  74. public static final int GET_BROKER_CLUSTER_ACL_INFO = 52;
  75. // 更新全局白名单
  76. public static final int UPDATE_GLOBAL_WHITE_ADDRS_CONFIG = 53;
  77. // 获取 Broker 集群的 ACL 配置
  78. public static final int GET_BROKER_CLUSTER_ACL_CONFIG = 54;
  79. /* NameServer 相关 */
  80. // 放入键值配置
  81. public static final int PUT_KV_CONFIG = 100;
  82. // 获取键值配置
  83. public static final int GET_KV_CONFIG = 101;
  84. // 删除键值配置
  85. public static final int DELETE_KV_CONFIG = 102;
  86. // 注册 Broker
  87. public static final int REGISTER_BROKER = 103;
  88. // 注销 Broker
  89. public static final int UNREGISTER_BROKER = 104;
  90. // 获取指定 Topic 的路由信息
  91. public static final int GET_ROUTEINFO_BY_TOPIC = 105;
  92. // 获取 Broker 的集群信息
  93. public static final int GET_BROKER_CLUSTER_INFO = 106;
  94. // 更新或创建订阅组
  95. public static final int UPDATE_AND_CREATE_SUBSCRIPTIONGROUP = 200;
  96. // 获取所有订阅组的配置
  97. public static final int GET_ALL_SUBSCRIPTIONGROUP_CONFIG = 201;
  98. // 获取 Topic 的度量指标
  99. public static final int GET_TOPIC_STATS_INFO = 202;
  100. // 获取消费者在线列表(rpc)
  101. public static final int GET_CONSUMER_CONNECTION_LIST = 203;
  102. // 获取生产者在线列表
  103. public static final int GET_PRODUCER_CONNECTION_LIST = 204;
  104. public static final int WIPE_WRITE_PERM_OF_BROKER = 205;
  105. // 从 NameSrv 获取所有 Topic
  106. public static final int GET_ALL_TOPIC_LIST_FROM_NAMESERVER = 206;
  107. // 删除订阅组
  108. public static final int DELETE_SUBSCRIPTIONGROUP = 207;
  109. // 获取消费者的度量指标
  110. public static final int GET_CONSUME_STATS = 208;
  111. public static final int SUSPEND_CONSUMER = 209;
  112. public static final int RESUME_CONSUMER = 210;
  113. public static final int RESET_CONSUMER_OFFSET_IN_CONSUMER = 211;
  114. public static final int RESET_CONSUMER_OFFSET_IN_BROKER = 212;
  115. public static final int ADJUST_CONSUMER_THREAD_POOL = 213;
  116. public static final int WHO_CONSUME_THE_MESSAGE = 214;
  117. // 删除 Broker 中的 Topic
  118. public static final int DELETE_TOPIC_IN_BROKER = 215;
  119. // 删除 NameSrv 中的 Topic
  120. public static final int DELETE_TOPIC_IN_NAMESRV = 216;
  121. // 获取键值列表
  122. public static final int GET_KVLIST_BY_NAMESPACE = 219;
  123. // 重置消费者的消费进度
  124. public static final int RESET_CONSUMER_CLIENT_OFFSET = 220;
  125. // 从消费者中获取消费者的度量指标
  126. public static final int GET_CONSUMER_STATUS_FROM_CLIENT = 221;
  127. // 让 Broker 重置消费进度
  128. public static final int INVOKE_BROKER_TO_RESET_OFFSET = 222;
  129. // 让 Broker 更新消费者的度量信息
  130. public static final int INVOKE_BROKER_TO_GET_CONSUMER_STATUS = 223;
  131. // 查询消息被谁消费
  132. public static final int QUERY_TOPIC_CONSUME_BY_WHO = 300;
  133. // 从集群中获取 Topic
  134. public static final int GET_TOPICS_BY_CLUSTER = 224;
  135. // 注册过滤器服务器
  136. public static final int REGISTER_FILTER_SERVER = 301;
  137. // 注册消息过滤类
  138. public static final int REGISTER_MESSAGE_FILTER_CLASS = 302;
  139. // 查询消费时间
  140. public static final int QUERY_CONSUME_TIME_SPAN = 303;
  141. // 从 NameSrv 中获取系统Topic
  142. public static final int GET_SYSTEM_TOPIC_LIST_FROM_NS = 304;
  143. // 从 Broker 中获取系统Topic
  144. public static final int GET_SYSTEM_TOPIC_LIST_FROM_BROKER = 305;
  145. // 清理过期的消费队列
  146. public static final int CLEAN_EXPIRED_CONSUMEQUEUE = 306;
  147. // 获取 Consumer 的运行时信息
  148. public static final int GET_CONSUMER_RUNNING_INFO = 307;
  149. // 查询修正偏移量
  150. public static final int QUERY_CORRECTION_OFFSET = 308;
  151. // 直接消费消息
  152. public static final int CONSUME_MESSAGE_DIRECTLY = 309;
  153. // 发送消息(v2),优化网络数据包
  154. public static final int SEND_MESSAGE_V2 = 310;
  155. // 单元化相关 topic
  156. public static final int GET_UNIT_TOPIC_LIST = 311;
  157. // 获取含有单元化订阅组的 Topic 列表
  158. public static final int GET_HAS_UNIT_SUB_TOPIC_LIST = 312;
  159. // 获取含有单元化订阅组的非单元化 Topic 列表
  160. public static final int GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST = 313;
  161. // 克隆消费进度
  162. public static final int CLONE_GROUP_OFFSET = 314;
  163. // 查询 Broker 上的度量信息
  164. public static final int VIEW_BROKER_STATS_DATA = 315;
  165. // 清理未使用的 Topic
  166. public static final int CLEAN_UNUSED_TOPIC = 316;
  167. // 获取 broker 上的有关消费的度量信息
  168. public static final int GET_BROKER_CONSUME_STATS = 317;
  169. /* update the config of name server */
  170. public static final int UPDATE_NAMESRV_CONFIG = 318;
  171. /* get config from name server */
  172. public static final int GET_NAMESRV_CONFIG = 319;
  173. // 发送批量消息
  174. public static final int SEND_BATCH_MESSAGE = 320;
  175. // 查询消费的 Queue
  176. public static final int QUERY_CONSUME_QUEUE = 321;
  177. // 查询数据版本
  178. public static final int QUERY_DATA_VERSION = 322;
  179. /* resume logic of checking half messages that have been put in TRANS_CHECK_MAXTIME_TOPIC before */
  180. public static final int RESUME_CHECK_HALF_MESSAGE = 323;
  181. // 回送消息
  182. public static final int SEND_REPLY_MESSAGE = 324;
  183. public static final int SEND_REPLY_MESSAGE_V2 = 325;
  184. // push回送消息到客户端
  185. public static final int PUSH_REPLY_MESSAGE_TO_CLIENT = 326;
  186. }

在 Response 中,为响应码

  1. public class ResponseCode extends RemotingSysResponseCode {
  2. // 刷新到磁盘超时
  3. public static final int FLUSH_DISK_TIMEOUT = 10;
  4. // 从节点不可达
  5. public static final int SLAVE_NOT_AVAILABLE = 11;
  6. // 从节点刷盘超时
  7. public static final int FLUSH_SLAVE_TIMEOUT = 12;
  8. // 非法的消息结构
  9. public static final int MESSAGE_ILLEGAL = 13;
  10. // 服务不可用
  11. public static final int SERVICE_NOT_AVAILABLE = 14;
  12. // 版本不支持
  13. public static final int VERSION_NOT_SUPPORTED = 15;
  14. // 未授权的
  15. public static final int NO_PERMISSION = 16;
  16. // Topic 不存在
  17. public static final int TOPIC_NOT_EXIST = 17;
  18. // Topic 已经存在
  19. public static final int TOPIC_EXIST_ALREADY = 18;
  20. // 要拉取的偏移量不存在
  21. public static final int PULL_NOT_FOUND = 19;
  22. // 立刻重新拉取
  23. public static final int PULL_RETRY_IMMEDIATELY = 20;
  24. // 重定向拉取的偏移量
  25. public static final int PULL_OFFSET_MOVED = 21;
  26. // 不存在的队列
  27. public static final int QUERY_NOT_FOUND = 22;
  28. // 订阅的 url 解析失败
  29. public static final int SUBSCRIPTION_PARSE_FAILED = 23;
  30. // 目标订阅不存在
  31. public static final int SUBSCRIPTION_NOT_EXIST = 24;
  32. // 订阅不是最新的
  33. public static final int SUBSCRIPTION_NOT_LATEST = 25;
  34. // 订阅组不存在
  35. public static final int SUBSCRIPTION_GROUP_NOT_EXIST = 26;
  36. // 订阅的数据不存在 (tag表达式异常)
  37. public static final int FILTER_DATA_NOT_EXIST = 27;
  38. // 该 Broker 上订阅的数据不是最新的
  39. public static final int FILTER_DATA_NOT_LATEST = 28;
  40. // 事务应该提交
  41. public static final int TRANSACTION_SHOULD_COMMIT = 200;
  42. // 事务应该回滚
  43. public static final int TRANSACTION_SHOULD_ROLLBACK = 201;
  44. // 事务状态位置
  45. public static final int TRANSACTION_STATE_UNKNOW = 202;
  46. // 事务状态Group错误
  47. public static final int TRANSACTION_STATE_GROUP_WRONG = 203;
  48. // 买家ID不存在
  49. public static final int NO_BUYER_ID = 204;
  50. public static final int NOT_IN_CURRENT_UNIT = 205;
  51. // 消费者不在线(rpc)
  52. public static final int CONSUMER_NOT_ONLINE = 206;
  53. // 消费超时
  54. public static final int CONSUME_MSG_TIMEOUT = 207;
  55. // 消息不存在
  56. public static final int NO_MESSAGE = 208;
  57. // 更新或创建 ACL 配置失败
  58. public static final int UPDATE_AND_CREATE_ACL_CONFIG_FAILED = 209;
  59. // 删除 ACL 配置失败
  60. public static final int DELETE_ACL_CONFIG_FAILED = 210;
  61. // 更新全局白名单地址失败
  62. public static final int UPDATE_GLOBAL_WHITE_ADDRS_CONFIG_FAILED = 211;
  63. }
lang

字段为消息发起方编码语言,这里默认为 java

  1. private LanguageCode language = LanguageCode.JAVA;
version

消息发起方的程序版本

opaque

该字段是为了在同一连接上标识不同的请求,在响应的时候能够回调对应的函数( rocketmq 的发送使用了 TCP 连接复用)

remark

在 Reqeust 中,用于传输自定义文本

在 Response 中,用于传输错误的原因

ext

传输自定义的消息头

消息的发送

在知道消息长啥样后,就可以继续看发送代码了

  1. switch (communicationMode) {
  2. case ONEWAY:
  3. this.remotingClient.invokeOneway(addr, request, timeoutMillis);
  4. return null;
  5. case ASYNC:
  6. final AtomicInteger times = new AtomicInteger();
  7. long costTimeAsync = System.currentTimeMillis() - beginStartTime;
  8. if (timeoutMillis < costTimeAsync) {
  9. throw new RemotingTooMuchRequestException("sendMessage call timeout");
  10. }
  11. this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
  12. retryTimesWhenSendFailed, times, context, producer);
  13. return null;
  14. case SYNC:
  15. long costTimeSync = System.currentTimeMillis() - beginStartTime;
  16. if (timeoutMillis < costTimeSync) {
  17. throw new RemotingTooMuchRequestException("sendMessage call timeout");
  18. }
  19. return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
  20. default:
  21. assert false;
  22. break;
  23. }
  24. return null;

NettyRemotingClient#invokeOneway

我们先来看最简单的Oneway

  1. public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException,
  2. RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
  3. // 创建 Channel
  4. final Channel channel = this.getAndCreateChannel(addr);
  5. if (channel != null && channel.isActive()) {
  6. try {
  7. doBeforeRpcHooks(addr, request);
  8. // 使用建立好的连接发送
  9. this.invokeOnewayImpl(channel, request, timeoutMillis);
  10. } catch (RemotingSendRequestException e) {
  11. log.warn("invokeOneway: send request exception, so close the channel[{}]", addr);
  12. this.closeChannel(addr, channel);
  13. throw e;
  14. }
  15. } else {
  16. this.closeChannel(addr, channel);
  17. throw new RemotingConnectException(addr);
  18. }
  19. }

以上可以大致抽象为两个操作:获取或建立TCP连接、通过连接发送数据,同时一旦发生异常则关闭连接

NettyRemotingClient#getAndCreateChannel

先看第一个操作

  1. private Channel getAndCreateChannel(final String addr) throws RemotingConnectException, InterruptedException {
  2. // 地址为空则说明要获取的是NameServer的地址
  3. if (null == addr) {
  4. return getAndCreateNameserverChannel();
  5. }
  6. // 尝试从缓存中获取
  7. ChannelWrapper cw = this.channelTables.get(addr);
  8. if (cw != null && cw.isOK()) {
  9. return cw.getChannel();
  10. }
  11. // 没有或未就绪则新建连接
  12. return this.createChannel(addr);
  13. }

可以看出,这里是由一个 ChannelTable 来维护所有的连接,而 ChannelTable 又是由 NettyRemotingClient 维护,即其是在 JVM 上的全局共享实例。

然后再具体查看创建的方法,可以发现 Channel 最终是由客户端的 Bootstrap 异步创建

  1. ChannelWrapper cw = this.channelTables.get(addr);
  2. if (cw != null && cw.isOK()) {
  3. return cw.getChannel();
  4. }
  5. // 连接的建立是串行的
  6. if (this.lockChannelTables.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
  7. try {
  8. // 双重校验保证连接确实没被创建
  9. boolean createNewConnection;
  10. cw = this.channelTables.get(addr);
  11. if (cw != null) {
  12. if (cw.isOK()) {
  13. // 连接建立完成
  14. return cw.getChannel();
  15. } else if (!cw.getChannelFuture().isDone()) {
  16. createNewConnection = false;
  17. } else {
  18. // 建立过但失败了
  19. this.channelTables.remove(addr);
  20. createNewConnection = true;
  21. }
  22. } else {
  23. createNewConnection = true;
  24. }
  25. if (createNewConnection) {
  26. // 实际上的连接创建
  27. ChannelFuture channelFuture = this.bootstrap.connect(RemotingHelper.string2SocketAddress(addr));
  28. log.info("createChannel: begin to connect remote host[{}] asynchronously", addr);
  29. cw = new ChannelWrapper(channelFuture);
  30. this.channelTables.put(addr, cw);
  31. }
  32. } catch (Exception e) {
  33. log.error("createChannel: create channel exception", e);
  34. } finally {
  35. this.lockChannelTables.unlock();
  36. }
  37. } else {
  38. log.warn("createChannel: try to lock channel table, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
  39. }
  40. if (cw != null) {
  41. ChannelFuture channelFuture = cw.getChannelFuture();
  42. // 阻塞直到创建完成
  43. if (channelFuture.awaitUninterruptibly(this.nettyClientConfig.getConnectTimeoutMillis())) {
  44. if (cw.isOK()) {
  45. log.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString());
  46. return cw.getChannel();
  47. } else {
  48. log.warn("createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString(), channelFuture.cause());
  49. }
  50. } else {
  51. log.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.nettyClientConfig.getConnectTimeoutMillis(),
  52. channelFuture.toString());
  53. }
  54. }
  55. return null;

NettyRemotingAbstract#invokeOnewayImpl

然后接着看第二个操作:通过连接发送数据

  1. public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)
  2. throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
  3. // 在请求头上的 flag 标记为 oneway 请求
  4. request.markOnewayRPC();
  5. // 获取信号量,保证不会系统不会承受过多请求
  6. boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
  7. if (acquired) {
  8. final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway);
  9. try {
  10. channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
  11. @Override
  12. public void operationComplete(ChannelFuture f) throws Exception {
  13. // 真正发送完成后,释放锁
  14. once.release();
  15. if (!f.isSuccess()) {
  16. log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed.");
  17. }
  18. }
  19. });
  20. } catch (Exception e) {
  21. once.release();
  22. log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed.");
  23. throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
  24. }
  25. } else {
  26. // 超出请求数
  27. if (timeoutMillis <= 0) {
  28. throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast");
  29. } else {
  30. // 超时
  31. String info = String.format(
  32. "invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreOnewayValue: %d",
  33. timeoutMillis,
  34. this.semaphoreOneway.getQueueLength(),
  35. this.semaphoreOneway.availablePermits()
  36. );
  37. log.warn(info);
  38. throw new RemotingTimeoutException(info);
  39. }
  40. }
  41. }

这块比较简单,在获取发送 oneway 的信号量后调用 Channel 的 writeAndFlush 方法发送,发送完成后释放

MQClientAPIImpl#sendMessageSync

然后来看同步的发送

  1. // 发送请求
  2. RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
  3. assert response != null;
  4. // 处理响应
  5. return this.processSendResponse(brokerName, msg, response,addr);

其中在 NettyRemotingClient#invokeSync 做的事和 oneway 发送差不多,都是创建或获取 Channel 然后处理钩子然后调用父类的响应实现。

所以我们直接来看父类是咋做的

NettyRemotingAbstract#invokeSyncImpl

  1. public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
  2. final long timeoutMillis)
  3. throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
  4. final int opaque = request.getOpaque();
  5. try {
  6. final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
  7. this.responseTable.put(opaque, responseFuture);
  8. final SocketAddress addr = channel.remoteAddress();
  9. channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
  10. @Override
  11. public void operationComplete(ChannelFuture f) throws Exception {
  12. if (f.isSuccess()) {
  13. responseFuture.setSendRequestOK(true);
  14. return;
  15. } else {
  16. responseFuture.setSendRequestOK(false);
  17. }
  18. // 发送失败,回填 responseFuture 并在 responseTable 移除其
  19. responseTable.remove(opaque);
  20. responseFuture.setCause(f.cause());
  21. responseFuture.putResponse(null);
  22. log.warn("send a request command to channel <" + addr + "> failed.");
  23. }
  24. });
  25. // 使用 countDownLatch 来等待响应到达
  26. RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
  27. if (null == responseCommand) {
  28. if (responseFuture.isSendRequestOK()) {
  29. throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
  30. responseFuture.getCause());
  31. } else {
  32. throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
  33. }
  34. }
  35. return responseCommand;
  36. } finally {
  37. this.responseTable.remove(opaque);
  38. }
  39. }

发现和 oneway 的发送的区别了吗?其中最大的区别有两个:

  1. 在 oneway 中出现的信号量限流不见了
  2. 出现了 responseTable 来管理所有的 responseFuture

我们发现了以下定义

  1. protected final ConcurrentMap<Integer /* opaque */, ResponseFuture> responseTable =
  2. new ConcurrentHashMap<Integer, ResponseFuture>(256);

由之前介绍到的 opaque 可以知道,这里对 opaque 和 responseFuture 做了映射,当响应到来时,可以根据 opaque 处理对应的 responseFuture。而流控的消失也是可以理解的,毕竟同步发送会阻塞整个线程,所以在发送方来做流控是不合理的

最后发送完成后使用 processSendResponse 处理响应后返回发送结果

NettyRemotingAbstract#invokeAsyncImpl

最后看异步的发送

  1. public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
  2. final InvokeCallback invokeCallback)
  3. throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
  4. long beginStartTime = System.currentTimeMillis();
  5. final int opaque = request.getOpaque();
  6. // 信号量流控
  7. boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
  8. if (acquired) {
  9. final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
  10. // 发生了任何阻塞操作后都要检查是否超时...
  11. long costTime = System.currentTimeMillis() - beginStartTime;
  12. if (timeoutMillis < costTime) {
  13. once.release();
  14. throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
  15. }
  16. final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
  17. this.responseTable.put(opaque, responseFuture);
  18. try {
  19. channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
  20. @Override
  21. public void operationComplete(ChannelFuture f) throws Exception {
  22. if (f.isSuccess()) {
  23. responseFuture.setSendRequestOK(true);
  24. return;
  25. }
  26. requestFail(opaque);
  27. log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
  28. }
  29. });
  30. } catch (Exception e) {
  31. responseFuture.release();
  32. log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
  33. throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
  34. }
  35. } else {
  36. if (timeoutMillis <= 0) {
  37. throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
  38. } else {
  39. String info =
  40. String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
  41. timeoutMillis,
  42. this.semaphoreAsync.getQueueLength(),
  43. this.semaphoreAsync.availablePermits()
  44. );
  45. log.warn(info);
  46. throw new RemotingTimeoutException(info);
  47. }
  48. }
  49. }

这里和同步发送区别不大,主要还是用了信号量做流控,且不在 responseFuture 使用 countDownLatch 阻塞

Netty 组件

总所周知,在 Channel 写入消息后,就会进入 Pipeline 的尾部,并往出站的方向流出,接下来就看看在出站的过程中又是怎样做的

NettyRemotingAbstract

在 rocketMQ 的 remoting 模块下有一个 netty 包,那里就是 RPC 调用的处理位置。

而在 NettyRemotingAbstract 下,我们就已经看到了有三个重要的方法 invokeOnewayImpl、invokeAsyncImpl、invokeSyncImpl

同时这个类的子类有 NettyRemotingClient 和 NettyRemotingServer,我们先来看和 Producer 有关的部分

  1. public abstract class NettyRemotingAbstract {
  2. /* oneway请求的信号量 */
  3. protected final Semaphore semaphoreOneway;
  4. /* async请求的信号量 */
  5. protected final Semaphore semaphoreAsync;
  6. /* 缓存所有进行中的请求(因为请求是并行的,要对对应的请求做出对应响应), */
  7. protected final ConcurrentMap<Integer /* opaque */, ResponseFuture> responseTable =
  8. new ConcurrentHashMap<Integer, ResponseFuture>(256);
  9. /* 对请求码进行对应的处理 */
  10. protected final HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>> processorTable =
  11. new HashMap<Integer, Pair<NettyRequestProcessor, ExecutorService>>(64);
  12. /* Executor to feed netty events to user defined {@link ChannelEventListener}. */
  13. protected final NettyEventExecutor nettyEventExecutor = new NettyEventExecutor();
  14. /* 默认请求处理器 */
  15. protected Pair<NettyRequestProcessor, ExecutorService> defaultRequestProcessor;
  16. /* SSL上下文 {@link SslHandler}. */
  17. protected volatile SslContext sslContext;
  18. /* rpc hooks */
  19. protected List<RPCHook> rpcHooks = new ArrayList<RPCHook>();
  20. /**
  21. * Custom channel event listener.
  22. *
  23. * @return custom channel event listener if defined; null otherwise.
  24. */
  25. public abstract ChannelEventListener getChannelEventListener();
  26. }

request 响应部分和 response 响应部分

  1. /**
  2. * 对请求进行响应
  3. * @param ctx Channel handler context.
  4. * @param msg incoming remoting command.
  5. * @throws Exception if there were any error while processing the incoming command.
  6. */
  7. public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
  8. final RemotingCommand cmd = msg;
  9. // 该请求可能是一个 request, 也可能是自己发出一个请求的 response
  10. if (cmd != null) {
  11. // 通过请求头上的 flag 就能判断
  12. switch (cmd.getType()) {
  13. case REQUEST_COMMAND:
  14. processRequestCommand(ctx, cmd);
  15. break;
  16. case RESPONSE_COMMAND:
  17. processResponseCommand(ctx, cmd);
  18. break;
  19. default:
  20. break;
  21. }
  22. }
  23. }
  24. /**
  25. * Process incoming request command issued by remote peer.
  26. *
  27. * @param ctx channel handler context.
  28. * @param cmd request command.
  29. */
  30. public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
  31. final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
  32. final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
  33. final int opaque = cmd.getOpaque();
  34. if (pair != null) {
  35. Runnable run = new Runnable() {
  36. @Override
  37. public void run() {
  38. try {
  39. doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
  40. final RemotingResponseCallback callback = new RemotingResponseCallback() {
  41. @Override
  42. public void callback(RemotingCommand response) {
  43. doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
  44. // 如果不是 oneway 请求的话,则需要进行响应
  45. if (!cmd.isOnewayRPC()) {
  46. if (response != null) {
  47. response.setOpaque(opaque);
  48. response.markResponseType();
  49. try {
  50. ctx.writeAndFlush(response);
  51. } catch (Throwable e) {
  52. log.error("process request over, but response failed", e);
  53. log.error(cmd.toString());
  54. log.error(response.toString());
  55. }
  56. } else {
  57. }
  58. }
  59. }
  60. };
  61. if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
  62. AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
  63. processor.asyncProcessRequest(ctx, cmd, callback);
  64. } else {
  65. NettyRequestProcessor processor = pair.getObject1();
  66. RemotingCommand response = processor.processRequest(ctx, cmd);
  67. callback.callback(response);
  68. }
  69. } catch (Throwable e) {
  70. log.error("process request exception", e);
  71. log.error(cmd.toString());
  72. if (!cmd.isOnewayRPC()) {
  73. final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
  74. RemotingHelper.exceptionSimpleDesc(e));
  75. response.setOpaque(opaque);
  76. ctx.writeAndFlush(response);
  77. }
  78. }
  79. }
  80. };
  81. // 是否触发流控
  82. if (pair.getObject1().rejectRequest()) {
  83. final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
  84. "[REJECTREQUEST]system busy, start flow control for a while");
  85. response.setOpaque(opaque);
  86. ctx.writeAndFlush(response);
  87. return;
  88. }
  89. try {
  90. final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
  91. // 交由对应的处理器处理
  92. pair.getObject2().submit(requestTask);
  93. } catch (RejectedExecutionException e) {
  94. if ((System.currentTimeMillis() % 10000) == 0) {
  95. log.warn(RemotingHelper.parseChannelRemoteAddr(ctx.channel())
  96. + ", too many requests and system thread pool busy, RejectedExecutionException "
  97. + pair.getObject2().toString()
  98. + " request code: " + cmd.getCode());
  99. }
  100. if (!cmd.isOnewayRPC()) {
  101. final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
  102. "[OVERLOAD]system busy, start flow control for a while");
  103. response.setOpaque(opaque);
  104. ctx.writeAndFlush(response);
  105. }
  106. }
  107. } else {
  108. // 没有对应的响应方式且不存在默认响应器
  109. String error = " request type " + cmd.getCode() + " not supported";
  110. final RemotingCommand response =
  111. RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
  112. response.setOpaque(opaque);
  113. ctx.writeAndFlush(response);
  114. log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error);
  115. }
  116. }
  117. /**
  118. * Process response from remote peer to the previous issued requests.
  119. *
  120. * @param ctx channel handler context.
  121. * @param cmd response command instance.
  122. */
  123. public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
  124. final int opaque = cmd.getOpaque();
  125. final ResponseFuture responseFuture = responseTable.get(opaque);
  126. if (responseFuture != null) {
  127. responseFuture.setResponseCommand(cmd);
  128. responseTable.remove(opaque);
  129. // 处理回调方法
  130. if (responseFuture.getInvokeCallback() != null) {
  131. executeInvokeCallback(responseFuture);
  132. } else {
  133. // 如果不是的话,说明这是一个阻塞调用,还需要去进行释放
  134. responseFuture.putResponse(cmd);
  135. responseFuture.release();
  136. }
  137. } else {
  138. log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
  139. log.warn(cmd.toString());
  140. }
  141. }
  142. /**
  143. * 在回调执行器中执行回调。如果回调执行器为空,则直接在当前线程中运行
  144. */
  145. private void executeInvokeCallback(final ResponseFuture responseFuture) {
  146. boolean runInThisThread = false;
  147. ExecutorService executor = this.getCallbackExecutor();
  148. if (executor != null) {
  149. try {
  150. executor.submit(new Runnable() {
  151. @Override
  152. public void run() {
  153. try {
  154. responseFuture.executeInvokeCallback();
  155. } catch (Throwable e) {
  156. log.warn("execute callback in executor exception, and callback throw", e);
  157. } finally {
  158. responseFuture.release();
  159. }
  160. }
  161. });
  162. } catch (Exception e) {
  163. runInThisThread = true;
  164. log.warn("execute callback in executor exception, maybe executor busy", e);
  165. }
  166. } else {
  167. runInThisThread = true;
  168. }
  169. if (runInThisThread) {
  170. try {
  171. responseFuture.executeInvokeCallback();
  172. } catch (Throwable e) {
  173. log.warn("executeInvokeCallback Exception", e);
  174. } finally {
  175. responseFuture.release();
  176. }
  177. }
  178. }
  179. /**
  180. * 执行回调方法需要的线程池
  181. */
  182. public abstract ExecutorService getCallbackExecutor();
  183. /**
  184. * 定期调用此方法来扫描和过期已弃用的请求。
  185. */
  186. public void scanResponseTable() {
  187. final List<ResponseFuture> rfList = new LinkedList<ResponseFuture>();
  188. Iterator<Entry<Integer, ResponseFuture>> it = this.responseTable.entrySet().iterator();
  189. while (it.hasNext()) {
  190. Entry<Integer, ResponseFuture> next = it.next();
  191. ResponseFuture rep = next.getValue();
  192. if ((rep.getBeginTimestamp() + rep.getTimeoutMillis() + 1000) <= System.currentTimeMillis()) {
  193. rep.release();
  194. it.remove();
  195. rfList.add(rep);
  196. log.warn("remove timeout request, " + rep);
  197. }
  198. }
  199. for (ResponseFuture rf : rfList) {
  200. try {
  201. executeInvokeCallback(rf);
  202. } catch (Throwable e) {
  203. log.warn("scanResponseTable, operationComplete Exception", e);
  204. }
  205. }
  206. }
  207. /**
  208. * 该服务用于使用子类实现的自定义的事件处理器对指定的事件进行处理
  209. */
  210. class NettyEventExecutor extends ServiceThread {
  211. }

这段代码看起来比较长,但实际逻辑还是比较简单的

  • 响应 Request

    1. 流量控制触发检查;触发则直接返回
    2. 获取 request code 对应的处理器,执行请求;根据同步或异步执行不同请求
    3. 返回响应;如果为 oneway request,不进行响应
  • 响应 Response
    1. 取出对应的活动中的 request
    2. 根据异步或同步来处理回调

NettyRemotingClient

再来看它的客户端实现类,这个类中我们主要看 Bootstrap 的创建

  1. // 建立客户端的 Bootstrap
  2. Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class)
  3. // 禁用 Nagle 算法
  4. .option(ChannelOption.TCP_NODELAY, true)
  5. // 关闭 keepalive,由我们自己管理连接
  6. .option(ChannelOption.SO_KEEPALIVE, false)
  7. // 设定的超时时间
  8. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
  9. // 发送窗口和接收窗口的大小
  10. .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
  11. .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize())
  12. .handler(new ChannelInitializer<SocketChannel>() {
  13. @Override
  14. public void initChannel(SocketChannel ch) throws Exception {
  15. ChannelPipeline pipeline = ch.pipeline();
  16. // TLS层添加
  17. if (nettyClientConfig.isUseTLS()) {
  18. if (null != sslContext) {
  19. pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc()));
  20. log.info("Prepend SSL handler");
  21. } else {
  22. log.warn("Connections are insecure as SSLContext is null!");
  23. }
  24. }
  25. pipeline.addLast(
  26. // 使用自定义的 EventLoop
  27. defaultEventExecutorGroup,
  28. // 注册编解码注册器
  29. new NettyEncoder(),
  30. new NettyDecoder(),
  31. // 注册 idle 检查,下一个handle通过覆写userEventTriggered监听连接超时事件
  32. new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()),
  33. // 管理连接,超时处理,维护channelTables与存活的连接
  34. new NettyConnectManageHandler(),
  35. // 实际上处理收到的请求
  36. new NettyClientHandler());
  37. }
  38. });

编码和解码我们都已经知道是直接编解码成字节流,而 NettyClientHandler 的实现就是直接调用父类的请求处理,所以我们主要看下 NettyConnectManageHandler

  1. class NettyConnectManageHandler extends ChannelDuplexHandler {
  2. @Override
  3. public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
  4. ChannelPromise promise) throws Exception {
  5. final String local = localAddress == null ? "UNKNOWN" : RemotingHelper.parseSocketAddressAddr(localAddress);
  6. final String remote = remoteAddress == null ? "UNKNOWN" : RemotingHelper.parseSocketAddressAddr(remoteAddress);
  7. log.info("NETTY CLIENT PIPELINE: CONNECT {} => {}", local, remote);
  8. super.connect(ctx, remoteAddress, localAddress, promise);
  9. if (NettyRemotingClient.this.channelEventListener != null) {
  10. NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remote, ctx.channel()));
  11. }
  12. }
  13. @Override
  14. public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
  15. final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
  16. log.info("NETTY CLIENT PIPELINE: DISCONNECT {}", remoteAddress);
  17. closeChannel(ctx.channel());
  18. super.disconnect(ctx, promise);
  19. if (NettyRemotingClient.this.channelEventListener != null) {
  20. NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel()));
  21. }
  22. }
  23. @Override
  24. public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
  25. final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
  26. log.info("NETTY CLIENT PIPELINE: CLOSE {}", remoteAddress);
  27. // 从 channelTables 移除
  28. closeChannel(ctx.channel());
  29. super.close(ctx, promise);
  30. // 处理已经失败的请求,调用回调方法
  31. NettyRemotingClient.this.failFast(ctx.channel());
  32. if (NettyRemotingClient.this.channelEventListener != null) {
  33. NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel()));
  34. }
  35. }
  36. @Override
  37. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
  38. if (evt instanceof IdleStateEvent) {
  39. // 处理超过时未接收心跳的 channel
  40. IdleStateEvent event = (IdleStateEvent) evt;
  41. if (event.state().equals(IdleState.ALL_IDLE)) {
  42. final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
  43. log.warn("NETTY CLIENT PIPELINE: IDLE exception [{}]", remoteAddress);
  44. closeChannel(ctx.channel());
  45. if (NettyRemotingClient.this.channelEventListener != null) {
  46. NettyRemotingClient.this
  47. .putNettyEvent(new NettyEvent(NettyEventType.IDLE, remoteAddress, ctx.channel()));
  48. }
  49. }
  50. }
  51. ctx.fireUserEventTriggered(evt);
  52. }
  53. @Override
  54. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  55. final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
  56. log.warn("NETTY CLIENT PIPELINE: exceptionCaught {}", remoteAddress);
  57. log.warn("NETTY CLIENT PIPELINE: exceptionCaught exception.", cause);
  58. closeChannel(ctx.channel());
  59. if (NettyRemotingClient.this.channelEventListener != null) {
  60. NettyRemotingClient.this.putNettyEvent(new NettyEvent(NettyEventType.EXCEPTION, remoteAddress, ctx.channel()));
  61. }
  62. }
  63. }

NettyConnectManageHandler 继承了 ChannelDuplexHandler 类,以此监听 Channel。

其主要做的事是:

  • 在连接 close 时移除在 channelTables 中移除并关闭连接
  • 关闭超时的连接

最后,NettyClientHandler 将收到的请求直接传入 NettyRemotingAbstractprocessMessageReceived 方法来处理

RocketMQ源码详解 | Producer篇 · 其二:消息组成、发送链路的更多相关文章

  1. RocketMQ源码详解 | Producer篇 · 其一:Start,然后 Send 一条消息

    概述 DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); ...

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

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

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

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

  4. RocketMQ源码详解 | Broker篇 · 其四:事务消息、批量消息、延迟消息

    概述 在上文中,我们讨论了消费者对于消息拉取的实现,对于 RocketMQ 这个黑盒的心脏部分,我们顺着消息的发送流程已经将其剖析了大半部分.本章我们不妨乘胜追击,接着讨论各种不同的消息的原理与实现. ...

  5. RocketMQ源码详解 | Broker篇 · 其三:CommitLog、索引、消费队列

    概述 上一章中,已经介绍了 Broker 的文件系统的各个层次与部分细节,本章将继续了解在逻辑存储层的三个文件 CommitLog.IndexFile.ConsumerQueue 的一些细节.文章最后 ...

  6. RocketMQ源码详解 | Broker篇 · 其五:高可用之主从架构

    概述 对于一个消息中间件来讲,高可用功能是极其重要的,RocketMQ 当然也具有其对应的高可用方案. 在 RocketMQ 中,有主从架构和 Dledger 两种高可用方案: 第一种通过主 Brok ...

  7. RocketMQ源码详解 | Broker篇 · 其二:文件系统

    概述 在 Broker 的通用请求处理器将一个消息进行分发后,就来到了 Broker 的专门处理消息存储的业务处理器部分.本篇文章,我们将要探讨关于 RocketMQ 高效的原因之一:文件结构的良好设 ...

  8. Linux内核源码详解——命令篇之iostat[zz]

    本文主要分析了Linux的iostat命令的源码,iostat的主要功能见博客:性能测试进阶指南——基础篇之磁盘IO iostat源码共563行,应该算是Linux系统命令代码比较少的了.源代码中主要 ...

  9. [转]Linux内核源码详解--iostat

    Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

随机推荐

  1. AWVS13批量添加目标脚本

    # -*-coding:utf-8-*- # @Author:malphite.tang import json import requests from queue import Queue req ...

  2. Vue项目-初始化之 vue-cli

    1.初始化项目 a.Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供: 通过 @vue/cli 搭建交互式的项目脚手架. 通过 @vue/cli + @vue/cli-servi ...

  3. Ubuntu中类似QQ截图的截图工具并实现鼠标右键菜单截图

    @ 目录 简介: 安装: 设置快捷键: 实现鼠标右键菜单截图: 简介: 在Windows中用惯了强大易用的QQ截图,会不习惯Ubuntu中的截图工具. 软件名为火焰截图,功能类似QQ截图,可以设置快捷 ...

  4. 洛谷P1060——开心的金明

    https://www.luogu.org/problem/show?pid=1060 题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间.更让他高兴的是,妈妈 ...

  5. 关于 CLAHE 的理解及实现

    CLAHE CLAHE 是一种非常有效的直方图均衡算法, 目前网上已经有很多文章进行了说明, 这里说一下自己的理解. CLAHE是怎么来的 直方图均衡是一种简单快速的图像增强方法, 其原理和实现过程以 ...

  6. Docker Command and Dockerfile

    镜像相关命令 # 下载镜像 docker pull xxx # 搜素镜像 docker search xxx # 查看已经下载了哪些镜像 docker images # 查看已下载镜像的id dock ...

  7. mysql事务干货详解

    说明: mysql是现在行业中流行的关系型数据库,它的核心是存储引擎.mysql的存储引擎有很多种我们可以通过命令查看如下 SHOW ENGINES 不同版本得到的数据不一样,我们今天说的事务是在 M ...

  8. 发送curl请求的函数

    //发送curl请求的函数function curl_request($url, $post = false, $data=array(), $https = false){ //使用curl_ini ...

  9. self是什么?什么时候加?什么时候不加?

    Python里边self倒底是什么?什么时候加self?什么时候不加? self是什么? 如果你问别人大多人回答是: 指对象本身,然后噼里啪啦说一堆,然后听完的你,仍然完全搞不清楚,什么时候变量前需要 ...

  10. 智汀家庭云-开发指南Golang: 插件模块

    插件模块 当前所说的插件仅指设备类插件,插件为SA提供额外的设备发现和控制功能: 插件通过实现定义的grpc接口,以grpc服务的形式运行,提供接口给SA调用 插件同时需要http服务提供h5页面及静 ...