元数据

元数据由RouteInfoManager类管理,包括如下:

java rocketmq 消费者顺序消费 rocketmq消费者线程数_线程池

1) createNamesrvController方法

执行createNamesrvController方法得到一个NamesrvController实例;

1.1)实例化NamesrvConfig和NettyServerConfig

NamesrvConfig主要保存一些属性包括各种配置路径等,其实例作为NamesrvController构造方法的入参;
NettyServerConfig中主要保存一些属性值到实例变量,在NettyRemotingServer中使用这些属性,包括监听的端口号,保存服务端worker线程组的业务线程池线程数为8,服务端回调线程池的线程数为0,服务端worker线程组线程数为3(有3个selector),保存单向,异步发送的并发上限分别为256和64,netty服务端读写缓冲区最大64kb,保存服务端内存池使能true;NettyServerConfig和客户端NettyClientConfig对应;

1.2)实例化NamesrvController

1.2.1)创建调度线程池
ScheduledExecutorService类型的调度线程池scheduledExecutorService,检查存活的broker状态,打印配置;
1.2.2)创建路由信息的类实例
实例化RouteInfoManager,用于管理路由信息,如元数据;
1.2.3)创建监听channel状态的类的实例
实例化BrokerHousekeepingService,用于监听channel状态,处理channel发生连接,关闭,异常,空闲的事件;

2)start方法

入参为NamesrvController实例;

2.1)controller.initialize方法

2.1.1)实例化网络服务NettyRemotingServer
客户端负责网络的类是NettyRemotingClient,是对应的;NettyRemotingServer继承自NettyRemotingAbstract,实现了RemotingServer接口,而RemotingServer接口和RemotingClient接口都继承了RemotingService接口; NettyRemotingServer的构造方法的两个入参是NettyServerConfig实例,和ChannelEventListener实例;
在NettyRemotingServer的构造方法中会做如下操作:
a)实例化Semaphore,封装服务端单步,异步发送的并发上限分别为256,64;
b)实例化ServerBootstrap;
c)设置服务端回调处理线程池的线程数为4;
d)设置publicExecutor为jdk的newFixedThreadPool;
f)设置服务端boss组线程数为1个,worker组线程数为3个,如果是linux系统,则是EpollEventLoopGroup类型,否则NioEventLoopGroup类型;
2.1.2)创建processor线程池实例
默认线程数为8,用的是jdk的newFixedThreadPool线程池;
2.1.3)注册缺省协议处理器
创建一个Pair实例,没有requestCode对应,此时称之为缺省协议处理器;这里缺省协议处理器用的是DefaultRequestProcessor实例,对应的线程池是业务线程池remotingExecutor;所有的处理器都继承了NettyRequestProcessor和AsyncNettyRequestProcessor;这里注册默认协议处理器,并没有放进processorTable中,即并没有requestCode与defaultRequestProcessor对应;
2.1.4)定时检测broker存活状态
向scheduledExecutorService线程池中提交任务,用于检测broker的存活状态,延迟5秒,每隔10秒执行RouteInfoManager#scanNotActiveBroker方法;将idle状态的broker移除,即遍历brokerLiveTable表,若上一次心跳时间加120秒小于当前时间,则namesrv认为这个broker节点已经宕机了,则将当前broker节点从brokerLiveTable表中移走,并且执行onChannelDestroy方法,该方法中执行逻辑和路由删除中很像;调度线程池是NamesrvController类的实例变量,在创建NamesrvController实例时,就已经初始化并赋值了,用的是jdk的newSingleThreadScheduledExecutor线程池;

2.2)jvm hook平滑关机逻辑

当jvm被关闭时,让jvm主动调用controller.shutdown方法,让服务器平滑关机;

2.3)controller.start方法中主要执行NettyRemotingServer#start方法

该方法大多数情况下有且只会执行remotingServer.start方法;主要是配置服务端netty启动对象serverBootstrap ;
2.3.1)配置并启动服务端netty
2.3.1.1)创建handler处理事件的线程池
当向channel pipeline添加handler时,指定group时,网络事件传播到当前handler时,事件处理由分配给handler的线程处理;此处用的是netty的DefaultEventExecutorGroup线程池,线程数默认8个,在真正执行业务逻辑之前需要进行SSL验证、编解码、空闲检查、网络连接管理,这些工作交给defaultEventExecutorGroup去做。

