【大白话系列】-- 消息队列
一、为什么使用消息队列?
解耦、异步、削峰
一个主流的电商系统,一个完整的下单流程包括:下单、支付、优惠券、积分、短信等等流程。这么长的链路不可能顺序执行,这样效率太低,所以我们需要同时做。所以最常用的方法:异步
1.1 异步
如1.2 中的图,当下单的流程完成,其他比如优惠券、积分等同步进行,这样大大节省了时间。
1.2 解耦
说到异步,首先最简单的方法就是使用线程去实现。一个业务就调用一个接口,但是每次加一个你要调用一个接口然后还要重新发布系统,这样非常不便。而且系统耦合非常严重,出问题难排查等等问题。
如果用到消息队列,当下单了,就把支付成功的消息告诉别的系统。我只用走完自己的流程,把消息发出去,其他系统订阅我发出去的支付成功的消息。
那么出现一个问题,我的下单流程走完了,我不用管别人是否成功吗?比如我下单了积分没增加,优惠券没扣等等,怎么办?
这其实是用了消息队列的一个缺点,涉及到分布式事务的知识点,后面会说到。这也告诉我们,任何技术都是有利有弊的,我们要懂得取舍。
1.3 削峰
拿比较常见的秒杀系统为例,平时流量较低,但是到了秒杀时间,比如00:00的时候,流量一下暴增,这时候服务器,Redis,MySQL各自的承受能力不一样,某个被击垮后容易引起服务雪崩。
我们可以把请求放入队列中,根据自己服务器的能力去决定每秒消费多少请求,这样可能比平时慢一点,但这样能保证服务器的稳定性,不至于一下把服务器击垮。在生产中,这个短暂的高峰期积压是允许的。
二、技术是把双刃剑!
它给我们带来便利的同时,产生的问题也接踵而至。主要从三个方面介绍其缺点:
2.1 可用性
本来其他系统只要运行好好的,那你的系统就是正常的。
加入MQ之前不用考虑消息丢失、MQ挂掉等问题,但是,引入MQ之后你就需要去考虑了!因此,系统可用性降低
2.2 复杂性
要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。
因此,需要考虑的东西更多,系统复杂性增大。
2.3 一致性问题
消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?比如前面说的,积分没有增加等等情况,这样就会导致数据不一致的情况了!
三、重复消费
3.1 为什么造成重复消费
无论是那种消息队列,造成重复消费原因其实都是类似的。正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同。kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表他的序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offset提交一下,代表已经消费过了。
但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
3.2 如何解决
针对这种问题,我们需要从业务角度,考虑它的幂等性。幂等:执行次数和执行结果没有直接关系,通俗了讲就是你同样的参数调用我这个接口,调用多少次结果都是一样。
怎么保证消息队列消费的幂等性?
我们需要结合业务来思考,比如下面的例子:
1. 比如某个数据要写库,你先根据主键查一下,如果数据有了,就别插入了,update一下好吧
2. 比如你是写redis,那没问题了,反正每次都是set,天然幂等性
3. 如果上面两种都不行,我们就利用第三方介质,对于消息,我们可以建个表(专门存储消息消费记录),然后每次都去查询。
四、顺序消费
3.1 为什么需要顺序消费
比如一个电商的下单操作,下单后先减库存然后生成订单,这个操作就需要顺序执行。
再比如数据库同时对一个Id的数据进行了增、改、删三个操作,但是你消息发过去消费的时候变成了改,删、增,这样数据就不对了。
3.2 如何解决
-
首先生产者需要保证入队的顺序,入队都是乱的那再厉害的MQ也没有办法。
-
一般的MQ都能保证内部消息队列是FIFO的(先进先出),但是只是针对一个消息队列中(kafka中就是partition,rabbitMq中就是queue),所以在发送消息的时候可以使用Hash取模法将同一个操作的消息发送到同一个消息队列里面,这样就能保证出队时是顺序的了。
-
消费者也需要注意,如果多个消费者同时消费一个队列。一样可能出现顺序错乱的情况。这就相当于是多线程消费了!