话不多说,我们先看看为什么要有消息中间件。
-
异步处理
-
应用解耦
-
消峰 虽然消息中间件有诸多好处,但是也带来了一些问题。比如
-
可用性、如何保证消息队列的高可用
-
复杂性
-
一致性问题,是指产生消息的业务动作与消息发送的一致,如果业务操作成功了,那么对应的消息一定要发送出去,否则就丢失消息。同理消息业务失败也不应该把消息发送出去。
如何处理消息丢失的问题
消息丢失可能出现在生产者、mq、消费者。下面以RabbitMQ举例。
-
在生产者给mq发消息的时候,由于网络等原因,消息没有收到,这个时候可以开启mq的事务机制,如果没有收到生产者消息就抛出异常,回滚,然后重试发送消息。当然这样太消耗性能。一般不被人们选择。但是生产者可以开启confirm 模式。它与事务机制最大的不同就是confirm是异步的,不会阻塞。大概流程是,每次写消息的时候都会给消息分配一个唯一id,如果写入成功,mq会回一个ack消息。如果mq没处理这条消息,就回调一个nack接口,通知生产者接受失败。同时自己可以维护这条消息,比如超过一定时间没回复就继续发送。
-
如果是RabbitMQ丢了消息,可以让消息持久化。持久化也不是及时的更新到硬盘中,可以和confirm结合使用。等到mq持久化到硬盘上以后再给生产者发ack消息。
-
消费端丢失数据,不开启自动ack机制,当消费完了再手动ack给mq
如何保证消息的顺序性?
queue天然就是顺序的。但是多个consumer用一个queue也是会有问题。 rabbitmq:拆分多个queue,每个queue一个consumer。或者一个queue只对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底部不同worker处理。
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
- 幂等性,不怕重复消费。
- 利用数据库唯一约束实现幂等
可以通过给消息的某一些属性设置唯一约束,比如增加唯一uuid,添加的时候查询是否存对应的uuid,存在不操作,不存在则添加,那样对于相同的uuid只会存在一条数据。其实,只要类似“insert if not exist”的操作都可能,但需要保证查询跟添加的操作必须是原子性操作。
- 设置前置条件
在更新的时候,可以通过设置一定的前置条件来保证数据幂等,比如给用户发送短信是非幂等操作,但可以添加前置条件,变成如果改用户未发送过短信,则给用户发送短信,此时的操作则是幂等性操作。但在实际上,对于一个问题如何获取前置条件往往比较复杂,此时可以通过设置版本号version,没修改一次则版本号+1,在更新时则通过判断两个数据的版本号是否一致。
- 通过全局ID实现 最后的方式就比较暴力也比较通用,通过设置全局Id去实现。实现的思路是,在发送消息时,给每条消息指定一个全局唯一的 ID(可以通过雪花算法去实现),消费时,先根据这个 ID 检查这条消息是否有被消费过,如果没有消费过,才更新数据。
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时怎么解决?
场景:消费慢、磁盘满、超时等
- 临时紧急扩容
- 直接把消息丢掉。做任务补救。等用户量少的时候再罐到mq中。
消息发送失败 如何补救
- 自动重发
- 人工干预