《RocketMQ实战与原理解析》总结
1.消息队列的作用
解耦:服务之间使用MQ做中介进行通信,服务间不直接调用,互不影响
异步:通过消息实现业务的异步
削峰:MQ本身相当于一个buffer,能够用来平衡生产者和消费者之间的速率
至于其他的延迟消息、事务消息、顺序消息是具体产品的附加功能,不是MQ本身的主要功能
2.阿里MQ中间件发展历史
2007年Notify,推模型,解决了事务消息
2010年Napoli
2011年MetaQ,拉模型,解决了顺序消息和海量堆积
2012年RocketMQ,长轮询的拉模型
2016年RocketMQ开源
2017年双十一RocketMQ承担万亿级消息流转
3.MQ部署和运维命令:https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md
4.控制台部署:https://github.com/apache/rocketmq-dashboard
5.名词解释
Producer:消息生产者
Consumer:消息消费者
Consumer Group:一类消费者的统称,注册时以消费者组为单位
Broker:MQ本身服务,支持主从
Topic:生产者生产消息和消费者消费消息的媒介,发送消息是投到指定的topic,消费消息也是消费指定topic中的消息
Message Queue:Message Queue是Topic的数据分片,每个topic有多个Message Queue,Topic中的数据会分散在内部的Message Queue中
Offset:指某条消息在Message Queue中的具体位置,通过Offset的值能够在Message Queue里定位到这条消息
Tag:同一个Topic的消息可以根据Tag进行再次区分
NameServer:阿里自己实现的轻量级注册中心,功能上类似eureka,提供topic和broker的路由服务
6.消费者类型
DefaultMQPushConsumer:推模式客户端,由系统控制拉取操作,之所以推模式还是用拉的形式是因为RocketMQ用的是拉模型,所谓的推模式只是系统屏蔽了拉取的过程,看起来像是推一样
DefaultMQPullConsumer:拉模式客户端,由用户自己控制拉取操作
7.消息模式
Clustering:集群模式,当一个topic对应多个消费者时,消息会分摊到每个消费者上
Broadcasting:广播模式,当一个topic对应多个消费者时,消息会全量的发送到每个消费者上
8.获取消息的模式
拉模式:客户端主动向broker请求拉取信息,优点是控制权在客户端,可以根据自身运行情况拉取,缺点是拉取间隔不好设定,长了短了都会有问题,同时拉取的代码也需要客户端自己编写
推模式:broker通过长连接主动向客户端推消息,优点是实时性高,且推送逻辑由broker完成,客户端编码简单。缺点是broker负担重,且broker不知道客户端的状态,如果客户端处理速度跟不上会导致消息堆积在客户端
长轮询拉模式:客户端不断的向broker请求拉取信息,如果有消息则即时返回,没有消息则由broker阻塞住该请求或者一定超时时间后才返回。这样就相当于将推和拉进行了一个结合,不仅免去了拉和推模式的各自缺点,同时拥有了各自的优点。RocketMQ使用的是这种方式
9.消费者流量控制
将MessageQueue中还未处理的消息保存成ProcessQueue对象,ProcessQueue内部持有一个TreeMap,key是队列中的位置,value是消息本身
从ProcessQueue中就可以获取到MessageQueue此时的状态,然后和设置的阈值做比较
如果currentQueueCount即当前队列中未处理的消息的总数量大于pullThresholdForTopic时,延迟PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL毫秒再试
如果cachedMessageSizeInMiB即当前队列中未处理的消息的总大小大于pullThresholdForQueue时,延迟PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL毫秒再试
如果当前队列中未处理的消息的偏移量跨度大于consumeMaxSpan时,延迟PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL毫秒再试
pullThresholdForTopic默认1000,pullThresholdForQueue默认100MB,consumeMaxSpan默认2000,PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL默认50ms
10.消息发送结果
SEND_OK:发送成功
FLUSH_DISK_TIMEOUT:没有在规定时间内完成刷盘,此错误只会在broker被设置成同步刷盘时才会出现
FLUSH_SLAVE_TIMEOUT:没有在规定时间内完成主从同步,此错误只会在主从模式下出现
SLAVE_NOT_AVAILABLE:在尝试进行主从同步时找不到从节点
11.延迟消息
发送延迟消息时需要预先设置messageDelayLevel,默认值1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,构造消息时设置delayLevel为对应时间的下标即可
创建延迟消息后会存入专门的以SCHEDULE_TOPIC开头的topic中,根据延迟级别放入对应的queue中
12.指定消息发送到具体的Message Queue
实现MessageQueueSelector接口的select方法,然后在发送消息时把这个selector传进去
13.事务消息
2PC
14.offset存储
对于集群模式,所有消费者的消费进度是共享的,所以进度由broker进行存储
对于广播模式,所有消费者的消费进度是独立的,所以进度由消费者本地存储
15.nameserver清理失效broker的逻辑
长连接断掉时,在channel的回调里移除broker的缓存
每隔10秒检查一次broker上次上报的时间,如果超过两分钟就把broker移掉
16.为什么不用zk而是自己写了个注册中心
zk太重不容易控制,NameServer一共才十个类,逻辑简单更稳定
17.数据存储方式
每个topic下的每一个Message Queue都有一个ConsumeQueue文件,这个文件是消息的索引,文件内容是消息的物理存储地址,一条索引是20字节,默认一个文件存储30万个索引,所以默认文件大小是5.7M
每个broker下有CommitLog文件,文件内容是Consume Queues、Message Key、Tag等所有信息,即使ConsumeQueue丢失,也可以通过commitLog完全恢复出来,默认大小1G,写满了就新增一个,文件名为偏移量
18.broker的高可用
broker通过主从架构实现高可用,主节点能读和写,从节点只能读
消息者不需要指定从主节点读还是从从节点读,当master不可用或者繁忙的时候会自动切换到从节点读
4.5.0版本开始,RocketMQ支持Dledger模式,使用Raft算法进行选举,能够实现主从切换
19.刷盘方式
同步刷盘:消息内容写入PageCache后立刻进行刷盘,刷盘成功后返回成功
异步刷盘:消息内容写入PageCache后就返回成功,当PageCache堆积到一定程度时再统一刷盘
20.主从同步方式
同步复制:主从必须都写成功才返回给客户端成功
异步复制:主节点写入成功就返回成功,后续再异步复制到从节点
21.同步设置的最佳实践
异步刷盘 + 同步复制
22.顺序消息
全局顺序消息:即全局所有消息均有顺序,topic中只有一个Message Queue,消费者和生产者也不能并发
局部顺序消息:即一组消息有顺序,这个功能需要生产者和消费者配合,生产者实现MessageQueueSelector确保同一组消息放入同一个Message Queue中,消费者注册listener的时候需要实现MessageListenerOrderly保证同一个Message Queue中的消息不会被并发消费,MessageListenerOrderly内部对每一个Queue加了一把锁
23.消息重复消费的解决方案
维护一张本地消息已消费集合,把已经消费过的消息维护起来,消费前先查一下
让消费逻辑本身幂等
24.数据可靠性
Broker正常启动和关闭:不会有消息丢失
Broker异常关闭或操作系统异常关闭或机器断电:设置成同步刷盘不会有消息丢失
磁盘等硬件损坏:主从之间设置同步复制不会有消息丢失
25.消息优先级
情景假设,有A,B,C三种消息在同一个Topic中,A,B的量特别大,导致C消费不到,现在想要消费机会均等或者C先消费
第一种方案,按Topic拆分
第二种方案,按Message Queue拆分,然后消费者用拉的方式从Queue中取消息,这样就可以由消费者自主决定消费优先级
26.增大消费者吞吐量的方案
使用Tag/Filter Server 在broker端就进行消息过滤,避免消费者接收到无用的消息
增加消费者线程数
设置消费者批量拉取消息,而不是一次拉一条
发生堆积时,若业务允许则跳过堆积的消息
27.消费者负载均衡的方案
情景假设,6条消息分别为1,2,3,4,5,6,然后有2个消费者,分别为消费者1和消费者2
平均分配策略(默认)(AllocateMessageQueueAveragely):消费者1消费消息1,2,3,消费者2消费消息4,5,6
环形分配策略(AllocateMessageQueueAveragelyByCircle):消费者1消费消息1,3,5,消费者2消费消息2,4,6
手动配置分配策略(AllocateMessageQueueByConfig):该策略内部有个setMessageQueueList方法,可以指定消费哪个Message Queue
机房分配策略(AllocateMessageQueueByMachineRoom):多机房时,只负载指定机房的Message Queue,要求broker名字为“机房名@brokerName”,设置方式为setConsumeridcs方法
一致性哈希分配策略(AllocateMessageQueueConsistentHash):按Consumer的名字做hash,遵循一致性hash的算法进行消费的分发
详细解释:https://blog.youkuaiyun.com/qq_38082304/article/details/112378245
28.增大生产者吞吐量的方案
可靠性要求不高的场景可以使用OneWay发送
增大Producer数量,并发发送。RocketMQ使用并发窗口解决了并发写降低写入效率的问题
阿里内部的RocketMQ经过调优写入可达90万+TPS
Linux的文件日志系统使用EXT4,EXT4创建/删除文件的性能比EXT3及其他文件系统要好,适用于RocketMQ这种频繁创建删除的服务
Linux的IO调度算法使用deadline,通过超时队列提高吞吐量
29.调优流程
逐步增大请求量
等待QPS到达峰值
维持峰值观察硬件情况,top看cpu和内存,sar看网卡
30.Nameserver工作流程
入口NamesrvStartup的主函数,解析命令行参数,初始化NamesrvController
NamesrvController开8个线程用于处理命令、1个定时任务扫描失效的broker、1个定时任务打印配置信息,注册DefaultRequestProcessor为请求处理器
DefaultRequestProcessor内部用一个switch case做分发,通过判断request命令中的code调用不同的方法完成命令的处理,code维护在RequestCode里
RouteInfoManager是维护集群元数据信息的类,里面有各种各样的map用来缓存broker、topic、queue的映射关系,通过可重入的读写锁控制线程安全