RocketMQ
MQ:MessageQueue,消息队列。
消息由生产者发送到MQ进行排队,然后按原来的顺序交由消息的消费者进行处理。
MQ的作用
- 异步:提高系统的响应速度、吞吐量
- 解耦:
- 服务之间进行解耦,才可以减少服务之间的影响。提高系统整体的稳定性以及可扩展性。
- 解耦后可以实现数据分发。生产者发送一个消息后,可以由一个或者多个消费者进行消费,并且消费者的增加或者减少对生产者没有影响
- 削峰:以稳定的系统资源应对突发的流量冲击
MQ的优缺点
- 系统可用性降低:系统引入的外部依赖增多,系统的稳定性会变差。
- 系统复杂度提高:引入MQ后,会变为异步调用,数据的链路会变得更复杂。
- 消息一致性问题:需要考虑如何保证消息数据处理的一致性
MQ产品对比:在服务端处理同步发送的性能上,Kafka>RocketMQ>RabbitMQ。
优点 | 缺点 | 使用场景 | |
---|---|---|---|
kafka | 吞吐量非常大 性能非常好 集群高可用 | 会丢数据 功能比较单一 | 日志分析 大数据采集 |
RabbitMQ | 消息可靠性高 功能全面 | 吞吐量比较低 消息积累会影响性能 erlang语言不好定制 | 小规模场景 |
RocketMQ | 高吞吐 高性能 高可用 功能全面 | 开源版功能不如云上版 官方文档比较简单 客户端只支持java | 几乎全场景 |
RocketMQ的组件:
- Producer Cluster
- Consumer Cluster
- NameServer Cluster:9876
- Broker Cluster:10909/10911
消息模型 Message Model
- Producer 负责生产消息
- Consumer 负责消费消息
- Broker 负责存储消息
- Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。
- Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。
- ConsumerGroup由多个Consumer 实例构成。
- Topic表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
- 同一个Topic下的数据,会分片保存到不同的Broker上,而每一个分片单位,就叫做MessageQueue。MessageQueue是生产者发送消息与消费者消费消息的最小单位
- Name Server ,名称服务充当路由消息的提供者。Broker Server会在启动时向所有的Name Server注册自己的服务信息,并且后续通过心跳请求的方式保证这个服务信息的实时性。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。意味着NameServer中任意的节点挂了,只要有一台服务节点正常整个路由服务就不会有影响。
- 消息 Message,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题Topic。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。并且Message上有一个为消息设置的标志,Tag标签。用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性
消息生产者:把业务应用系统里产生的消息发送到broker服务器。
- 同步发送:等待消息返回后再继续进行下面的操作,org.apache.rocketmq.example.simple.Producer
- 异步发送:引入了一个countDownLatch来保证所有消息回调方法都执行完了再关闭Producer,org.apache.rocketmq.example.simple.AsyncProducer
- 单向发送:sendOneway ,有返回值,也没有回调
消息消费者:从Broker服务器拉取消息、并将其提供给应用程序。
- 消费者主动去Broker上拉取消息的拉模式,org.apacherocketmq.example.simple.PullConsumer
- 消费者等待Broker把消息推送过来的推模式,org.apache.rocketmq.example.simple.PushConsumer
顺序消息:
- 不管订单在多个Consumer实例之前是如何分配的,每个订单下的多条消息顺序都是固定从0~5的。
- RocketMQ保证的是消息的局部有序,而不是全局有序。
- 生产者样例见: org.apache.rocketmq.example.order.Producer,消费者样例见: org.apache.rocketmq.example.order.Consumer
广播消息:
- 在集群状态(MessageModel.CLUSTERING)下,每一条消息只会被同一个消费者组中的一个实例消费到。而广播模式则是把消息发给了所有订阅了对应主题的消费者,而不管消费者是不是同一个消费者组
- 消息生产者样例见:org.apache.rocketmq.example.broadcast.PushConsumer
- MessageModel:MessageModel.CLUSTERING(集群模式)、MessageMode.BROADCASTING(广播模式)
延时消息
public class ScheduledMessageProducer{
public static void main(Stringl] args) throws Exception{
// Instantiate a producer to send scheduled messages
DefaultMOProducer producer = new DefaultMOProducer("ExampleProducerGroup");
//Launch producer
producer.start();
int totalMessagesTosend = 100;
for (int i = 0; i < totalMessagesToSend; i++){
Message message new Message("TestTopic", ("Hello schedulednessage " +i).getBytes());
// This message will be delivered to consumer 10 seconds later .
message.setDelayTimeLevel(3);
// Send the message
producer.send(message);
}
// Shutdown producer after use .
producer.shutdown () ;
}
}
批量消息:
- 将多条消息合并成一个批量消息,一次发送出去。这样的好处是可以减少网络IO,提升吞吐量。
- 消息生产者样例:org.apache.rocketmq.example.batch.SimpleBatchProducer和org.apache.rocketmq.example.batch.SplitBatchProducer
- 一个批次消息的大小不要超过1MB,实际最大的限制是4194304字节,大概4MB
- 批量消息的使用是有一定限制的,这些消息应该有相同的Topic,相同的waitStoreMsgOK。而且不能是延迟消息、事务消息等
过滤消息:
- 在大多数情况下,可以使用Message的Tag属性来简单快速的过滤信息
- 使用Tag过滤消息的消息生产者案例见:org.apache.rocketmq.example.filter.TagFilterProducer,消息消费者案例见:org.apache.rocketmq.example.filter.TagFilterConsumer
- TAG是RocketMQ中特有的一个消息属性。RocketMQ的最佳实践中建议,使用RocketMQ时,一个应用可以用一个Topic,而应用中的不同业务用TAG来区分。
事务消息:
- 在分布式系统中保证最终一致性的两阶段提交的消息实现。他可以保证本地事务执行与消息发送两个操作的原子性,也就是这两个操作一起成功或者一起失败。
- 事务消息只保证消息发送者的本地事务与发消息这两个操作的原子性,因此,事务消息的示例只涉及到消息发送者,案例见org.apache.rocketmq.example.transaction.TransactionProducer
ACL权限控制:
- 为RocketMQ提供Topic资源级别的用户访问控制
- 在使用RocketMQ权限控制时,可以在Client客户端通过 RPCHook注入AccessKey和SecretKey签名
- 对应的权限控制属性 (包括Topic访问权限、IP白名单和AccessKey和SecretKey签名等) 设置在$ROCKETMQ HOME/conf/plain acl.yml的配置文件
- Broker端对AccessKey所拥有的权限进行校验,校验不过,抛出异常
- ACL客户端参考: org.apache.rocketmq.example.simple.AclClient。
SpringCloudStream:Spring社区提供的一个统一的消息驱动框架,目的是想要以一个统一的编程模型来对接所有的MQ消息中间件产品。
Dledger高可用集群:
- 接管Broker的CommitLog消息存储
- 从集群中选举出master节点(Raft算法)
- 完成master节点往slave节点的消息同步
Raft算法:
- 所有节点启动时都是follower状态;在一段时间内如果没有收到来自leader的心跳,从follower切换到candidate,发起选举;如果收到大多数人( N/2 + 1 )(含自己的一票),则切换到leader状态;如果发现其他节点比自己更新,则主动切换到follower。
- 系统中最多只有一个leader,如果在一段时间里发现没有leader,则大家通过选举-投票选出leader。leader会不停的给follower发心跳消息,表明自己的存活状态。如果leader故障,那么follower会转换成candidate,重新选出leader。
消息存储:
- MQ收到一条消息后,需要向生产者返回一个ACK响应,并将消息存储起来
- MQ Push一条消息给消费者后,等待消费者的ACK响应,需要将消息标记为已消费。如果没有标记为消费,MQ会不断的尝试往消费者推送这条消息
- MQ需要定期删除一些过期的消息,这样才能保证服务一直可用
消息存储优势:
- RocketMQ的消息用顺序写,保证了消息存储的速度。
- 零拷贝技术加速文件读写:使用mmap的方式,可以省去向用户态的内存复制,提高速度(适合比较小的文件),sendfile适合传递比较大的文件。
常规文件操作、网络操作:
- 从磁盘复制数据到内核态内存
- 从内核态内存复制到用户态内存
- 然后从用户态 内存复制到网络驱动的内核态内存
- 最后是从网络驱动的内核态内存复 制到网卡中进行传输
RocketMQ消息存储结构:
- CommitLog: 存储消息的元数据。所有消息都会顺序存入到CommitLog文件当中。CommitLog由多个文件组成,每个文件固定大小1G。以第一条消息的偏移量为文件名。
- ConsumerQueue: 存储消息在CommitLog的索引。一个MessageQueue一个文件,记录当前MessageQueue被哪些消费者组消费到了哪一条CommitLog。
- IndexFile: 为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程
消息存储文件:
- abort:RocketMQ用来判断程序是否正常关闭的一个标识文件。正常情况下,会在启动时创建,而关闭服务时删除。但是如果遇到一些服务器宕机,或者kill -9这样一些非正常关闭服务的情况,这个abort文件就不会删除,因此RocketMQ就可以判断上一次服务是非正常关闭的,后续就会做一些数据恢复的操作
- checkpoint:数据存盘检查点
- config/*json:这些文件是将RocketMQ的一些关键配置信息进行存盘保存。例如Topic配置、消费者组配置、消费者组消息偏移量Offset 等等一些信息。
刷盘机制:RocketMQ需要将消息存储到磁盘上,这样才能保证断电后消息不会丢失。同时这样才可以让存储的消息量可以超出内存的限制
- 同步刷盘机制 SYNC_FLUSH:消息从Producder端发送出去后,被Broker接收,Broker接收到消息后将消息写入内存的PageCache后,立即通知刷盘线程进行刷盘,当前线程等待刷盘线程的通知。刷盘线程开始进行刷盘操作,刷盘完毕后唤醒之前等待的线程,再返回写成功状态,最后Producer会收到消息发送成功的ACK
- 异步刷盘机制 ASYNC_FLUSH:消息从Producder端发送出去后,被Broker接收到,Broker端接收到消息后,消息被写入PageCache后立即返回写成功给Producer端。然后另一个异步线程专门会将PageCache中的数据写到磁盘里,确保消息的持久化。
主从复制 brokerRole:producer发送消息给master节点之后,master节点会把消息同步给slave节点上进行存储
- SLAVE:从节点,只负责接收master的请求
- SYNC_MASTER :同步双写Master。master节点发送消息给slave节点之后,master节点会进行等待, 等master和slave都写入消息成功后才反馈给客户端写入成功的状态,master节点才会接着同步下面的数据
- ASYNC_MASTER:异步复制Master。只要master写入消息成功,就反馈给客户端写入成功的状态。然后再异步的将消息复制给Slave节点,然后Slave自己就进行保存数据
消息负载均衡:一种技术解决方案,用来在多个资源(一般是服务器)中分配负载,达到最优化资源使用,避免单台服务器过载。
- Producer发送消息负载均衡
- 通过增加机器,可以水平扩展队列容量。
- 自定义方式选择发往哪个队列。如顺序消息发送的时候,就要求实现队列选择器,根据消息唯一标识选择对应的队列进行发送。
- Consumer订阅消息负载均衡(不适用广播消费)
- 同一个 Consumer Group下,通过增加Consumer实例数量来提高并行度,不过需要注意,超过订阅队列数的Consumer实例无效。可以通过加机器,或者在已有机器启动多个进程的方式。
- 提高单个 Consumer 的消费并行线程,通过修改设置 consumeThreadMin最小并发线程数和consumeThreadMax最大并发线程数来提高消费能力。
- 通过设置 Consumer的consumeMessageBatchMaxSize这个参数,默认是1,即一次只消费一条消息,例如设置为N,那么每次消费的消息数小于等于N。即可大幅度提高消费的吞吐量。
系统默认Topic消息:
- RMQ_SYS_TRACE_TOPIC:消息轨迹 Topic,broker开启消息跟踪之后,系统自动创建的
- RMQ_SYS_TRANS_HALF_TOPIC:事务消息 Half Topic
- SCHEDULE_TOPIC_XXXX:延迟消息Topic
在客户端的两个核心对象 DefaultMQProducer和DefaultMQPushConsumer,构造函数中,都有两个可选的参数来打开消息轨迹存储
- enableMsgTrace: 是否打开消息轨迹。默认是false。
- customizedTraceTopic: 配置将消息轨迹数据存储到用户指定的Topic。
- 打开消息轨迹功能,需要在broker.conf中打开一个关键配置:traceTopicEnable=true
防止消息丢失:(消息零丢失方案)
- 生产者:使用事务消息机制(同步消息重试机制重发,或者用事务消息保证正常消息不会丢失)
- broker配置同步刷盘+Dledger /di:ˈledʒər/主从架构(master->slave:同步复制不会丢失,master:Dledger主从架构)
- 消费者:不使用异步消费方式
- 整个MQ挂了之后准备降级方案(nameSrv:挂了,生产者没有办法发送,降级机制:业务系统再尝试往rocketMq丢消息)
消息积压:
- 原因:在正常情况下,使用MQ都会尽量保证消息生产速度和消费速度整体上是平衡的,但是如果部分老系统出现故障会造成大量的消息积累
- 新上线的消费者功能有BUG,消息无法被消费。
- 消费者实例宕机或因网络问题暂时无法同Broker建立连接。
- 生产者短时间内推送大量消息至Broker,消费者消费能力不足。
- 生产者未感知Broker消费堆积持续向Broker推送消息
- 查看消息积压情况:mqadmin
- 解决方案:负载均衡,加消费者,创建另一个topic(增加消费者做中转)
- 提高消费并行度
- 批量方式消费
- 跳过非重要消息
- 优化每条消息消费过程
消息有序:
- 全局有序::整个MQ系统的所有消息严格按照队列先入先出顺序进行消费
- 局部有序:只保证一部分关键消息的消费顺序
1、系统复杂性提高,可用性降低
系统多了一个组件就需要考虑MQ自身会不会出问题,万一MQ自己挂了会导致整个系统不可用。系统每多加一个组件都会导致系统的复杂性提高。
2、数据的一致性问题
系统异步第调用ABC三个系统完成下单当有一个服务处理失败时,如何保证数据的一致性
解决方法:采用分布式事务
3、MQ的常见问题解决方法
- 如何提高可靠性?
解决方法:搭建MQ镜像集群
- 如何保证消息不丢失?
解决方法:每次发送消息到MQ之后,MQ处理完通知发送方数据处理情况,例如消息生产方发送消息后如果再规定时间内没有收到回复就会重新发送消息。
- 如何保证消息不重复消费?
解决方法:在消息中添加一个唯一ID,消费端每次从MQ中取出消息都去查询一下是否已经存在,如果存在就丢弃。
- 如何保证消息传递的顺序?例如一笔订单产生三条消息,分别是创建订单,支付、库存扣减,消费时需要按顺序依次消费
解决方法:让一笔订单中三条消息发送给同一个队列,消费时每取出一条消息就给队列加上分段锁,直到处理完第一个消息后释放锁