1 前置流程
服务器是如何处理消费者拉消息的流程的呢?
消费者向服务器发起请求,封装了包装类RemoteCommand,包含了pull业务参数,服务器拿到了RemoteCommand请求后经过编解码框架会到serverHandler,该Handler就是NettyServerHandler,服务器在启动阶段向协议处理器映射表注册了好多处理器,key是协议码,value是pair对象,pair对象第一个value就是处理器,第二个value就是运行时要使用的线程池,NettyServerHandler的逻辑很简单,就是根据RemoteCommand对象的RequestCode在现已处理器映射表中找到pair,之后根据处理器包装出来的RequestTask提交到线程池里,因此要搞明白RequestTask做了啥。
2 RequestTask
RequestTask其实就做了两件事:
- 调用处理器的处理方法,会得到一个为RemoteCommand的返回值;
- 将该返回值传入callback方法,该方法内,如果RemoteCommand对象不为空,则会调用ctx.writeAndFlush将数据发送给客户端;如果RemoteCommand对象为空则什么也不做,流程如下:
接下来就是处理器的执行逻辑,也就是PullMessageProcessor。
3 PullMessageProcessor处理器
处理器的逻辑如下图所示,其中,brokerAllowSuspend值表示是否允许本次拉查询在服务端这里长轮询,当拉消息的时候队列里没消息或者没有查询到消息的时候允许长轮询,在创建响应对象时使用的opaque字段是消费者传过来的RemoteCommand对象里的opaque字段一致。
查询结果字段里的bufferTotalSize为messageBufferList的大小,suggestPullingFromSlave为服务器建议客户端下次到该queue 拉消息时使用主或从节点,messageBufferList里的每一个数据都是一条message,,查消息的时候用的是MapedFile对象的get方法,返回的信息为SelectMappedBufferResult对象,该对象主要包含MappedFile和byteBuffer,当将查询结果对象GetMessageResult对象放到Response中后会调用release方法,该方法遍历SelectMappedBufferResult的List,每个SelectMappedBufferResult对象会调用MappedFile方法的release方法释放资源。
检查response状态,然后根据状态做处理,长轮询的好处在于不会让客户端频繁请求服务器,将response设置为null,啥也不做,即这一步不会给客户端发送数据, 具体逻辑如下:
返回码对照表如下:
接下来看在PULL_NOT_FOUND中的逻辑中长轮询的具体实现原理。
4 服务器长轮询拉消息请求
长轮询服务的PullRequestHoldService服务的pullRequestTable中的value为ManyPullRequest,里面主要对象为PullRequest的List,正常情况该List只有一条或没有数据,因为正常情况下每个queue只有一个消费者。当消费模式为广播模式的时候,消费者需要订阅每一个队列,这种情况ManyPullRequest会存在多个PullRequest,还有在集群新增或减少节点的时候需要重新进行负载均衡,原来所持有当前queue的节点还没做负载均衡运算的时候也有可能存在多个。
PullRequestHoldService继承了ServiceThread,具体内容如下:
run方法具体内容在死循环里,每循环一次休眠五秒如下:
核心方法为notifyMessageArriving,通知消息到达,具体实现如下: