你好,我是陶辉。
在前 26 讲中我们介绍了许多异步实现机制,这节课我们来看看如何通过消息队列提升分布式系统的性能。
异步通讯是最常用的性能提升方式,比如 gRPC 提供的异步 API,或者基于 write-back 模式向缓存写入数据时,系统性能都可以提高。然而,对于复杂的大规模分布式系统,这些分散、孤立的异步实现机制,无法解决以下问题:
想必你肯定听过 Kafka、RabbitMQ、RocketMQ 这些流行的消息队列吧?通过消息队列实现组件间的异步交互方式,上述问题就会迎刃而解。这一讲我们就来看看如何在分布式系统中使用消息队列,以及高可用性又是如何保证的。
当进程中需要交互的两个模块性能差距很大时,我们会基于 FIFO 先入先出队列实现生产者消费者模型,通过调整生产者、消费者的数量,实现线程间的负载均衡。而且,生产者仅将任务添加至队列首部就可以返回,这种异步操作释放了它的性能。比如[第 13 课] 中接收心跳包的分发线程性能要比处理心跳包的工作线程性能高得多,两者间就通过单机上的消息队列提高了整体性能。
把单机中的 FIFO 队列放大到分布式系统中,就形成了独立的消息队列服务。此时,生产者、消费者的角色从线程变成了网络中的独立服务,如下图所示,生产者可以向消息队列发布多种消息,多个消费者也可以订阅同一种消息,如下图所示:
总结一下的话,消息队列就具备了以下 7 个优点:
正是因为这么多的优点,所以消息队列成为了多数分布式系统必备的基础设施。而且,消息队列自身也拥有很高的性能,比如 RabbitMQ 单机每秒可以处理 10 万条消息,而 Kafka 单机每秒甚至可以处理百万条消息。消息队列的性能为什么如此夸张呢?除了消息队列处理逻辑简单外,还有一个重要原因,就是消息的产生、消费在时间上是连续的,这让消息队列在以下优化点上能获得很高的收益:
而且,目前主流消息队列都支持集群架构,因此消息队列自身一般不会是性能瓶颈。
为了提升整个分布式系统的性能,我们在处理消息时,还需要在生产端、消费端以及消息队列的监控上,做到以下 3 件事:
接下来,我们再来看消息队列的 QoS(Quality of Service)是如何保证的,即:消息在传递过程中会不会丢失,以及接收方会不会重复消费消息。在MQTT 协议中,给消息队列定义了三种 QoS 级别:
需要 at most once 约束的场景较罕见,因此目前绝大部分消息队列服务提供的 QoS 约束都是 at least once,它是通过以下 3 点做到的:
这样,消息队列就能以很高的可用性提供 at least once 级别的 QoS。而 exactly once 是在 at least once 的基础上,通过幂等性 idempotency 实现的。对于一条“幂等性消息”,无论消费 1 次还是多次,结果都是一样的。因此,Kafka 通过消息事务和幂等性约束实现了exactly once 语义,其中,发布消息时 Kafka 会创建全局唯一的递增 ID,这样传输消息时它就能低成本地去除重复的消息,通过幂等性为单队列实现 exactly once 语义;针对生产者向多个分区发布同一条消息的场景,消息事务通过“要么全部成功要么全部失败”,也实现了 exactly once 语义。
这一讲我们介绍了消息队列及其用法。
消息队列可以解耦分布式系统,其缓存的消息提供了削峰填谷功能,将消息持久化则提高了系统可用性,共享队列则为系统提供了可伸缩性,而且统计消息就可以监控整个系统,因此消息队列已成为当下分布式系统的必备基础设施。
虽然消息队列自身拥有优秀的性能,但若想提高使用效率,我们就需要确保在生产端实现网络传输上的并发,在消费端扩容时同步增加队列或者分区,并且需要持续监控系统,确保消息的生产能力小于消费能力,防止消息积压。
消息队列的 Qos 提供三种语义,其中 at most once 很少使用,而主流的 at least once 由消息持久化时的冗余,以及生产端、消息端使用消息的方式共同保障。Kafka 通过幂等性、事务消息这两个特性,在 at least once 的基础上提供了 exactly once 语义。
最后,留给你一道讨论题。你在实践中使用过消息队列吗?它主要帮你解决了哪些问题?欢迎你在留言区与大家一起探讨。
感谢阅读,如果你觉得这节课对你有所帮助,也欢迎把今天的内容分享给你的朋友。