RocketMQ角色介绍

Broker集群:

  接收生产者发送的消息和消费者消费的请求。Master可读可写,Slave只读。

  每个Broker节点,启动时遍历NameServer列表,与每个NameServer建立长连接,注册自己的信息,定时上报

Producer集群:

  通过NameServer集群获得Topic的路由信息,包括Topic下的Queue,以及Queue分布在哪些Broker上。Producer只需要跟Broker集群的主节点建立连接,因为只将消息发送给Master

Consumer集群:

  通过NameServer集群获得Topic的路由信息,连接到对应的Broker上,主从节点都会连接。   

三者的关系:https://blog.csdn.net/linyaogai/article/details/77876078

一、NameServer

  功能:管理Broker,相当于注册中心,对外提供路由注册,路由剔除和路由发现,但NameServer内部节点之间不通信。RocketMQ早期版本参考Kafka使用Zookeeper中间件作为NameServer,它保证了强一致性。RocketMQ的设计只需要保证最终一致性,为了维护成本减少依赖,自己开发NameServer。

  1.1 节点互不通信,如何保证最终一致性

  1.1.1 路由注册

  Broker节点启动时,遍历NameServer列表,对每个都建立长连接,并进行初始注册。每个NameServer维护一个Broker表,存储Broker信息。

  每隔30秒,Broker节点像NameServer集群发送心跳(包含BrokerId,地址,名称,以及所属集群名称),NameServer收到后更新存活时间戳,存在并发修改同一个表的问题,加入了读写锁,消费者可以共享读取配置信息,但NameServer写入时只能串行写,并会阻塞读。

  1.1.2 路由剔除

  正常情况,Broker关闭与NameServer断开连接,关闭监听器监测到断开会将这个Broker信息剔除。

  异常情况,NameServer有一个定时任务轮询每个Broker的心跳同步时间,如果距离上次同步时间过长,则判定失效并移除Broker

  1.1.3 路由发现

  正常情况,生产者在发送陌生Topic之前会从NameServer获取路由信息。而消费者在启动时从NameServer拉取。

  当NameServer中的路由信息发生变化,并不会主动通知客户端(发送者和接收者),需要客户端拉取。

    1.1.3.1 拉取策略

    生产者和消费者实例底层依赖于MQClientInstance类,其中updateTopicRouteInfoFromNameServer()方法,每30秒执行一次

    1. 从生产者和消费者中获取所有Topic

    2. 对每个Topic执行mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3),获取NameServer新的路由信息

    3. 判断本地TopicRouteTable中的路由信息和新的路由信息是否一致,若一致还需要再判断topic在本地是否存在,不存在也需要更新

    updateTopicRouteInfoFromNameServer()这个方法有三个地方引用:

    1. 首次使用Topic,本地topic信息不存在,需要去注册中心获取

    2. topic不存在,走默认方法,isDefault=true,defaultMQProducer!=null。获取默认Topic(TBM102)下的Broker等等

    3. 定时任务调用  

    源码分析:https://segmentfault.com/a/1190000021175879,https://zhuanlan.zhihu.com/p/299795679

  1.1.4 客户端NameServer选择策略

  RocketMQ将用户设置的NameServer列表设置到NettyRemotingClient类的namesrvAddrList字段中。

  每个客户端只选择一个注册中心连接,具体使用哪个注册中心,使用轮询策略。但第一次选取的结果,下次优先选取。只连接一个是为了减轻注册中心集群的压力。如果连接失败回自动重试其他节点,参见NettyRemotingClient#getAndCreateNameserverChannel。

  1.1.5 特点

  稳定性高。集群中注册中心彼此独立,一个挂掉不会影响其他。没有频繁的读写,性能开销很小。

 

二、Broker

  功能:暂存和传输消息

  消息存储是核心,提供了消息的接收、存储和拉取功能。一般会使用主从,主写读,从只读。主节点和从节点BrokerName相同,BrokerId为0是主节点,非0是从节点。

  每30秒向注册中心发送心跳

 

三、Producer

  功能:发送消息

  生产者与注册中心保持长连接,将路由信息缓存到本地,每30秒向注册中心拉取新的路由信息

  生产者和Broker保持长连接,每30秒向Broker发送心跳,检测broker是否活跃

 

四、Consumer

  功能:接收消息并消费

  消费者和注册中心保持长连接,将路由信息缓存到本地,每30秒向注册中心拉取新的路由信息

  消费者和Broker保持长连接,每30秒向Broker发送心跳,检测broker是否活跃  

五、Topic

   区分消息的种类,一个发送者可以发送多个topic、一个消费者也可以消费多个topic

