近期项目的事一直很忙,发现要学习只能自己挤时间。最近大致看了下淘宝曾宪杰写的《大型网站系统与Java中间件实践》,感觉还是受益匪浅。这里结合自己的体会把可靠消息这块总结一下。
可靠消息,顾名思义就是在前置交易成功后系统能保证一定把消息发送出去,另一方面,如果前置交易失败,消息一定不能发送。可靠消息和事务交易还是有区别的,事务消息一般是通过事务管理器来保证消息系统和应用服务的数据库系统的一致性。由于消息系统和数据库系统一般是两个系统,所以事务消息一般采用两阶段提交来做,而两阶段提交在异常情况下问题很多,实际系统一般很少采用。可靠消息是事务消息的简化版,它提供了最终的一致性,同时有避免了厚重的事务管理器。
注: 本文介绍的可靠消息专指发送端一致性的保证,即保证消息一定能发送到消息中间件或接收端。对于接收端的消息一致性,即接收端一定能收到并处理消息不做阐述。接收端消息一致性的实现方式有很多,很多消息中间件的文档中都会提到,包括但不限于如下两种(1)接收端消息处理成功才认为消息接收成功,接收端进行幂等处理(进行判重或业务逻辑支持重复);(2)接收端采用拉取方式从中间件主动获取消息。
下面简单介绍一下可靠消息的实现方式:
1、本地事务方式
这种是最简单的方式,即在同一个本地事务中将待发送的消息放在单独的表中,并更新(或新增)前置交易的状态。详细处理过程如下:
联机交易处理过程:
- 启动事务
1.1 记录“待发送消息表”,状态为“待发送”
1.2 完成业务处理,更新(或新增)前置交易的状态,例如记录业务流水等
- 提交事务
- 进行异步消息发送功能。在异步发送消息确认成功后,将“待发送消息表”状态改为“发送成功”。
异常情况处理:
- 启动自动任务定时扫描“待发送消息表”中的长期“待发送”的消息,然后进行消息发送。
- 对于多次重试发送消息均失败的情况(可能消息系统故障,或应用程序问题导致消息格式不正确等),将“待 发送消息表”状态置为“发送失败”,后续由人工处理。
上述这种本地事务方式的优点是:
- 由本地事务保证发送消息和记录交易状态的一致性,而无需应用提供单独的查询交易确定交易的状态;
- 相比后面处理方案,无需考虑应用本身阻塞的问题,可保证消息永远不会漏发;
- 由自动任务保证消息最终一定会发送成功,从而保证了最终一致性。
缺点是:
- 需要保证消息待发送表和业务流水信息表在一个数据库中,在分布式环境下,相当于每个数据库上都需要有这样一套表;
- 需要应用本身支持事务,且需要应用明确记录待发送表,或通过框架隐式将业务更新和消息记录进行事务耦合;
- 需单独的自动任务对待发送消息表进行轮训、更新,增加了数据库的开销。
2、预发送及确认机制
这是曾宪杰书中提到的方式,即在应用记录前置交易状态之前、之后反别发送预发送消息、确认发送消息。详细处理过程如下:
联机交易处理:
1. 发送预发送消息:接收方一般为消息中间件或其他单独消息服务,此系统需保证持久化后再返回成功。消息中需包含应用交易标示信息,以便能在后面确认应用是否处理成功。
2. 完成业务处理,更新(或新增)前置交易的状态,例如记录业务流水等。
3. 发送确认消息:接收方收到消息后进行消息发送,并更新本地的消息状态为发送成功。此步骤可以异步来做,以减少联机交易等待时间。
异常情况处理:
- 消息中间件或消息服务对于长时间处于待发送的消息,需要调应用提供的查询交易确认实际交易结果,如实际交易成功则发送消息,如失败则取消发送。
- 需要注意应用查询交易返回“处理中”或“无此流水”的情况,这时由于应用本身可能存在阻塞,故不能盲目将消息置为失败。建议置入异常消息,进行人工处理,或设置很长的超时时间,超时后置为失败。
- 对于消息服务实际发送消息时的失败,需要重试进行发送,对于长时间发送均失败的情况,需要置入异常队列,由人工处理。
上述处理方式的优点是:
- 只需要应用增加一个预发送的消息,即可保证消息可靠性
- 由单独的消息中间件来保证消息发送,避免了与应用过多耦合
- 可以严格保证消息的最终一致性,而无需引入事务的开销
- 可扩展性好,各组件功能切分明确,对应用侵入性小
缺点是:
- 当应用阻塞时无法断定交易即为失败,超时时间的设定有经验的因。为防止事务挂起,异常情况只能人工确认。系统无法做到消息发送和业务功能的强一致。
- 应用需额外开发查询交易,来确认交易的结果。此交易需要保证幂等性。
3、接收方定期校对方式
当发送方无法或不愿意进行改动以保证消息一致性时,可以通过接收方的定期校对来保证消息的最终一致性。大体实现逻辑如下:
联机交易处理:
- 完成业务处理,更新(或新增)前置交易的状态,例如记录账务流水等。
- 发送消息。(发送消息可能会丢失,发送方不进行任何保证)。接受方收到消息进行相应处理(异步)。
异常情况处理:
- 当接收方没有收到消息时或收到消息但业务处理异常时,需要根据定时策略,定时调用发送方的查询交易进行查询并校对,对遗漏的消息进行补偿处理。
上述处理方式的优点:
- 对发送方无约束,其可以不保证发送消息一定发送成功;
- 可以适用于消息发送渠道不可靠或即使收到消息也无法保证消息被处理的情况。比如移动手机端的消息推送;
- 无需消息中间件支持。
缺点是:
- 定期校对功能的实现开销,比对哪些消息没处理当交易量大时并不是一个简单的工作。
- 需要发送方提供业务查询或下载的功能,以便确认漏处理的消息。
- 接收方需要进行消息幂等处理(其实不算缺点了,上面两个方案也需要进行此保证)
上面简单介绍了可靠消息的设计思路,大家可结合自身应用的特点进行选择。可靠消息本身是有额外开销的,我们能做的只能尽量减少对应用的影响。实际中消息中间件的实现还有其他考虑的内容,比如消息存储、性能、应用如何处理中间件故障等等,大家可以参考曾宪杰书中的内容。