主流的消息队列 MQ 比较,详解 MQ 的 4 类应用场景
3.ActiveMQ
历史悠久的开源项目,是 Apache 下的一个子项目。已经在很多产品中得到应用,实现了 JMS1.1 规范,可以和 spring-jms 轻松融合,实现了多种协议,不够轻巧(源代码比 RocketMQ 多),支持持久化到数据库,对队列数较多的情况支持不好。
4.Redis
做为一个基于内存的 K-V 数据库,其提供了消息订阅的服务,可以当作 MQ 来使用,目前应用案例较少,且不方便扩展。对于 RabbitMQ 和 Redis 的入队和出队操作,各执行 100 万次,每 10 万次记录一次执行时间。
测试数据分为 128Bytes、512Bytes、1K 和 10K 四个不同大小的数据。
实验表明:入队时,当数据比较小时 Redis 的性能要高于 RabbitMQ,而如 果数据大小超过了 10K,Redis 则慢的无法忍受;出队时,无论数据大小,Redis 都表现出非常好的性能,而 RabbitMQ 的出队性能则远低于 Redis。
5.Kafka/Jafka
Kafka 是 Apache 下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而 Jafka 是在 Kafka 之上孵化而来的,即 Kafka 的一个升级版。
具有以下特性:
快速持久化,可以在 O(1)的系统开销下进行消息持久化;
高吞吐,在一台普通的服务器上既可以达到 10W/s 的吞吐速率;完全的分布式系统,Broker、Producer、Consumer 都原生自动支持分布式,自动实现负载均衡;
支持 Hadoop 数据并行加载,对于像 Hadoop 的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。
Kafka 通过 Hadoop 的并行加载机制统一了在线和离线的消息处理。Apache Kafka 相对于 ActiveMQ 是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。
何时需要消息队列
当你需要使用消息队列时,首先需要考虑它的必要性。
可以使用 mq 的场景有很多,最常用的几种:
做业务解耦
最终一致性
广播
错峰流控等
反之,如果需要强一致性,关注业务逻辑的处理结果,则 RPC 显得更为合适。
消息队列使用场景
1.解耦
解耦是消息队列要解决的最本质问题。所谓解耦,简单点讲就是一个事务,只关心核心的流程。而需要依赖其他系统但不那么重要的事情,有通知即可,无需等待结果。换句话说,基于消息的模型,关心的是“通知”,而非“处理”。
举一个例子,关于订单系统,订单最终支付成功之后可能需要给用户发送短信积分什么的,但其实这已经不是我们系统的核心流程了。
如果外部系统速度偏慢(比如短信网关速度不好),那么主流程的时间会加长很多,用户肯定不希望点击支付过好几分钟才看到结果。那么我们只需要通知短信系统“我们支付成功了”,不一定非要等待它立即处理完成。
2.最终一致性
最终一致性指的是两个系统的状态保持一致,要么都成功,要么都失败。
当然有个时间限制,理论上越快越好,但实际上在各种异常的情况下,可能会有一定延迟达到最终一致状态,但最后两个系统的状态是一样的。
业界有一些为“最终一致性”而生的消息队列,如:
Notify(阿里)
QMQ(去哪儿)等
其设计初衷,就是为了交易系统中的高可靠通知。
以一个银行的转账过程来理解最终一致性,转账的需求很简单,如果 A 系统扣钱成功,则 B 系统加钱一定成功。反之则一起回滚,像什么都没发生一样。
然而,这个过程中存在很多可能的意外:
A 扣钱成功,调用 B 加钱接口失败。
A 扣钱成功,调用 B 加钱接口虽然成功,但获取最终结果时网络异常引起超时。
A 扣钱成功,B 加钱失败,A 想回滚扣的钱,但 A 机器 down 机。
可见,想把这件看似简单的事真正做成,真的不那么容易。
所有跨 VM 的一致性问题,从技术的角度讲通用的解决方案是:
强一致性,分布式事务,但落地太难且成本太高,后文会具体提到。
最终一致性,主要是用“记录”和“补偿”的方式。在做所有的不确定的事情之前,先把事情记录下来,然后去做不确定的事情,结果可能是:成功、失败或是不确定,“不确定”(例如超时等)可以等价为失败。成功就可以把记录的东西清理掉了,对于失败和不确定,可以依靠定时任务等方式把所有失败的事情重新搞一遍,直到成功为止。
回到刚才的例子,系统在 A 扣钱成功的情况下,把要给 B“通知”这件事记录在库里(为了保证最高的可靠性可以把通知 B 系统加钱和扣钱成功这两件事维护在一个本地事务里),通知成功则删除这条记录,通知失败或不确定则依靠定时任务补偿性地通知我们,直到我们把状态更新成正确的为止。
整个这个模型依然可以基于 RPC 来做,但可以抽象成一个统一的模型,基于消息队列来做一个“企业总线”。
具体来说,本地事务维护业务变化和通知消息,一起落地(失败则一起回滚),然后 RPC 到达 broker,在 broker 成功落地后,RPC 返回成功,本地消息可以删除。否则本地消息一直靠定时任务轮询不断重发,这样就保证了消息可靠落地 broker。
broker 往 consumer 发送消息的过程类似,一直发送消息,直到 consumer 发送消费成功确认。
我们先不理会重复消息的问题,通过两次消息落地加补偿,下游是一定可以收到消息的。然后依赖状态机版本号等方式做判重,更新自己的业务,就实现了最终一致性。
最终一致性不是消息队列的必备特性,但确实可以依靠消息队列来做最终一致性的事情。
另外,所有不保证 100%不丢消息的消息队列,理论上无法实现最终一致性。好吧,应该说理论上的 100%,排除系统严重故障和 bug。
像 Kafka 一类的设计,在设计层面上就有丢消息的可能(比如定时刷盘,如果掉电就会丢消息)。哪怕只丢千分之一的消息,业务也必须用其他的手段来保证结果正确。
2.广播
消息队列的基本功能之一是进行广播。
如果没有消息队列,每当一个新的业务方接入,我们都要联调一次新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。
比如本文开始提到的产品中心发布产品变更的消息,以及景点库很多去重更新的消息,可能“关心”方有很多个,但产品中心和景点库只需要发布变更消息即可,谁关心谁接入。
评论