Transliteration
Roducer fā xiāoxī, huì xiān lián
目录
RocketMQ事务
RocketMQ集群
集群特征
集群工作流程
消息的存储
顺序写与零拷贝
消息的存储结构
消息刷盘机制
同步刷盘:
异步刷盘:
MQ高可用与主从复制
高可用
主从复制
负载均衡
Producer负载均衡
Consumer负载均衡
消息重试
消息重试机制分为顺序消息和无序消息
死信队列
消息重复消费
幂等
RocketMQ事务
事务消息流程:
角色:生产者,消费者,本地事务,经纪人Broker(也就是我们的MQ)
- 消息的生产者首先会发一个半消息给我们的Broker
- 然后Broker会返回一个ok状态给生产者
- 生产者这时就会去访问本地事务,本地事务会告诉生产者发送的信息是提交还是回滚
- 生产者会将从本地事务获取到的结果返回给Broker(提交还是回滚),还有一种是无反应
- 如果是无反应的,Broker会在向生产者索要结果
- 生产者就会在向本地事务索要结果
- 最后生产者在将结果返回给Broker
- 如果Broker接收到的是提交就将生产者发送的消息保存起来,供消费者消费
- 如果Broker接收到的是回滚就将生产者发送的消息删除掉,消费者就不能消费该消息了
注:5-6-7步骤称为消息的补偿过程
RocketMQ集群
单机:
使用单个Broker提供服务,一单Broker宕机,那么服务就不可用,也会造成一个数据的丢失
集群:
- 使用多个Broker提供服务,解决单机情况下Broker一旦宕机就不可用的情况
- Broker的作用是接收消息以及推送消息,也可以理解为读写操作
- 在一个集群下,如果每个Broker都做读写操作的话,性能会比较低,所以会采用读写分离
- 也就是说在集群下设置主机(master)与从机(slave),可以是多主多从的结构,master用于消息的写入,slave用于推送消息
- 采用多主多从的结构,那么就需要考虑消息同步的问题,有两种方式
- 同步:对比与异步性能略低,消息无延迟
- 异步:对比与同步性能略高,消息有延迟
集群特征
角色:producer(生产者)、consumer(消费者)、Broker(MQ)、NameServer(中控中心)
- 所有的服务都会连接到NameServer中
- 建立集群时,哪个Broker是master哪个是slave是根据Broker中的brokerId区分的。borkerId=0说明是master,否则都是slave
- 而集群中又可以存在多主多从的结构,每个master和它的slave都可以看成是一个组,那么这就需要根据Broker中brokerName区分,如果brokerName的值是一样的就在一个组中
- Broker集群中所有的Broker(maseter和slave)都会注册到NameServer中(NameServer也可以是一个集群,但是它们之间互不通信),并建立长连接。Broker和NameServer在进行数据同步的时候,Broker中有一个Topic是一个很重要的信息,需要同步到每一台NameServer中。Topic:消息的大分组,也就是生产者发送消息时分类存储的地方
- 生产者发消息时(会提供一个Topic),首先会连接NameServer,根据Topic找对应的Broker,再从Broker中找到对应的Topic(Topic可以是很多个组,可以理解成是存放消息的管道,每个Topic是一个单独的管道,group则是存放这些管道的空间),然后就可以根据这些信息发送消息了。消费者与NameServer和Broker跟生成者是一样的
集群工作流程
- 先启动NameServer,开启监听,等待broker、producer和consumer连接
- broker启动,根据配置信息,连接到所有的NameServer,并保持一个长连接
- 如果broker中有数据,那么NameServer将会保存topic和broker的关系,因为最终会根据topic找到对应的broker
- producer发消息,会先连接NameServer,并建立长连接。(注意:并不是先连接Broker)
- producer发消息,存在两个情况:topic存在:由NameServer直接分配到对应的broker;topic不存在:由NameServer创建topic与broker的关系,并分配
- producer在broker中的topic选择一个小消息队列(从列表中选择)
- producer与broker建立长连接,用于发消息
- producer最终发送消息
消息的存储
Broker中为什么需要将消息存储起来?
答:防止Borker宕机,产生消息不可用的情况
消息可以存在数据库,但是消息的发送(存储速度)快慢很大程度上就取决于数据库的速度,它快则快,它慢则慢,所以会受到它的限制;而数据库最终存储数据的地方还是文件系统中,也就是硬盘中;所以可以选择将消息直接存入到文件系统中,这样就跨过了数据库,实现了存储。
所以随之而来的问题就是,怎么跨过数据库将消息存入文件系统中呢?
解决:采用消息刷盘机制进行数据存储
顺序写与零拷贝
顺序写:
即使将消息直接存储到硬盘上,也还是会存在IO速度的问题,因为硬盘也分为机械硬盘和固态硬盘,这两者的IO速度差距是很大的,所以会采取固态硬盘进行存储
而固态硬盘存储数据也分为随机写和顺序写
- 随机写:因为硬盘中会存在磁盘碎片,所以消息的存储可能就不是连续的,而是断续的存储
- 顺序写:先占用一片空间,不允许其它业务使用,这样就能保证这一片空间是连续的,那么IO的速度将会大大提升(随机写(100KB/S);顺序写(600MB/S))
所以RocketMQ采用的就是顺序写,它首先会在我们的磁盘空间开辟一个G的空间,这样需要写数据的时候就直接写在里面,大大提高了速度
零拷贝:
数据的传输过程:
该过程总共经历了四次拷贝。网卡→内存数据不算拷贝
RocketMQ减少一次数据的拷贝,直接从内核态转为网络驱动内核,中间跳过了用户态;该过程就称为零拷贝技术
实现:java语言中使用MappedByteBuffer类实现了该技术
要求:预留存储空间,用户使用顺序写保存数据(1G存储空间起步)
消息的存储结构
也就是RocketMQ的结构
消息刷盘机制
将MQ的消息写入到磁盘的过程就叫刷盘,有两种方式:同步刷盘,异步刷盘
同步刷盘:
- 生产者发送消息给MQ,MQ接收到消息
- MQ就会挂起生产者发送消息的线程
- MQ将消息数据写入到内存中
- 内存数据写入磁盘中
- 数据写入磁盘完成后给MQ返回ok(SUCCESS)
- MQ接收到状态结果后,就会唤醒在挂起生产者发送消息的线程
- MQ就会将发送结果发给生产者
异步刷盘:
- 生产者发送消息给MQ,MQ接收到消息
- MQ将消息数据写入到内存中
- MQ返回发送结果给生产者
也就是说异步刷盘只有同步刷盘中的1,3,7步,因为将数据从内存写入到磁盘中是异步执行的,不在主流程中,也不需要将发送消息的线程挂起;
好处在于:生产者可以一直发消息,无需等待,当消息达到一定的量级在将数据进行一个异步的写入磁盘中
总结:
同步刷盘:安全性高,效率低,速度慢(使用于对数据安全要求较高的业务中)
异步刷盘:安全性低,效率高,速度快(用于对数据处理速度要求较高的业务中)
MQ高可用与主从复制
高可用
- nameserve: 无状态+全服务注册,也可以搭建集群
- 消息服务器(Broker):主从架构(双主双从)
- 消息生产:生产者将相同的topic绑定到多个group组中,保障master挂掉后,其他的master仍可正常消息接收
- 消息消费:RockerMQ自身会根据master承担消息读取的功能,当master繁忙时,自动切换由slave承担数据读取的工作
主从复制
也就是master数据同步到slave中的过程,分为同步复制和异步复制
同步复制:
- master接收消息后,先复制到slave中,在返回结果给生产者
- 优点:数据安全,不丢失数据,出现故障容易恢复
- 缺点:影响数据的吞吐量,整体性能降低
异步复制:
- master接收消息后,立即将操作成功的结果返回给生产者,当消息达到一定量后在异步复制到slave中
- 优点:数据吞吐量大,操作延迟低,性能高
- 缺点:数据不安全,会出现数据丢失的现象,一但master发生故障,那么从上次数据同步时间到出现故障时间之间的数据将会丢失
如何配置Broker是同步复制还是异步复制?
在下载的rocketmq文件的bin目录下有一个broker.conf配置文件,修改:
brokerRole = ASYNC_MASTER
ASYNC_MASTER:异步;前面不加A表示同步
负载均衡
Producer负载均衡
场景:producer和broker都是集群
如果producer发送多个消息在分别对应borker集群中的broker 是同一个组(group),同一个队列(topic)的话,会将所有的消息顺序的发送broker对应的topic中
Consumer负载均衡
平均分配:将多个broker中的消息平均的分给consumer,如果平均是每个服务两个消息,那么会连续发送两个消息到每个consumer中
比如:有两个broker,broker1有1、2、3三个消息,broker2有4、5、6三个消息 有三个consumer都需要消费这些消息,那么会平均分配消息给consumer
- consumer1就有消息1、2
- consumer2就有消息3、4
- consumer3就有消息5、6
产生的问题:如果提供的broker的集群中有一个broker挂了,那么很有可能有些consumer会消费不到消息
循环平均分配:当broker集群提供消息时,会一条一条的将消息发给每一个consumer就避免了上面出现的问题
消息重试
什么是消息重试?
答:当broker推送消息给consumer后,consumer没有返回消费结果将会启动消息重试机制
消息重试机制分为顺序消息和无序消息
顺序消息:
当broker推送消息给consumer时,consumer如果没有返回消费结果,那么就会一直重复发送消息(1秒一次),直到consumer返回结果为止。(注意:如果consumer没有返回消息消费结果,消息是不会删除的)
这样虽然会保证我们的消息会被消费,但是会造成broke中消息的阻塞,前面的消息一直在发,后面的消息发不出去
无序消息:
- 无序消息包括普通消息、定时消息、延时消息、事务消息
- 无序消息重试仅适用于负载均衡(集群)模型下的消息消费,不适用于广播模式下的消费
- 为保障无序消息的消费,MQ设定了合理的消息重试间隔时长
重试间隔时长
当MQ重试次数达到设置的默认最大次数后consumer还是没有返回结果,那么就会将消息放入MQ中的死信队列,那么消息就会变为死信
死信队列
死信队列特征:
- 存放死信的对列,也就是未被消费的消息;它归属于组(group),而不是topic
- 一个死信队列中可以包含同一个组下的多核Topic中的死信消息
- 死信队列在没有死信的时候是没有的,只有在出现了死信,死信队列才会被创建出来
死信队列中消息特征:
- 死信队列中的消息不会被主动消费,如果真想消费,可以根据消息id强制消费消息
- 死信队列中的消息有效期为3天,达到后会被清除
消息重复消费
也就说broker推送消息给consumer,consumer消费完成后给broker返回成功结果时,broker因为某些原因没有收到consumer返回的结果,那么broker就会在发一次,那么就造成了消息的重复消费
消息重复消费的原因
使用消息幂等解决该问题
幂等
- 对同一条消息,无论消费多少次,结果保持一致,称为消息幂等性
- 解决方案
- 使用业务id作为消息的key
- 在消费消息时,客户端对key做判定,未使用过放行,使用过抛弃
- 注意:messageId有RocketMQ产生,messageId并不具有唯一性,不能作用幂等判定条件,所以需要使用业务id
- 常见的幂等方式实例:
- 新增:不幂等 每次添加的数据返回的结果可以是不一致的
- 查询:幂等 必须保证同一个消息每次查询的结果是一致的
- 删除:幂等 删除一条信息返回的结果必须是一致的
- 修改:幂等 修改一条信息的状态为1,那么无论修改多少次,该状态还是要为1
- 修改:不幂等 加钱,每次在原有的基础上加钱,那么每一次操作的结果都会不一样