RocketMQ
为什么使用mq?mq的作用?mq对你项目带来了什么?不选mq行不行?
- 异步,MQ能够以异步的方式对消息进行处理,能够大大提高了系统的响应以及吞吐量
- 解耦,MQ双方只需要负责生产或消费信息即可,生产者与消费者并没有捆绑关系,例如:项目中聊天微服务和连接微服务之间并没有捆绑,只需要处理好生产和消费的逻辑,底层的代码结构、运用的框架可以任意改变
- 削峰,同任一时间段海量消息来的时候会导致流量激增,MQ的broker会对数据进行缓存,并不会因为消费者消费速度慢,导致消息丢失,具有一定的容灾性。例如:与三峡大坝的作用一样,水量激增也能保证下游的流速稳定
缺点:
- 系统可用性降低,过度依赖MQ,若MQ故障,那么依靠MQ进行通信的微服务也会失效
- 更加复杂,由原始的同步方式转换为异步,消息处理的逻辑并不是线性的了,需要解决额外产生的问题:
- 消息如何不丢失?
- 消息重复消费如何解决?
- 消息顺序性如何保证?
- 消息一致性如何保证?
你为什么会选择RocketMQ?Kafka怎么样?你对MQ是如何进行产品选型的?
基于面试的八股:
- Kafka
- 优点:吞吐量、性能市面最强
- 缺点:但会造成一定的数据丢失并且功能比较单一
- 使用场景:针对于大数据的处理
- RabbitMQ
- 优点:消息可靠性高,功能丰富
- 缺点:吞吐量以及性能较差,因为会造成消息积累,导致性能不佳
- 使用场景:流量小的场景
- RocketMQ
- 优点:综合了Kafka和Rabbit的优点,解决了其他MQ的缺点,拥有高性能、高吞吐、高可用、功能全面的特性
- 缺点:客户端只支持Java,局限性较高
MQ如何保证消息不丢失的?MQ的消息是否会丢失,从架构原理层面分析一下?
首先,MQ消息可能丢失的地方以及对应处理方法:
-
生产消息,将消息通过网络发送给MQ时(保证消息能抵达MQ)
-
消息发送+回调
-
※ 事务消息 ※
整个流程:生产者会发送Half消息,MQ接收到Half消息并将Half消息存放到一个特殊的队列当中后进行响应,表示已经成功收到此消息。当生产者收到这个响应的同时会立马执行本地事务。接着会在指定的时间发送当前本地事务的状态:
- 事务成功,则Half消息会先还原至原消息,然后就把原消息放入真正的topic所属的队列以及Half_op中,等待消费者消费
- 事务失败,则Half消息就会被放入Half_op主题的队列当中,到时候后台服务扫描半消息时,就通过这个Half消息是否存在于此队列中,来判断这个消息是否已经处理过了
- 为什么需要Half_op? 因为RocketMQ是顺序追加写入,所以不会更改已经入盘的half消息,而回查机制是将已经入盘的half消息进行回查,若half消息已经被判定为成功或失败时也会被读取进行回查,则就冗余、没有必要了。所以必须要通过某个东西来记录half消息是否已经被处理过,于是这个东西就是 half_op,half_op中的消息都是已经处理过的了.
- 事务未知,则MQ发起回查,执行4->5->6的操作,默认回查次数15次,超过则代表事务失败!
-
-
主从同步时(保证消息存在于主从节点中)
-
异步、同步,同步保证不会丢失数据,但会降低吞吐量;异步反之
-
※ Dledger集群-两阶段提交 ※:
D表示节点,M表示主节点,S表示从节点。当要进行主从同步时,节点会开始自动选举出一个主节点M,接着会将消息率先发送给主节点,主节点接收后,状态转为Uncommited并进行分发同步至其余从节点,当大部分从节点返回同步成功的消息后,则主节点状态转为commited,表示同步成功。(具体了解可以去看相关书籍)
-
-
刷盘时(保证消息成功入盘)
- 同步刷盘、异步刷盘,同步保证不会丢失数据,但会降低吞吐量;异步反之
-
MQ通过网络将消息发送给消费者时(保证消息成功被消费者接收)
- 同步传送、异步传送,同步保证不会丢失数据,但会降低吞吐量;异步反之
如何保消息消费的幂等性?如何防止消息重复消费?
重复消费产生的例子:假设我们消费者拿到消息消费了,业务逻辑已经走完了,事务提交了,此时需
要更新 Consumer offset 了,然后这个消费者挂了,另一个消费者顶上,此时 Consumer offset 还没
更新,于是又拿到刚才那条消息,业务又被执行了一遍。于是消息又重复了。
RocketMQ会对每个消息进行分配MessageID,可以利用此方式作为判断是否消费过的依据
@中本聪:这个要看你的业务的
如果你的消息存储的数据是需要插入数据库的,那么你在生成消息时就把 id 生成好(消息的数据中包含 id),在消费时可以通过 id 先查询数据库中是否已经存在,已经存在则是之前已经处理过了。
-
如果 id 不能提前生成,比如用的是自增长 id,也可以考虑加一个有唯一值的字段,提前先用 uuid 生成这个字段的值,在消息的数据中存储这个值。
-
如果是统计阅读时长之类的业务,定时发一次请求,然后累计时间。消息中肯定有用户 id,在消费时可以查找用户最后的记录,如果用户最后的在线记录中的时间段已经覆盖了消息数据中记录的时间,说明消费已经处理过了,不用再处理。
如何保证消息的顺序?为什么要保证消息顺序?
消息顺序分为两种:
- 全局顺序:所有经过消息队列的消息都要保证先进先出的规则
- 局部顺序:同一组经过消息队列的消息都要保证先进先出的规则
各MQ实现消息顺序的大致流程:
- RocketMQ:生产者将一组有序消息发送到同一个MessageQueue里,指定一个消费者消费完这组消息,即可保证局部顺序
- Kafka:生产者通过定制partition分配规则,将消息分配到同一个partition,Topic下只对应一个消费者
- RabbitMQ:要保证目标exchange只对应一个队列。并且一个队列只对应一个消费者。
如何保证消息的高效读写?消息的读写性能怎么样?还能进一步优化吗?
借用 [小林coding](9.1 什么是零拷贝? | 小林coding (xiaolincoding.com)) 这张图可以知道,当用户程序要对消息进行读写时:
- 调用read,进行用户态和内核态进行切换、
- CPU发送指令告知要读取的文件内容,交给DMA进行拷贝至内核缓冲区(此时CPU是可以去执行其他任务的),当内核缓冲区拷贝好之后,DMA会发起中断通知CPU------------第一次数据拷贝
- CPU则会将内核缓冲区的数据拷贝到用户缓冲区当中------------第二次数据拷贝
- 当用户程序对数据进行处理好之后,接着调用write函数,CPU则把用户缓冲区的数据拷贝到内核缓冲区(图中指socket缓冲区)------------第三次数据拷贝
- 内核缓冲区准保好数据之后,会通过DMA进行拷贝到指定区域(图中指网卡)------------第四次数据拷贝
综上,可以发现需要执行四次数据拷贝并且需要进行四次的用户态与内核态之间的转换,这都是很耗性能的操作,那为了提高性能可以通过减少拷贝和用户态内核态之间的转换次数,那么就引出了零拷贝
是的,如何保证消息高效读写?答案就是需要进行零拷贝。
零拷贝技术剖析:
零拷贝分为有两个函数:mmap 和 sendfile
先讲讲mmap:
过程如下:
- 其中mmap是将内核缓冲区的地址映射到用户态的页表上(这里也是需要拷贝的,但是不是数据拷贝,而是地址空间,所以速度极快)
- 用户可以直接对内核缓冲区的数据进行读写操作**---------减少了一次拷贝**
- 接着,当调用write函数时,只需要将内核的缓冲区拷贝至对应的缓冲区即可(图中指socket缓冲区)
综上可以发现,一共只需要三次拷贝,但仍然需要四次用户态内核态的切换
接着是sendfile(需要Linux内核2.1以上):
首先,它可以替代前面的 read()
和 write()
这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。其次,该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 **3 次数据拷贝。
紧接着,如果网卡还支持SG-DMA的技术,则不仅会放弃使用CPU进行拷贝而且可以直接从内存缓冲区直接拷贝至网卡当中。在网络这一方面,又减少了一次拷贝,总共只需要两次拷贝、两次用户态内核态切换,性能又一步提升!当然,这项技术的前提都需要Linux内核2.1以上的版本!
让你设计一个消息队列,你会如何设计?
答题思路:
- 从整体到细节,从业务场景到技术实现,BFS而不是DFS
- 以了解的市面上现有的MQ为基础
个人模拟的口水话:
首先需要明确地提出消息中间件的几个重要角色,分别是生产者、消费者、Broker、注册中心,而消息队列流转过程就是生产者发消息,通过消息队列中的broker暂存消息,接着消费者会通过broker取消息用于消费。那么注册中心用于服务发现可以采用市面流行的Nacos或者zookeeper,保证能够实时监控服务的上下线。底层的通信结构可以根据Netty进行实现,而为了保证数据的可靠性,可以采用多节点方式存储,主从同步采用一主多从方式,根据倾向于性能或数据可靠采用同步或异步的方式进行刷盘操作。并且主从节点可以通过选举算法来判定哪个为主哪个为从,而且当主节点挂了之后从节点顶上,保证消息队列的高可用。对了,文件的读写操作也可以进一步采取零拷贝的方式提高性能,进入提高整体吞吐量。
ChatGPT回答:
第一版:
如果您是指消息队列(MQ,Message Queue)系统的设计,那么以下是一些基本的设计方案和考虑因素:
- 选择适合你的技术栈:选择适合你的技术栈并支持你的需求的MQ软件,例如Apache ActiveMQ、RabbitMQ、Apache Kafka、Amazon SQS等。
- 设计消息格式:定义消息格式并确保消息可以正确地序列化和反序列化,以便消息可以在不同的平台和编程语言之间传递。
- 定义消息队列:根据你的需求定义消息队列,包括队列名称、最大容量、过期时间等等。
- 实现消息的发布和订阅:定义消息发布者和消息订阅者,并确保消息可以按照指定的规则正确地路由到相应的队列。
- 实现消息的确认机制:当一个消息被消费者成功消费后,要及时地将消息标记为已消费,以避免重复消费。
- 实现错误处理和故障恢复机制:当MQ出现故障或异常情况时,要有相应的错误处理和故障恢复机制,以确保系统的可靠性和可用性。
- 监控和日志记录:监控MQ的性能和运行情况,并记录日志以便于追踪和排查问题。
- 安全性和权限控制:确保MQ系统的安全性和权限控制,以避免未授权的访问和数据泄露。
这只是MQ设计中的一些基本考虑因素。具体的设计方案会根据您的实际需求和使用场景而有所不同。
第二版:
为了设计一个消息队列系统,可以考虑以下方面:
- 消息队列类型:选择适合业务需求的消息队列类型,例如点对点、发布/订阅、消息广播等。
- 消息传递协议:选择合适的消息传递协议,例如AMQP、MQTT等。
- 消息存储:选择合适的消息存储方案,可以考虑使用关系型数据库、NoSQL数据库、内存数据库等。
- 消息生产者和消费者:设计消息生产者和消费者的接口和实现,包括如何连接到消息队列、发送和接收消息、处理消息异常等。
- 消息路由和过滤:设计消息路由和过滤规则,以确保消息被正确地传递到目标接收者。
- 消息优先级:为消息设置优先级,以确保高优先级消息能够优先被处理。
- 消息确认和持久化:设计消息确认机制,保证消息不会被重复消费,并考虑消息持久化的方案,以保证消息不会因为系统故障而丢失。
- 消息监控和管理:设计消息队列监控和管理系统,包括消息队列性能监控、消息追踪、日志管理等。
- 安全性和权限控制:考虑消息队列的安全性和权限控制,确保消息传输过程中的数据安全和访问控制。
- 扩展性和高可用性:设计消息队列的扩展性和高可用性方案,以应对高流量和故障情况。
以上是设计消息队列系统的一些关键因素,具体实现可以根据业务需求和技术选型进行调整和优化。
消息不会因为系统故障而丢失。
8. 消息监控和管理:设计消息队列监控和管理系统,包括消息队列性能监控、消息追踪、日志管理等。
9. 安全性和权限控制:考虑消息队列的安全性和权限控制,确保消息传输过程中的数据安全和访问控制。
10. 扩展性和高可用性:设计消息队列的扩展性和高可用性方案,以应对高流量和故障情况。
以上是设计消息队列系统的一些关键因素,具体实现可以根据业务需求和技术选型进行调整和优化。
如有侵权和错误,请及时告知!