SocketMQ NameServer注册中心
客户端通过注册中心知道所有的服务实例,这就需要注册中心提供服务注册、服务剔除、服务发现这些基本功能。
NameServer
NameServer的特点是简单、无状态、可横向扩展、节点间互不通信,相对于kafka来说,kafka使用Zookeeper来Master选举、分布式锁等功能保证强一致性,RocketMQ使用的NameServer达到的是最终一致性。根据CAP理论,Zookeeper是CP,NameServer是AP:
一致性(Consistency):NameServer节点间互不通信,意味着某一时刻,不同节点可能存在数据不一致
可用性(Availability):只要不是NameServer都挂掉,就可以对外提供服务
分区容错性(Partiton Tolerance):分布式架构跨网络的情况下,部分网络不可达是不可避免的,只要做好跨机房容灾,就可以达到分区容错性
最终一致性
前面说到RocketMQ使用的NameServer达到的是最终一致性,这个是怎么做到的呢?从路由注册、路由剔除、路由发现三个角度来说。
- 路由注册
像Zookeeper这样的强一致性组件,数据只要写到了主节点,内部就会通过状态机将数据复制到其他节点,但是NameServer节点间无状态且互不通信的,RockerMQ采取的策略是Broker轮询NameServer列表,与每个NameServer节点建立长链接发起注册请求,每30s发送一个心跳包,将自己的最新信息上报给NameServer,包含BrockerId、Brocker名称、Broker地址、Broker所属集群名称等等,然后NameServer收到心跳包之后,会更新所维护的Broker路由表,记录Broker最新存活时间,而且这个Broker路由表的操作引入了ReadWriteLock,允许并发读
路由剔除
- 正常情况下,Broker关闭时,会与NameSerer断开长连接,此时NameServer会将这个Broker的信息剔除掉
- 异常情况下,NameServer有一个定时任务,每10s扫一遍Broker表,如果Broker最新的存活时间已经距当前超过120s,则将Broker移除
- 路由发现
路由发现是客户端的行为,包括生产者和消费者都需要依赖NamerServer进行路由发现
对于生产者来说,因为可以发送任意Topic的消息,所以只有在发送消息的时候,才会根据Topic从NameServer获取路由消息
对于消费者来说,一般是固定Topic,所以启动的时候就会从NameServer获取路由消息
从路由注册、路由剔除的环节可以看出来,NameServer并不会主动推送路由到客户端,所以需要客户端自己拉取,RocketMQ客户端提供了定时拉取机制,默认是每隔30s会从NameServer拉取一次,也就意味着,在这30s内,客户端无法感知到Broker的宕机,依然会向宕机的Broker发送或者消费消息,那这个问题是怎么解决的呢?
靠的是客户端重试机制
生产者重试机制
首先我们要知道消息按有序性有3种类型:普通消息、普通有序消息、严格有序消息
普通消息
消息是无序的,发送到任意队列都可以
普通有序消息
消息发送的时候会动态选择队列,正常情况下同一类消息指定一个队列,异常情况下发送到其他队列
严格有序消息
消息必须发送到同一个队列,一旦发生异常情况,也不允许发送到其他队列
当发送的是普通消息时,一旦发送失败,会自动重试选择其他Broker下的Queue,而普通有序消息,发送失败后,RocketMQ不会自动进行重试,如果需要重试需要在业务代码上编写重试,但是严格有序消息,因为不会更换MessageQueue,所以重试也不会成功,直到宕机的Broker恢复。
即使是普通消息的自动重试,也存在一个问题,即每条消息都会先失败然后重新选择其他的Broker,必然每次消息发送的耗时都会增加,所以RocketMQ提供了以下两个功能:
- 失败隔离:如果消息往某个Broker发送失败了,则后续的消息发送都不选择该Broker
- 超时隔离:如果消息往某个Broker发生了超时,则按照超时时间来制定一定时间范围内,后续的消息不选择该Broker