消息队列具有低耦合、可靠投递、广播、流量控制等功能,成为异步RPC的主要手段之一。

常见的消息中间件:ActiveMQ、RabbitMQ、Kafka,以及阿里的MetaQ、Notify、RocketMQ。

什么时候需要消息队列

使用MQ的场景有很多,最常用的几种:

  • 业务解耦
  • 最终一致性
  • 广播
  • 错峰流控

解耦

一个事务,只关心核心的流程。而需要依赖其他系统但不那么重要的事情,有通知即可,无需等待结果。 给予消息的模型,关心的是通知,而非处理

举个例子,对于订单系统,订单最终支付成功之后可能需要给用户发送短信积分等逻辑,但其实这已经不算系统
的核心流程了。如果外部系统速度偏慢(如短信服务速度不好),那么主流程的时间回加长很多,用户肯定不希望
点击支付过好几分钟才看到结果。那么我们只需要通知短信系统`我们支付成功了`,不一定非要等待它处理完。

最终一致性

以一个银行的转账过程来理解最终一致性,转账的需求很简单,如果A系统扣钱成功,则B系统加钱一定成功。
反之则一起回滚,像什么都没发生一样。然而,这个过程中存在很多可能的意外:

* A扣钱成功,调用B加钱接口失败。
* A扣钱成功,调用B加钱接口虽然成功,但获取最终结果时网络异常引起超时。
* A扣钱成功,B加钱失败,A想回滚扣的钱,但A机器down机。

最终一致性不是消息队列的必备特性,但确实可以依靠消息队列来做最终一致性的事情。 另外,所有不保证100%不丢消息的消息队列,理论上无法实现最终一致性。 好吧,应该说理论上的100%,排除系统严重故障和bug。 像Kafka一类的设计,在设计层面上就有丢消息的可能(比如定时刷盘,如果掉电就会丢消息)。 哪怕只丢千分之一的消息,业务也必须用其他的手段来保证结果正确。

广播

消息队列的基本功能之一是进行广播。如果没有消息队列,每当一个新的业务方接入,我们都要联调一次 新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑 极大地减少了开发和联调的工作量。

错峰与流控

当上下游系统处理能力存在差距的时候,利用消息队列做一个通用的“漏斗”。在下游有能力处理的时候,再进行分发。 如果下游有很多系统关心你的系统发出的通知的时候,果断地使用消息队列吧。

设计一个队列

设计一个消息队列,并且配备broker,无外乎要做两件事情:

  1. 消息的转储,在更合适的时间点投递,或者通过一系列手段辅助消息最终能送达消费机。
  2. 规范一种范式和通用的模式,以满足解耦、最终一致性、错峰等需求。 发送者把消息投递到服务端(以下简称broker),服务端再将消息转发一手到接收端,就是这么简单。
一般来讲,设计消息队列的整体思路是先build一个整体的数据流,例如producer发送给broker,
broker发送给consumer,consumer回复消费确认,broker删除/备份消息等。
利用RPC将数据流串起来。然后考虑RPC的高可用性,尽量做到无状态,方便水平扩展。
之后考虑如何承载消息堆积,然后在合适的时机投递消息,而处理堆积的最佳方式,就是存储,
存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。
为了实现广播功能,我们必须要维护消费关系,可以利用zk/config server等保存消费关系。

实现队列基本功能

RPC通信协议

刚才讲到,所谓消息队列,无外乎两次RPC加一次转储,当然需要消费端最终做消费确认的情况是三次RPC。 既然是RPC,就必然牵扯出一系列话题,什么负载均衡啊、服务发现啊、通信协议啊、序列化协议啊,等等。 在这一块,我的强烈建议是不要重复造轮子。

利用公司现有的RPC框架:Thrift也好,Dubbo也好,或者是其他自定义的框架也好。

1

消息关系解析

广播关系的维护,一般由于消息队列本身都是集群,所以都维护在公共存储上,如config server、zookeeper等。维护广播关系所要做的事情基本是一致的:

  • 发送关系的维护。
  • 发送关系变更时的通知。
⤧  Next post Java性能优化 ⤧  Previous post 归纳数据库规范