支付系统: 消息队列

Page content

你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 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 即可,这样就能保证顺序性。