支付系统: 消息队列
你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。
为什么使用消息队列?
较核心的场景有 3 个:
- 解耦 - 如 同步数据给下游,例如审计系统
- 异步 - 如 支付成功,给用户发消息;支付异常,异步重试;异步给用户发放优惠券
- 削峰 - 如 降低mysql写入负载
支付系统用到的MQ
- RabbitMQ: 万级,比 RocketMQ、Kafka 低一个数量级
- Redis: 10 万级,big key的问题
- Kafka: 100 万级,高吞吐;不支持消息调度
消息队列有什么优缺点
- 系统可用性降低,系统引入的外部依赖越多,越容易挂掉。如MQ挂了,给用户发消息就失败
- 系统复杂度提高。Q: 怎么保证不重复消费,怎么防止消息丢失?
- 一致性问题。比如 消息处理完,ack失败了,消费者中途挂了。
消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
如何保证消息队列的高可用
RabbitMQ 镜像集群模式(高可用性)
这种模式,才是所谓的 RabbitMQ 的高可用模式。 跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论是元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。
Kafka 的高可用性
Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 是一个节点; 你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。 这就是天然的分布式消息队列,就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。
怎么保证不重复消费
例如:给用户发通知不能重复。
- redis set一个key,自动保证幂等。
- mysql uk记录一行: 基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
怎么防止消息丢失
生产者丢数据
RabbitMQ 事务机制(同步)一搞,基本上吞吐量会下来,因为太耗性能。
Kafka设置了 acks=all ,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。
MQ自己把消息弄丢了
RabbitMQ开启持久化,性能会降低。
消费者丢消息 - 拿到消息后,中途挂了。
RabbitMQ
关闭 RabbitMQ 的自动 ack ,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。
Kafka
Kafka 会自动提交 offset,那么只要关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。 但是此时确实还是可能会有重复消费,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。
如何保证消息的顺序性?
场景:
mysql 里增删改一条数据,对应出来了增删改 3 条 binlog 日志,接着这三条 binlog 发送到 MQ 里面,再消费出来依次执行,起码得保证人家是按照顺序来的吧?
RabbitMQ
- 拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点,这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。
Kafka
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。