整体框架图
路由中心NameServer
基本原理
Broker消息服务器在启动的时候向所有NameServer注册,消息生产者在发送消息之前先从NameServer获取Broker服务器地址列表,然后 根据负载均衡算法从列表中选择一台消息服务器进行消息发送,如果检测到Broker宕机,则从路由注册表中将其移除,但是路由变化不会马上通知消息生产者。
NameServer本身的高可用可通过部署多台NameServer服务器来实现,但彼此互不通信,也就是NameServer服务器之间在某一时刻的数据并不会完全相同 ,但并不影响消息发送。
作用:为消息生产者和消息消费者提供Topic的路由信息。因此需要存储路由的信息和管理broker节点的注册和删除
实现原理
路由注册:
Broker每30s会向NameServer发送心跳包,NameServer收到心跳包后会更新缓存
若连续120s没有收到心跳包,就会移除Broker的路由信息同时关闭Socket连接。
发送消息
验证消息:
确保生产者可用,验证消息是否规范
查找路由:
获取发送到的broker节点,先查生产者中有没有缓存topic的路由信息,如果没有就去nameServer中查。
选择消息队列:
不启用故障延迟:
沿着队列轮流往下选,这次是往队列1发,下次就是往队列2发。如果发送异常了,重试的时候就剔除掉该broker下面的所有队列,尝试另一个broker下面的队列。
这样会带来一个问题,发送消息的时候,我们发现broker-a故障了,重发消息的时候临时剔除了broker-a。但是这个并没有记录下来,发送下一条消息的时候,尝试下个队列,其实有可能还是broker-a下面的队列,这样还是会发送失败一次。
启用故障延迟:
对于高延迟的、有故障的broker,都会保存下来,并有一个不可用时间段,发送消息的时候都会临时避开这个broker。过了这个时间段,这个broker才可以重新参与选择。判断broker是否可用,就是判断当前时间是否已经超过了那个不可用时间点
消息发送:
根据messageQueue获取broker的网络地址,未缓存就去nameServer里拉
为消息分配全局唯一ID,并给消息打一个标记(压缩标记、事务标记等)
构建消息发送请求包,包含各种元数据
根据消息发送方式,同步、异步、单项方式发消息
消息存储
原理图
简单流程
1.校验:broker状态异常或消息长度过长
2.延迟处理:延迟级别大于0 放入延迟消息主题
3.获取commitlog文件
4.申请putMessageLock,单机串行写入commitLog
5.设置消息的存储时间,如果没有commit文件,创建一个
6.将消息写入文件,获取消息唯一ID,储存偏移量
7.将消息与写入到文件对应的内存映射中,等待刷写入磁盘
8.数据刷入磁盘
存储文件
Commitlog 文件:消息主体
ConsumeQueue 文件 :消息队列,即关于消息消费的索引文件 ,根据时间戳,使用二分查找。提高主题与消息队列检索消息的速度
Index 索引文件:hash索引机制建立消息索引
文件恢复
存储文件加载时会进行纠错,然后根据broker是否正常停止 来执行不同的恢复策略
恢复 正常:从倒数第三个往前遍历直到找到正确的
非正常:从最后开始遍历
文件冗余需要销毁
主从同步
1.主服务器启动,开始监听从服务器的连接
2.从服务器连接主服务器,主服务器接收,建立TCP连接
3.从服务器从主服务器拉取消息偏移量
4.从服务器持续发送同步请求
读写分离
首先,消息消费者在向 Broker 发送消息拉取请求时,会根据筛选出来的消息队列,判定是从Master,还是从Slave拉取消息,默认是Master。
Broker 接收到消息消费者拉取请求,在获取本地堆积的消息量后,会计算服务器的消息堆积量是否大于物理内存的一定值,如果是,则标记下次从 Slave服务器拉取,计算 Slave服务器的 Broker Id,并响应给消费者。
消费者在接收到 Broker的响应后,会把消息队列与建议下一次拉取节点的 Broker Id 关联起来,并缓存在内存中,以便下次拉取消息时,确定从哪个节点发送请求。
消息消费
整体流程
负载:
消息队列负载由Rebalance线程默认每隔20s进行一次消息队列负载,获取主题队列信息mqSet与消费组当前所有消费者cidAll,然后按照某一种负载算法进行队列分配,分配原则为同一个消费者可以分配多个消息消息队列,同一个消息消费队列同一时间只会分配给一个消费者。
拉取:
pullMessageService根据上面负载好的拉取任务进行拉取,默认一批拉取32条消息。
消费:
并发地对同一个消息消费队列进行消费。如果需要重试,将消息放入一个新的主题的固定名称格式的消息队列中。
定时:
不支持任意精度的定时消息。将定时消息放入与延迟级别对应的消息消费队列中,主题固定。到时后,将消息转发如相应的消息队列以便拉取
顺序消费:
串行消费消息,消费消息时必须锁定消费消息队列,broker端会检查锁占用情况
消息负载
分配算法
1.平均分配:
c1: q1,q2,q3
c2: q4,q5,q6
c3: q7,q8
1.平均轮询分配
c1: q1,q4,q7
c2: q2,q5,q8
c3: q3,q6
3. 一致性 hash。 不推荐使用
4.根据配置,为每一个消费者配置固定的消息队列
5.根据 Broker部署机房名,对每个消费者分配
消息拉取
1.客户端封装请求
从processQueue(消息的读取偏移量)获取消息内容
获取主题订阅消息
构建标记位
与服务端交互获取broker地址
异步向broker拉取消息
2.服务器查找并返回消息
根据订阅消息构建消息过滤器
获取消息消费队列
根据消息偏移量获取消息
3.客户端处理返回的消息
根据相应结果填充相应对象,方便解析
将偏移量存入客户端 将消息放入消费队列后提交给消费者线程
拉取轮询机制
rocketMQ是主动拉取,因此有长轮询模式
5s查看一次消息是否可达(是否连通)
1毫秒轮询一次
PullMessageService负责对消息队列进行消息拉取,从远端服务器拉取消息后将消息存入ProcessQueue消息队列处理队列中,然后调用ConsumeMessageService.submitConsumeRequest方法进行消息消费,使用线程池来消费消息,确保了消息拉取于消息消费的解偶
消息消费
1.对消息条数划分,超过最大32条会分页
2.有参数限制,如果该消息被重新分配,停止消费,消费前后都有校验
3.调用监听器 listener.consumeMessage()方法执行具体消费
4.成功消费返回ack
消费未返回ack,重试
确定配置了重试功能,即允许重试
创建重试主题,%RETRY%+消费组名称,创建重试topic
设置重试次数,超过次数会进入只写队列,不再重试,需要人工干预
一般5s重试一次
定时消息
rocketMQ的定时消息只支持特定时间的延迟消息,比如 1s 5s 10s 30s lm 2m 3m 4m 5m 6m 7m 8m 9m 这种
流程
生产者发送消息,如果是定时,放入专门的topic
每隔1s拉取消息
到时将消息放入原topic,持久化入磁盘等待消费
与Kafka的区别
转载自:https://blog.csdn.net/damacheng/article/details/42846549
数据可靠性
RocketMQ支持异步实时刷盘,同步刷盘,同步Replication,异步Replication
Kafka使用异步刷盘方式,异步Replication
总结:RocketMQ的同步刷盘在单机可靠性上比Kafka更高,不会因为操作系统Crash,导致数据丢失。 同时同步Replication也比Kafka异步Replication更可靠,数据完全无单点。另外Kafka的Replication以topic为单位,支持主机宕机,备机自动切换,但是这里有个问题,由于是异步Replication,那么切换后会有数据丢失,同时Leader如果重启后,会与已经存在的Leader产生数据冲突。开源版本的RocketMQ不支持Master宕机,Slave自动切换为Master,阿里云版本的RocketMQ支持自动切换特性。
性能对比
Kafka单机写入TPS约在百万条/秒,消息大小10个字节
RocketMQ单机写入TPS单实例约7万条/秒,单机部署3个Broker,可以跑到最高12万条/秒,消息大小10个字节
总结:Kafka的TPS跑到单机百万,主要是由于Producer端将多个小消息合并,批量发向Broker。
RocketMQ为什么没有这么做?
Producer通常使用Java语言,缓存过多消息,GC是个很严重的问题
Producer调用发送消息接口,消息未发送到Broker,向业务返回成功,此时Producer宕机,会导致消息丢失,业务出错
Producer通常为分布式系统,且每台机器都是多线程发送,我们认为线上的系统单个Producer每秒产生的数据量有限,不可能上万。
缓存的功能完全可以由上层业务完成。
单机支持的队列数
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
RocketMQ单机支持最高5万个队列,Load不会发生明显变化
队列多有什么好处?
单机可以创建更多Topic,因为每个Topic都是由一批队列组成
Consumer的集群规模和队列数成正比,队列越多,Consumer集群可以越大
消息投递实时性
Kafka使用短轮询方式,实时性取决于轮询间隔时间
RocketMQ使用长轮询,同Push方式实时性一致,消息的投递延时通常在几个毫秒。
消费失败重试
Kafka消费失败不支持重试
RocketMQ消费失败支持定时重试,每次重试间隔时间顺延
总结:例如充值类应用,当前时刻调用运营商网关,充值失败,可能是对方压力过多,稍后在调用就会成功,如支付宝到银行扣款也是类似需求。
这里的重试需要可靠的重试,即失败重试的消息不因为Consumer宕机导致丢失。
严格的消息顺序
Kafka支持消息顺序,但是一台Broker宕机后,就会产生消息乱序
RocketMQ支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序
Mysql Binlog分发需要严格的消息顺序
定时消息
Kafka不支持定时消息
RocketMQ支持两类定时消息
开源版本RocketMQ仅支持定时Level
阿里云ONS支持定时Level,以及指定的毫秒级别的延时时间
分布式事务消息
Kafka不支持分布式事务消息
阿里云ONS支持分布式定时消息,未来开源版本的RocketMQ也有计划支持分布式事务消息
消息查询
Kafka不支持消息查询
RocketMQ支持根据Message Id查询消息,也支持根据消息内容查询消息(发送消息时指定一个Message Key,任意字符串,例如指定为订单Id)
总结:消息查询对于定位消息丢失问题非常有帮助,例如某个订单处理失败,是消息没收到还是收到处理出错了。
消息回溯
Kafka理论上可以按照Offset来回溯消息
RocketMQ支持按照时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息
总结:典型业务场景如consumer做订单分析,但是由于程序逻辑或者依赖的系统发生故障等原因,导致今天消费的消息全部无效,需要重新从昨天零点开始消费,那么以时间为起点的消息重放功能对于业务非常有帮助。
消费并行度
Kafka的消费并行度依赖Topic配置的分区数,如分区数为10,那么最多10台机器来并行消费(每台机器只能开启一个线程),或者一台机器消费(10个线程并行消费)。即消费并行度和分区数一致。
RocketMQ消费并行度分两种情况
顺序消费方式并行度同Kafka完全一致
乱序方式并行度取决于Consumer的线程数,如Topic配置10个队列,10台机器消费,每台机器100个线程,那么并行度为1000。
消息轨迹
Kafka不支持消息轨迹
阿里云ONS支持消息轨迹
开发语言友好性
Kafka采用Scala编写
RocketMQ采用Java语言编写
Broker端消息过滤
Kafka不支持Broker端的消息过滤
RocketMQ支持两种Broker端消息过滤方式
根据Message Tag来过滤,相当于子topic概念
向服务器上传一段Java代码,可以对消息做任意形式的过滤,甚至可以做Message Body的过滤拆分。
消息堆积能力
理论上Kafka要比RocketMQ的堆积能力更强,不过RocketMQ单机也可以支持亿级的消息堆积能力,我们认为这个堆积能力已经完全可以满足业务需求。