2.3.1.2)实例化共享的handler
执行prepareSharableHandles方法,目的是实例化共享的handler,包括HandshakeHandler实例,NettyEncoder实例,NettyServerHandler实例,以及NettyConnectManageHandler实例,NettyConnectManageHandler实例作用是监听当前channel的连接状态,如果连接状态变成关闭(CLOSE),异常(EXCEPTION),空闲(IDLE)等等,则它会将这种状态封装成一个事件即NettyEvent实例,再将其加入到NettyEventExecutor的一个队列eventQueue 中去,NettyEventExecutor的run方法会从eventQueue中取出NettyEvent实例,判断是哪一种状态,再执行BrokeHousekeepingService#onChannelxxxx方法,修改路由信息;
2.3.1.3)配置服务端boss,worker线程与channel
如果当前平台是linux,则boss,worker线程组均使用EpollEventLoopGroup,线程数量分别为1和3,显然这是一主多从模式,channel则使用EpollServerSockerChannel类型,否则boss,worker线程组均使用NioEventLoopGroup,channel则使用NioServerSocketChannel类型,两者都支持epoll,只不过前者做了优化;优化详情见

边缘触发像是收外卖,外卖来了,用户不在家,就给放门口了,外卖员去送下一单了;
水平触发像是收快递,快递到了站点,用户收到短信提示后,今天没去取,明天又会收到短信提示;
显然外卖员效率高,而快递站点会重复发短信提示导致送货效率低;

2.3.1.4)配置服务端与客户端的channel选项以及服务器端口
服务端和客户端channel选项分别调用option方法和childOption方法;设置服务器端口为9876;
2.3.1.5)向客户端channel pipeline中添加ChannelInitializer实例
调用childHandler方法向客户端pipeline添加ChannelInitializer实例,其initChannel方法包括如下逻辑:
依次添加handShakeHandler, encoder,NettyDecoder实例,IdleStateHandler实例,connectionManagerHandler,serverHandler;
a)添加DefaultEventExecutorGroup类型的线程池;
b)添加HandShakeHandler实例,数据入管道时处理,SSL,与数据证书相关;
c)添加NettyEncoder实例,数据出管道时处理;
d)添加NettyDecoder实例,数据入管道时处理;
e)添加IdleStateHandler实例,数据出入管道时使用,监听通道空闲情况,通道空闲达到超时时间,则关闭当前通道;
f)添加NettyConnectManageHandler实例,数据出入管道时使用,当通道出现关闭或异常等情况时,会有产生相应事件,并且交给单线程线程池nettyEventExecutor处理,其中会交给ChannelEventListener实例处理,BrokerHousekeepingService和ClientHousekeepingService均继承了ChannelEventListener类,其中BrokerHousekeepingService实例用于监听broker到nameserver的channel状态,处理channel发生连接,关闭,异常,空闲的事件,如关闭事件就是检查当前broker在nameserver上是否还有存活的节点,若没有则将broker上的元数据清掉,目的是更新nameserver上的元数据信息;
e)添加NettyServerHandler实例,入管道时处理;是最后一个自定义的处理器,业务入口的handler,执行的是NettyServerHandler#channelRead0方法,其中会执行NettyRemotingAbstact#processMessageReceived方法,判断入管道的数据是REQUEST_COMMAND还是RESPONSE_COMMAND类型;
2.3.1.6)配置childHandler的childOption使用netty内存池
serverPooledByteBufAllocatorEnable值默认为true,则设置serverBootstrap的客户端channel选项,使用内存池;
2.3.1.7)绑定端口并且启动
severBootstrap.bind方法,这里nameSrv作为服务端,所以需要绑定地址,但客户端即生产者,消费者配置netty时则不需要执行bind操作;
2.3.2)是否启动特殊事件线程
如果channelEventListener不为null,则执行nettyEventExecutor.start方法,启动网络异常事件处理器,包括连接,关闭,异常,空闲四大事件;
2.3.3)处理responseTable中超时的responseFuture
延迟3秒,每隔1秒,执行NettyRemotingAbstract#scanResponseTable方法,扫描responseTable表,将过期的responseFuture移除,如果是存在invokeCallback,则执行其callback方法;
服务端处理客户端的响应或请求
NettyRemotingServer的内部类NettyServerHandler#channelRead0方法该类负责服务端接收客户端的响应或者服务端接收客户端的请求;
服务器给客户端发送请求,此时客户端接收到的请求类型是REQUEST_COMMAND;此时若客户端继续响应请求给服务器,此时服务器接收到的请求类型是RESPONSE_COMMAND,此时如果当时服务端是异步发送则执行本地回调,如果当时服务端是同步发送,则执行countDownLatch.countDown方法以及semaphore.release方法,分别唤醒同步发送时等待的线程,以及释放信号量;
具体代码是在NettyRemotingAbstract#processResponseCommand方法中,会再次拿到opaque,接着根据opaque从responseTable中取出ResponseFuture实例responseFuture,若存在,则将响应回来的RemotingCommand实例设置进responseFuture中,并且将当前键值对opaque和responseFuture从responseTable中移除,判断若ResponseFuture实例中的invokeCallback不为null,不为null意味着是服务器向客户端发送的是异步请求,则回调callback方法;若invokeCallback为null,则直接执行countDownLatch.countDown方法,此时服务端发送线程被唤醒,继续后续操作;注意响应和请求不同;
当客户端向服务端发送请求时,此时服务端接收到的请求类型是REQUEST_COMMAND,此时io线程会将任务交给业务线程,处理内容如下
1)选择合适的协议处理器,执行processRequest方法,得到RemotingCommand类型的response,接着调用RemotingResponseCallback#callback方法;
2)实现RemotingResponseCallback接口,重写callback方法,判断若response不为null,则调用writeAndFlush方法,将其写进socket写缓冲区,准备给客户端发送响应;
在NettyRemotingAbstract类有两个子类,NettyRemotingServer和NettyRemotingClient;当服务端收到客户端主动发送的请求后,通过netty会到NettyRemotingServer.NettyServerHandler#channelRead0方法中,其中会执行processMessageReceived方法,该方法中会判断请求类型是REQUEST_COMMAND,此时会执行NettyRemotingAbstract#processRequestCommand方法;

