消息队列-mq
1. 背景已经基础知识介绍
消息队列,也就是我们常说的mq(message queue),是在后端开发中非常常用的一个技术。
1.1 消息队列解决了什么问题
主要解决以下几个问题
1.解耦和
很好理解,两个service本来是直接通信的,现在变成了有个中间人,整个的流程是变长了,但是这两个service之间的耦合度肯定是下降了。逻辑图如下:
有MQ时:
2. 异步通信
很多时候,在一个流程里面有的操作是需要立刻执行的,但是有的操作并不需要立刻执行,比如用户注册之后,需要将用户信息写入数据库,同时给用户的邮箱发送一封邮,在这样一个场景里面,对于注册用户来说,将用户信息存入数据库是必须要马上做的事情,而发邮件这件事情却可以在稍后一点再执行也不会有任何影响。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
3. 峰值处理
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。非常典型的场景,应该是淘宝双11以及京东618的活动,在一整年之中,大部分的时候,对于淘宝和京东来说每天的业务访问量都是比较均衡的,也不会在某一个时刻特别高,但是在这种特殊的一两天,流量会剧增,那么,我的系统是要为满足最大峰值而设计吗,显而易见不是,只需满足平常的需求,然后在流量特别大时借助消息队列的帮助即可。想必淘宝的Rocketmq就一定发挥了这样的作用
4.消息通讯
消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 包含两种:一对一,一对多。
这当中前面三个是重中之重。
2. 为什么使用消息队列,消息队列有什么优点和缺点
为什么要使用消息队列,以及消息队列有什么有点,上一部分的:解耦和,峰值处理,异步通讯等,做了很好的解释。
那么缺点是什么呢,很容易想到肯定是变复杂了,具体如下:
系统可用性降低: 本来其他系统只要运行好好的,那你的系统就是正常的。现在加个消息队列进去,消息队列挂了,你的系统也就不能工作了。
系统复杂性增加: 要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
3. 如何保证消息队列的高可用,如何保证不被重复消费
承接第二部分,这些问题和缺点我们怎么解决呢?
1.如何保证消息队列的高可用性?
高可用性定义如下:
服务器的可用性是指单位时间内(通常一年),服务器可以正常工作的时间比例,计量单位是百分比,常用99%,99.9%,99.99%来表示。一般讲4个9,5个9或者6个9。
可用性为99%的系统,全年停机时间为3.5天;99.9%的系统;全年停机时间为8.5小时;99.99%的系统全年停机时间为53分钟;99.999%的系统全年停机时间仅仅约为5分钟
即解决第一个问题,就是消息队列挂了你的系统用不了,我们如何保证消息队列挂了也能工作?
基本思想是,链接一个rabbitmq,如果这一个宕机了,那么就不能工作了,那么很简单,我不用一个rabbitmq来处理,而是用一群来处理,即MQ的一个集群,并且部署在多个服务器上,其中一个不工作,也不会影响系统的使用,那么稳定性和安全性自然就提升了。更多的可以参考:https://blog.****.net/super_rd/article/details/70856909
2. 如何保证不被重复消费?
首先需要明确的问题是:为什么消息会被重复消费?
其实无论是那种消息队列,造成重复消费原因其实都是类似的。正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,简单说一下(如果还不懂,出门找一个kafka入门到精通教程),就是每一个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。那造成重复消费的原因?,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
如何解决?这个问题针对业务场景来答分以下几点
(1)比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
(3)如果上面两种情况还不行。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
4. kafka,activemq,rabbitmq,rocketmq各有什么优缺点
特性 | ActiveMQ | RabbitMQ | RocketMQ | kafka |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | ms级 | us级 | ms级 | ms级以内 |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
功能特性 | 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 | MQ功能比较完备,扩展性佳 | 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。 |
5. 如果让你设计一个消息队列,应该如何进行架构设计,思路是怎样的。
基本的消息队列功能其实很简单。一个消息队列无外乎做两件事情
- 消息的转储,在更合适的时间点投递,或者通过一系列手段辅助消息最终能送达消费机。
- 规范一种范式和通用的模式,以满足解耦、最终一致性、错峰等需求。
设计消息队列的整体思路是先build一个整体的数据流,例如producer发送给broker,broker发送给consumer,consumer回复消费确认,broker删除/备份消息等。
利用RPC(Remote Procedure Call远程过程调用,就是一种通信方式而已)将数据流串起来。然后考虑RPC的高可用性,尽量做到无状态,方便水平扩展。
之后考虑如何承载消息堆积,然后在合适的时机投递消息,而处理堆积的最佳方式,就是存储,存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。
为了实现广播功能,我们必须要维护消费关系,可以利用zk/config server等保存消费关系
更多可以参考:https://blog.****.net/xiangshui021/article/details/52382122