六、Quene

  对于同一个消费者组,一个分区只支持一个消费者消费。分区过少,容易造成消息积压。在生产环境中,一个Topic会设置成多个分区(默认16),支持多个消费者。

  

  (网图:https://blog.csdn.net/qq_33709508/article/details/107937475)

  一个Topic可以分布在各个Broker上,每个Broker上的Topic可以看成是一个Topic分片,而Queue是负载均衡调度的最小单位。

  RocketMQ的负载均衡策略规定,Consumer数量应该小于等于Queue的数量,多余的Consumer不能消费消息。在同一个消费者组中,Queue和消费者是一对多的关系,一个消费者可以消费多个Queue,但是一个Queue只能由一个消费者消费,保证了消费的过程中,不会产生竞争,提高处理速度。

  同一个Queue对同一个消费者组内的消费者是独享的。

  同一个Queue对不同的消费者组是共享的。

七、Producer Group

  一个生产者组,生产相同的topic。

八、Consumer Group

  一个消费者组,消费相同的topic,tag。

九、Message  

public class Message implements Serializable {
    private static final long serialVersionUID = 8445773977080406428L;
    private String topic;
    private int flag;
    private Map<String, String> properties;
    private byte[] body;
    private String transactionId;
    //....      
    

}

  topic:消息的主题

  flag:RocketMQ不做处理,由应用设置

  properties:消息属性,存储元数据,比如tag标签用于过滤,keys用作幂等

  body:消息内容,一个字节数组,序列化方式由应用决定。

  transactionId:事务id,在事务消息中用到

十、Tag

  消息的二级类型,比如订单创建消息,订单完成消息

十一、Offset

  offset指某个Topic下的某条消息在某个Message Queue中的位置。

  集群模式消费:由Broker端存储和控制Offset的值,使用RemoteBrokerOffsetStore结构。

{
    "offsetTable": {
        "test-topic@test-group": {
            "0": 88526, 
            "1": 88528
        }
    }
}

  consumer本地也会维护一份在offsetTable,在本地消费完成之后,会通过定时任务同步给broker。

  广播模式消费:每个消费者内部存储,使用LocalfileOffsetStore

  消费端的offset机制

    1. offset初始化

    消费者启动时,会初始化加载offset(OffsetStore#load),广播模式下从本地,集群模式没有实现这个方法。在rebalance对messageQueue分配完成之后会更新offset。

      1. 首先删除本地旧的offset信息(OffsetStore#removeOffset,从offsetTable删除该队列)

      2. 请求broker获取最新的offset,这里分成三种模式(enum ConsumeFromWhere):从最新offset开始消费、从第一个offset开始消费、从指定时间点开始消费。这三种都会获取最新offset,如果不小于0,则从该位置继续消费。

    2. offset提交更新

    消费者从broker拉去消息后,将消息的扩展信息存放到ProcessQueue的TreeMap<Long, MessageExt> msgTreeMap属性中,key为消息在queue中的offset。并发消费模式下,拉取得消息会分发给消费线程。消费成功,将消费完成的消息从msgTreeMap中移除,继续从msgTreeMap中取出第一条消息(offset最小的消息,TreeMap是排序了的),将其offset存入本地的offsetTable。将offset同步回服务器是定时任务处理的。MQClientInstance.start()会启动客户端相关定时任务。

    同步消费进度的定时任务:

    DefaultMQPushConsumerImpl#persistConsumerOffset方法,底层是RemoteBrokerOffsetStore#persistAll

    会将当前offsetTable存储的每个队列的offset同步到broker。默认5秒一次version-4.3.2版本

    3. 并发消费时offset的更新

    消费者一次拉取多条消息,譬如offset从1到9,因为是并发消费的,2到9已经消费了,1还没有消费。从msgTreeMap中删除已消费消息,返回的最小offset是1,更新到offsetTable中,当前队列的消费进度依然是1,即使同步broker,broker存放的也依然是1(见2)

    下次拉取时,使用的是上一次拉取返回的nextBeginOffset也就是11,并不是本地offsetTable中存储的值。所以正常情况下并不会重复拉取。但是如果消费者此时宕机了,内存保存的nextBeginOffset丢失了,重新请求broker,拿到1到9,就会重复消费2到9 

    offset管理,请见:https://www.jianshu.com/p/b4970f59a8b1

十二、网络模型  

  RocketMQ对Broker的线程池进行了隔离,消息的生产、消费、客户端心跳、客户端注册等请求互不干扰。

 

posted @ 2021-03-25 14:01  walker993  阅读(115)  评论(0编辑  收藏  举报