创建Runnable封装数据处理逻辑
该方法中首先会创建Runnable对象,并封装了处理数据逻辑,逻辑中包括先执行processRequest业务处理方法以及随后回调callback方法的逻辑;callback方法中主要是判断若业务处理后返回的response不为null,则设置opaque和响应类型,执行channel的writeAndFlush方法,即将数据交给netty的io线程完成数据的写和发送,若客户端发送用的是单向模式或者response为null,则啥也不做;
创建并提交RequestTask实例
该实例封装了上述Runnable实例,并且将RequestTask实例提交至处理器对应的线程池中运行,此步即从io线程提交任务至业务线程;
路由注册
在NettyRemotingAbstract#processRequestCommand中找到DefaultRequestProcessor#processRequest方法,该方法中找到RequestCode.REGISTER_BROKER分支, 该分支处理路由注册请求;
1)创建RemotingCommand实例response
2)checksum
将requestHeader中拿到的crc32与重新根据消息体计算的crc32进行比较,若不等,则说明消息传送出现了问题,直接返回false;
3)RouteInfoManager#registerBroker方法
路由删除
在broker关闭前一刻,回向namesrv发送rpc请求,requestCode为UNREGISTER_BROKER;namesrv收到请求后,会移除掉当前broker的元数据信息;包括:当前broker节点的存活信息,过滤服务的信息;由于一个brokerName可以对应多个节点,即主从节点,只有当前brokerName下的所有节点都被移走之后,当前brokerName才可以从集群中移走,若当前集群下已无任何brokerName,则将当前集群也从集群列表中移走;最后移除当前broker下挂载的主题信息,即遍历topicQueueData,其key为topic,value为topic对应的队列分布信息,由一个topic可以拿到List,若某个QueueData实例的brokerName和当前下线的brokerName相等,则将该条数据从List实例中移走,若此时该List也为null,则将当前topic也从元数据信息表中移走;
查询路由
requestCode为GET_ROUTEINFO_BY_TOPIC