摘要
作为技术方案最常提到的组件:消息队列,它在我们的程序中起到了重要的作用。异步、解耦、削峰(缓冲)等特性正是我们选择它的原因。本文将会按自己的理解聊一聊消息队列的本质、使用场景、注意事项、以及介绍下主流的消息队列。
一、消息队列是什么?
在使用消息队列之前,我们首先得对它有一个清楚的认知。通常在上下游的通信过程中,我们需要一个载体来传送某一方关心的信息。
比如用户在商城下单成功后,商品信息会被打包成一个对象,发送给物流模块。在这里,物流模块就是上游,而商品信息就是消息。在将对应模块抽象出来后,通过消息这个粘合剂,就能将整条链路连接起来。
上面提及到的上下游模块假设都在同一个进程里,那么消息的存在方式就是内存了;那如果上下游是在两个进程程序或者部署在不同的机器中呢?消息又该以何种方式存储?又该怎么流转呢?
这就是“队列”的作用了,传统的数据结构“队列”表示的是一种先进先出的链表。而这里的“队列”更多的是指兼具存储和路由特性的消息管理器。
至于消息的存储方式可以是临时内存,也可以持久化到文件或数据库中。但消息不会一直存储着,它有属于自己的消费规则,在按一定的规则分发到各个模块后,就会被剔除掉。
二、消息队列的特性与使用
在软件设计里,我们经常要求模块功能遵守单一职责原则,这跟专人专事是一样的道理。但在涉及服务与服务的调用过程中,总会涉及到状态维护、失败重试等跟主流程关联性不强的需求。而这部分功能正是消息队列的强项。
一般的,我们将发送数据给消息队列的功能模块,称之为“生产者”。而最终使用消息的模块,称之为“消费者”。
当生产者成功投递消息后,就不再关心消息的流转了,后续的流程将由消息队列来保证,生产者最多保存下元数据。而为了防止数据丢失,消息队列一般都会提供持久化功能,以便进行重启恢复。
这样一来,即使生产者发送了异常,只要消息队列能正常运行,能继续分发剩余的消息,那消费者就不会空闲下来。
从这,我们也能看出消息队列的解耦作用,让生产者与消费者互不关联。
在解耦的同时,消息队列也提供了一个缓冲地带。当面对高并发高流量到来时,消息并不会直接的打到消费端,它会先在消息队列走一回。只要我们在消息队列里做好把控,那么它就不会成为压倒系统的最后一根稻草。
而市面上的消息队列框架一般都会考虑到这些异常情况,比如提供了流量控制的 RabbitMQ。
在有了消息队列这个中间件之后,我们应该尽可能的将系统设计成异步处理的。比如,前面提到过的下单完成通知与物流发货功能。
三、消息队列的注意事项
尽管有了消息队列的存在,系统服务的联动变得更加的灵活,可拓展性也大大提高。但毕竟引入了第三方组件,这也意味着复杂度增加了。
一致性、幂等性
不管你承不承认,从我们把消息队列抽象出来后,我们的系统就是一个分布式存在了。对于分布式系统,最难的莫过于一致性问题,先有 CAP 原则,后有 BASE 理论,此处本文不会详细说明,大伙感兴趣的可以自己研究下。这里想提及的就是不能单靠消息队列本身来实现消息的可靠性、一致性。还得生产者、消费者一起来完善整条传输链路。
对于生产方来说应该要考虑本地操作+发送消息这一动作的事务特性,当消息发送失败时,本地操作要有回滚动作,或者通过定时校验来达到最终一致状态。
对于消费端来说,要有幂等性的保证。由于网络环境的复杂,我们是无法在一次通信过程中就能确定消息接收情况的。消息队列往往会有重发机制,来防止失败或超时的情况产生。
因此,我们不能简单的认为,消息有且只会出现一次,这是不现实的。
通常的,我们会在消息中添加业务唯一标识,外加状态字段进行事务性的判断修改,来确保业务的一次执行。
顺序性
在同一个进程里想要实现顺序性并不难,只要有一个全局的控制即可。但一般我们的消息队列为了避免单机瓶颈,都会提供集群的部署方式。这种在分布式里考虑顺序性,难度无疑大大提高。
因此对于顺序性的消息需求,我们应该尽可能从业务上重新规划,考虑是否真的有这个必要性。当然,现在流行的消息队列框架也是有提供顺序性功能的,像 Kafka 的 相同 Partition 策略。
常见的消息队列
前面提到过 RabbitMQ、Kafka,它们都是主流的消息队列。常见的还有 ActiveMQ 以及 阿里的 RocketMQ。
RabbitMQ 是用 erlang 语言开发的,实现了高级消息队列协议(AMQP),并且提供了多语言的 SDK 接口,有比较完善的消息转发机制。
Kafka 具有高吞吐高性能的数据处理能力,常用于日志收集,像 ELK 日志分析系统就采用了 Kafka 作为消息队列。
ActiveMQ 是 Apache 出品的消息队列,实现了 Java 消息服务(JMS)协议。是一个成熟的、快速的开源消息组件
RocketMQ 从它的名字就看得出来它的高性能了,当然,由于它是阿里开源的,所以在文档资料方面还是挺有优势的。而且也比较贴近我们中国程序员的开发思维。
上面的消息队列框架大多具备了高可用、高拓展、高性能的特性。个人推荐当然是国产的 RocketMQ 了(◠◡◠)。
总结
消息队列就像我们生活中的中介,引入它是需要付出代价的,但它也确实能让我们更加专注于业务上的开发。而且现在市面上已经有很多成熟可用的开源项目了,借助开源的力量,我们的程序将能够更加的健壮!