一、看前提问:
- RocketMQ有哪些角色?
- 各角色的作用?
二、角色一览图:
三、NameServer:
管理broker,集群中的各个服务都需要通过NameServer来了解各个服务的状态
-
服务发现机制:
请求发出时客户端通过注册中心获取所有的服务实例,客户端接着负载均衡算法选择可用的服务实例中的一个发送。
2.使用NameServer的原因:
相比zk的强一致性,nameserver只需要保持最终一致性即可。NameServer如何保证最终数据一致性:
NameServer节点并不互相通信,但为保持最终数据一致性需要从路由注册、路由剔除、路由发现三个角度介绍:
路由注册:
对比zk强一致性,数据只需要写入主节点内部会通过状态机将数据复制到其他节点,ns节点之间无法通信,RocketMQ的策略是broker启动时轮训NameServer列表与每个NameServer节点建立长链接,发起注册请求。NameServer内部会维护一个Borker表用来动态存储Borker的信息。
Broker会每隔30s向NameServer发送心跳包,包括BrokerId、Broker地址、Broker名称、Broker所属集群名称、队列和brokerIP对应关系等,然后NameServer接收到⼼跳包后,会更新时间戳,记录这个Broker的最新存活时间。Nameserver在处理心跳包时因多个Broker同时操作同一张Borker表,路由注册操作引入了ReadWriteLock读写锁,这个设计亮点允许多个消息⽣产者并发读,保证了消息发送时的⾼并发,但是同⼀时刻NameServer只能处理⼀个Broker⼼跳包,多个⼼跳包串⾏处理。这也是读写锁的经典使⽤场景,即读多写少。
路由剔除:
正常情况:Broker关闭,断开与NameServer长链接,Netty通道关闭监听器会监听到断开事件,然后将Broker剔除。
异常情况:NameServer定时任务会每隔10s扫描一次Broker表将Broker最新时间戳与当前时间超过120s的也会进行剔除
特殊情况:通过命令行工具禁止这个Broker的写权限,发送消息到这个Broker都会收到NO_PERMISSION响应,也会被剔除。
路由发现:
路由发现是客户端行为 -
生产者:发送第一条消息根据topic从NameServer获取路由信息;
-
消费者:启动时拉取
RocketMQ也提供了定时拉取Topic最新路由信息的机制源码可以看:
DefaultMQProducer、DefaultMQConsumer都继承了MQClientInstance其中有个startScheduledTask,定时从NameServer获取最新的路由表,默认是30s
3.客户端NameServer选择策略:
客户端每次都会尝试从缓存的路由表获取Topic路由信息,找不到才会从NameServer更新。
选择策略:
RocketMQ会将用户设置的NameServer列表设置到NettyRemotingClient类的namesrvAddrList
字段中,如下:
private final AtomicReference<List<String>> namesrvAddrList = new
AtomicReference<List<String>>();
具体选择哪个NameServer使用round-robin策略,注意如果选择了NameServer节点后面也会优先选择这个节点除非发生异常情况,才会选择其它节点。主要是为了减少每个NameServer节点的压⼒,所以每
个客户端节点只随机与其中⼀个NameServer节点建⽴连接。为了保证NameServer集群每个节点的负载均衡,在round-robin策略时,每个客户端的初始位置都不同,如下:
# 其中initValueIndex就是计算一个随机值
private final AtomicInteger namesrvIndex = new AtomicInteger(initValueIndex());
之后每次选择NameServer时,namesrvIndex +1之后再对namesrvAddrList取模,计算在数据下标的位置,尝试创建连接,⼀旦创建成功,会将当前选择的NameServer地址记录到namesrvAddrChoosed字段中:
private final AtomicReference<String> namesrvAddrChoosed = new
AtomicReference<String>();
4.优点:
- nameserver 互相独⽴,彼此没有通信关系,单台 nameserver 挂掉,不影响其他
- nameserver 不会有频繁的读写,所以性能开销⾮常⼩,稳定性很⾼。
四、Broker:
提供消息的接受、存储、拉取等功能。
五、Producer:
生产者
六、Consumer:
消费者,Consumer会与NameServer建立长连接,每隔30s会从NameServer服务器查询topic路由信息,再根据ip映射文件从broker中消费消息并存入本地缓存。
七、Topic:
每一个topic代表一类消息,一个生产者可以发消息给一个或多个topic,一个消费者也可以订阅一个或多个topic。
八、Queue:
RokcetMQ都是磁盘消息队列模式,同一个消费组,一个分区只能由一个消费者来消费消息。
**Message Queue:**相当于是Topic的分区;⽤于并⾏发送和接收消息
RocketMQ 在进⾏Topic分⽚以后,已经达到⽔平扩展的⽬的了,为什么还需要进⼀步切分为Queue呢?
Queue 存在的意义: 消费负载均衡过程中资源分配的基本单元.
queue数量指定方式:
- 代码指定:producer.setDefaultTopicQueueNums(8);
- 配置⽂件指定:同时设置broker服务器的配置⽂件broker.properties:defaultTopicQueueNums=16
- mqadmin命令创建:updateTopic -r/w(读写队列个数建议相等) 个数
九、 Producer Group:
生产者组
十、Consumer Group:
消费者组
十一、Message:
代表一条消息,必须指定一个topic,用户在发送时可设置messagekey便于查询,Message还可选Tag设置也可以添加额外的键值对。
十二、Tag:
标签可以被认为是对 Topic 进⼀步细化,相当于topic的二级消息分类。
十三、Offset:
Offset是指某个 Topic下的⼀条消息在某个 Message Queue⾥的 位置,offset主要分为本地文件类型和Broker代存类型两种。
**集群消费模式:**由Broker端存储和控制offset(RemoteBrokerOffsetStore 结构)。
**广播消费模式:**由LocalfileOffsetStore,把 Offset存到本地
DefaultMQPushConsumer类中setConsumeFromWhere可以设置从哪儿开始消费(支持具体的offset和时间段)注意设置读取位置不是每次都有效,它的优先级默认在 Offset Store后面时读取不到Offset 的时候, ConsumeFromWhere 的设置才⽣效⼤部分情况下这个设置在 Consumer Group初次启动时有效。 如果 Consumer正常运⾏后被停⽌,然后再启动, 会接着上次的 Offset开始消费, ConsumeFromWhere 的设置无效。
RocketMQ的broker端中,offset的是以json的形式持久化到磁盘⽂件中,⽂件路径为
${user.home}/store/config/consumerOffset.json