Message Queue
为什么使用消息队列
https://aws.amazon.com/message-queue/benefits/
解耦
Message queues remove dependencies between components and significantly simplify the coding of decoupled applications. Software components aren’t weighed down with communications code and can instead be designed to perform a discrete business function.
异步 (weak consistency)
再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。
一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。
如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快!
Better Performance
Message queues enable asynchronous communication, which means that the endpoints that are producing and consuming messages interact with the queue, not each other. Producers can add requests to the queue without waiting for them to be processed. Consumers process messages only when they are available. No component in the system is ever stalled waiting for another, optimizing data flow.
削峰
每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。
一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。
但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。
如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。
这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。
Granular Scalability
Message queues make it possible to scale precisely where you need to. When workloads peak, multiple instances of your application can all add requests to the queue without risk of collision. As your queues get longer with these incoming requests, you can distribute the workload across a fleet of consumers. Producers, consumers and the queue itself can all grow and shrink on demand.
保证 MQ 消息不丢
RabbitMQ 生产者丢消息
解决方案:事务机制:(一般不采用,同步的,生产者发送消息会同步阻塞卡住等待你是成功还是失败。会导致生产者发送消息的吞吐量降下来)
confirm机制:(一般采用这种机制,异步的模式,不会阻塞,吞吐量会比较高)
先把 channel 设置成 confirm 模式
发送一个消息到 rabbitmq
发送完消息后就不用管了
rabbitmq 如果接收到了这条消息,就会回调你生产者本地的一个接口,通知你说这条消息我已经收到了
rabbitmq 如果在接收消息的时候报错了,就会回调你的接口,告诉你这个消息接收失败了,你可以再次重发。
RabbitMQ 消费者丢消息
原因:消费者打开了 autoAck机制(消费到一条消息,还在处理中,还没处理完,此时消费者自动 autoAck了,通知 rabbitmq说这条消息已经消费了,此时不巧,消费者系统宕机了,那条消息丢失了,还没处理完,而且 rabbitmq还以为这个消息已经处理掉了)
解决方案:关闭 autoAck,自己处理完了一条消息后,再发送 ack给 rabbitmq,如果此时还没处理完就宕机了,此时rabbitmq没收到你发的ack消息,然后 rabbitmq 就会将这条消息重新分配给其他的消费者去处理。
保证 MQ 重复消费幂等性
幂等:一个数据或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。思路:
拿数据要写库,首先检查下主键,如果有数据,则不插入,进行一次update
如果是写 redis,就没问题,反正每次都是 set ,天然幂等性
生产者发送消息的时候带上一个全局唯一的id,消费者拿到消息后,先根据这个id去 redis里查一下,之前有没消费过,没有消费过就处理,并且写入这个 id 到 redis,如果消费过了,则不处理。
基于数据库的唯一键