所有中间件都是为了解决特定领域的某个问题。
MsgBroker解决:分布式系统的消息传递的问题。
首先先了解消息中间件的一些背景知识
1.消息中间件
1.1消息中间件是什么
- 消息中间件用于分布式系统的消息传递
- 利用高效可靠的消息传递机制进行与平台无关的数据交流
- 基于数据通信来进行分布式系统的集成
- 通过提供消息传递和消息排队模型
1.2为什么要有消息中间件
原本的RPC接口调用有什么问题
- 客户端服务进程和服务端服务进程的生命周期紧密耦合,紧密耦合带来三个问题
- 客户端进程和服务端进程必须都正常运行,如果由于服务方的对象错误或者网络错误等引起的请求不可达,客户端的请求就会异常。
- 后续客户端有其他业务变更的时候服务端的服务也得做出相应调整。
- 同步处理导致服务端需要等待客户端处理完后才能处理完,处理时间慢。
消息中间件解决了什么问题
- 客户端和服务端的对象生命周期的松耦合关系:客户端进程和服务端对象进程不要求都正常运行,如果由于服务对象崩溃或者网络故障导致客户的请求不可达,客户不会接收到异常,消息中间件能保证消息不会丢失。
- 用异步通信模式:发送消息者可以在发送消息后进行其它的工作,不用等待接收者的回应,而接收者也不必在接到消息后立即对发送者的请求进行处理;
一个例子
一个账单查询Case,基本业务逻辑:
- 账单服务:检索数据库,获取指定账户的账单记录。
- 风险控制服务:记录用户的检索行为,为风险控制提供数据积累。
- 短信通知服务:发送短信到用户手机,通知用户其账单被查询事件。
方案1,同步RPC调用
方案2,异步消息中间件
方案2对比方案1的优点:
- 账单服务处理这个case时间减少了许多,提高了吞吐量
- 账单服务,风险控制服务和短信通知服务完全解耦,后续有其他业务变更时,账单服务不需要变更
- 风险控制服务和短信通知服务不可用时,不会导致账单服务不可用
1.3消息中间件应用场景
- 系统集成:系统集成除了通过接口还可以通过消息。不同点:接口通过模块间直接调用;使用消息中间件的系统通过消息模块通信,通过消息来分解系统。
- 业务解耦:如果模块之间不存在直接调用,那么修改或新增模块对其他模块影响最小。增加消息中间件后,业务模块仅需关注自己的业务逻辑和如何将消息发送给消息服务器,消息由中间件存储和分发。
- 异步处理:多个应用对消息中间件的同一消息处理,应用间并发处理消息,比起串行处理,减少处理时间。
- 改善系统性能(削峰填谷):比如上游系统的吞吐能力高于下游系统,在流量洪峰时可能会冲垮下游系统,消息中间件可以在流量高峰时堆积消息,而在峰值过去后下游系统慢慢消费消息解决流量洪峰的问题
- 跨平台:跨平台使用,降低网络协议的复杂性
- 事务型消息:基于消息的通信是可靠的,消息不会丢失。可以发送事务性消息,而且大多数消息中间件都提供将消息持久化到磁盘的功能。
1.4消息中间件主要特点
- 可靠性:基于消息的通信是可靠的,大多数消息中间件都提供将消息持久化到磁盘的功能,可以发送离线消息,消息不会丢失。在分布式事务中担当重要的角色。
- 异步:基于事件驱动架构,将调用异步化
- 分布式:消息中间件都是分布式的
1.5两种模式
点对点模式 (point to point)
点对点模式下包括三个角色:
- 消息队列
- 发送者 (生产者)
-
接收者(消费者)
消息发送者生产消息发送到queue中,然后消息接收者从queue中取出并且消费消息。消息被消费以后,queue中不再有存储,所以消息接收者不可能消费到已经被消费的消息。
点对点模式特点:
- 每个消息只有一个接收者(Consumer)(即一旦被消费,消息就不再在消息队列中);
- 发送者和接收者间没有依赖性,发送者发送消息之后,不管有没有接收者在运行,都不会影响到发送者下次发送消息;
- 接收者在成功接收消息之后需向队列应答成功,以便消息队列删除当前接收的消息;
发布/订阅模式(publish/subcribe)
发布/订阅模式下包括三个角色:
- 角色主题(Topic)
- 发布者(Publisher)
- 订阅者(Subscriber)
发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。
发布/订阅模式特点:
- 每个消息可以有多个订阅者;
- 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
- 为了消费消息,订阅者需要提前订阅该角色主题,并保持在线运行;
1.6消费消息的推拉模式
- 推模式push:由消息中间件主动发消息给消费者
- 拉模式pull:消费者主动从消息中间件拉取消息
- 比较:采用push模式,可以尽可能快的把消息发给消费者,但是如果消费者处理一条消息能力较弱(处理时间长),消息中间件会不断的发消息给消费者,到时消费者的缓存区溢出;采用pull模式,可能会增加消息的延迟。
1.7引入消息中间件需要注意点
- 复杂性:消息中间件都是分布式的,引入分布式会大大增加系统复杂度,在不同主机、不同进程之间的调用和调试,会带来更多的不稳定性。分布式系统还会增加对外部系统的依赖。即使自己的系统没有问题,也可能会因为依赖系统出问题而导致系统不稳定。
- 异步调用:带来的业务交互的改变,有一定的操作延迟。
- 同步调用:尽管消息中间件也可用于同步调用,但这并不是它的长项,同步调用可以考虑使用HTTP、NIO等其他方式。
然后再看蚂蚁的消息中间件msgbroker是怎么做的,重点讲述MsgBroker的事务型消息
2.MsgBroker
2.1MsgBroker原理架构图
2.2MsgBroker组成
- 消息发布者Publisher:发送消息的应用系统,发送消息到可靠消息组件 (MsgBroker)。
- 可靠消息组件MsgBroker:即MsgBroker,负责接收发布者发送的消息,根据消息类型和订阅关系将消息分发投递到一个或多个消息订阅者。
- 消息订阅者Subscriber:指订阅消息的应用系统,收到的消息来自可靠消息组件 (MsgBroker)。
- 消息类型Message Type:一种消息类型由 TOPIC 和 EVENTCODE 唯一标识。
- 订阅关系Binding:用来描述一种消息类型被订阅者订阅。
2.3Msgbroker特性
- 可靠性:MsgBroker确保消息可以投递到订阅者,发送者和订阅者都不必担心消息丢失
- 事务一致性:保证消息和本地数据库事务的一致性
- 不保证消息不重复:所以订阅者要做好幂等
- 不保证消息投递顺序:如果订阅者有需求需要自己实现
2.4普通消息
普通消息:较简单的发送和投递两个过程
2.5事务型消息
什么是事务型消息
一种特殊类型的消息:消息中间件收到消息发布者发布的消息后不会立刻投递给消费者,而是根据发布者应用的数据库事务状态来决定是否投递,如果数据库事务是提交,就投递;是回滚就不投递。
为什么要设计事务型消息
为了保证在分布式系统中数据库变更之间以及数据库变更和业务处理保持事务一致性。
事务型消息如何保证数据一致性
事务型消息通过“二阶段”来保证一致性
- 第一个阶段:当发送端像发送普通消息一样,将消息发送给Broker,Broker会将该条记录保存在数据库中,并将其事务状态设置为未知状态。 入库操作完成后,Broker会向发送端返回一个消息确认的信息,一阶段结束。
- 第二个阶段:发送端的代码包在事务模板中,当这个事务完结的时候,发送端会将本地事务的执行结果(提交/回滚)发送给Broker,Broker对结果做出判断。提交则发送,回滚则不发送
思考
- 为什么发送消息要在事务操作之前?
- 如果Msgbroker一直收不到事务的执行结果状态(提交/回滚)时要怎么办?
- 第一个问题,如果顺序反过来,先执行事务,事务执行完成提交后再发送消息,那么消息发送失败就会导致本地事务回滚,这个肯定是不合理的,因为事务回滚比重发消息代价高很多
- 第二个问题,如果MsgBroker一直没收到事务的执行结果,这个在分布式系统里属于异常情况,也是有可能发生的,比如 说网络异常等,而Msgbroker作为一个严谨的消息中间件也考虑到了这种异常情况,设计了“回查阶段”,这个阶段只会在事务“提交/回滚消息”失败时才会被触发。
总结
- 消息中间件MsgBroker事务型消息通过“二阶段”消息实现
- 事务型消息是否投递与本地事务状态保持一致
- 事务型消息状态回查是为了保证事务型消息的严谨性
事务型消息(事务成功时消息才提交):
事务型消息(事务回滚时不发送消息):
4.几个消息队列比较
电商、金融对事务型消息要求较高,因此MsgBroker重点在于支持事务型消息
MsgBroker | RocketMQ | ActiveMQ | RabbitMQ | KafKa | |
---|---|---|---|---|---|
所属公司社区 | 蚂蚁 | 阿里 | Apache | Mozilla | Apache |
开发语言 | Java | Java | Java | Relang | Scale&Java |
消息消费模式(pull/push) | push | 多协议,均支持 | 多协议,均支持 | 多协议,均支持 | pull |
数据可靠性 | 可靠 | 可靠,同步刷盘,同/异步复制 | 可靠,master/slave | 保证数据不丢,slave备份 | 可靠,replica机制,容错容灾 |
持久化能力 | DB | 磁盘文件 | 内存/文件/DB | DB / 文件 | 磁盘文件 |
是否有序 | 无序 | 有序 | 可以支持有序 | 一个Client才有序 | 多Client有序 |
事务型消息 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
负载均衡 | 支持 | 支持 | 支持 | 支持 | 支持 |
关键场景 | 事务,高可靠 | 高吞吐,高可靠 | 高扩展 | 高并发,高可靠 | 高吞吐,分布式 |
5.知识点回顾
从本地事务到分布式事务到消息队列
5.1事务
事务:一系列指令的集合
满足:ACID原则
- Automicity(原子性):操作这些指令,要么全部成功,要么全部失败
- Consistency(一致性):事务执行使得数据库从一个状态到另一个状态,但是对于整个数据的完整性保持一致
- Isolation(隔离性):在一个事务执行过程中,任何数据变更都只存在于这一个事务中,不会被外界感知,只有事务执行完成后,其他事务才能感知到这个事务引起的数据变更。
- Durability(持久性):事务正确完成后,它带来的数据变更时永久的。
5.2本地事务
本地事务是在单个数据源上进行数据访问和更新,资源由资源管理器本地管理
本地事务特点:
- 一次事务只连接一个支持事务的数据库
- 事务的执行保证ACID原则
- 会用到数据库锁
5.3分布式事务
跨越多个数据源进行数据访问和更新。
随着业务变得复杂,一个单机应用的一个数据库会被拆成多库多表,一个应用也会被拆成多个应用,整个系统处于分布式的状态,这个时候就需要保证分布式架构下的数据一致性。关于分布式事务在之后学习XTS中间件的时候再深入了解。