消息队列如何解决消息一致性问题

典型消息中间件的架构

消息中间件的价值:就是异步、解耦合、简单化分布式系统,
减轻业务和数据库的负担,业务只需要最简单的事情系统解耦合、减轻了系统的依赖

一般来讲,设计消息队列的整体思路是先构建一个整体的数据流,
例如Producer发送给Broker,Broker发送给consumer,consumer回复消费确认,Broker删除/备份消息等。
利用RPC将数据流串起来。
然后考虑RPC的高可用性,尽量做到无状态,方便水平扩展。
之后考虑如何承载消息堆积,然后在合适的时机投递消息,而处理堆积的最佳方式,就是存储,
存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。
为了实现广播功能,我们必须要维护消费关系,可以利用zk/config server等保存消费关系。

当设计消息中间件时需要关心什么

消息的顺序保证
消息中间件的扩展性、可靠性
业务操作与消息发送一致性
多集群订阅消息

消息队列的消息一致性问题

消息发送一致性是指产生消息的业务动作与消息发送的一致,如果业务操作成功了,那么对应的消息一定要发送出去,否则就丢失消息。而另一方面,如果这个业务行为没有发生或者失败,那么就不应该把消息发出去。

考虑如下的操作:

void test(){
  //业务操作
  //列如写数据库,调用服务等
  //发送消息
}

void test(){
  //发送消息
  //业务操作
  //列如写数据库,调用服务等
}

上面伪代码不管哪一步出现问题了,都很难保持一致性。

 

使用分布式事务实现消息一致性

JMS的API有很多XA开头的接口,JMS中定义的XA系列的接口就是为了实现分布式事务的支持,使用XA系列的接口实现,可以保证发送消息给消息中间件及业务操作这两件事情的事务,使用分布式事务虽然保证了消息一致性,但是有如下问题:

1.引入了分布式事务,这会带来一些开销并增加复杂性。
2.对于业务操作有限制,要求业务操作的资源必须支持XA协议,才能够与发送消息一起来做分布式事务。
这会成为一个限制,因为并不是所有需要与发送消息一起做成分布式事务的业务操作都支持XA协议。

去哪儿网的消息中间件业务实践

(1)结合数据库事务和消息表保证一致性

将订单持久化在同一个事务里,共享订单操作的数据库连接,在这个连接里将消息持久化到同一个实例里的消息库里,
然后在事务提交之后将消息发送到消息的 server。如果事务回滚了,消息就不会发送出去了。

流程图 :

开启事务;
业务操作,比如说订单进行持久化等等动作;
生成消息,并存储,这和业务操作是在同一个事务里;
事务提交;
消息真正发送出去。

消息如果发送成功了,会将消息表里的消息删除,而此时如果消息发送失败了,后台有一个任务会把消息表里面发送失败的消息重新进行发送,这样最终达到一致性,保证业务操作成功了,消息一定能发出去。

(2)Producer向Broker两次确认消息状态

每次发送新的消息,直接发给Broker,这个消息发给Broker并不立即将消息投递出去。

然后做本地的业务操作,再调Broker的接口,这一步真正把消息发送出去。
如果这个时候,即使第二步操作成功,第三步发送失败了,第一步发送给Broker的消息就是一个未决状态。

Broker反过来询问 Producer,那条消息是发还是不发出去呢?

这种模型就不需要一个将一个消息库放在业务库实例了,比较灵活,成本也更低些。
但是业务使用的复杂度可能要高一些,需要提供一个接口供 Broker 反查。

 

 

参考

消息队列设计精要

设计消息中间件时我关心什么?(解密电商数据一致性与完整性实现)

《大型网站系统与中间件实践 》

posted @ 2016-08-30 11:11  邴越  阅读(5246)  评论(0编辑  收藏  举报