一篇文了解分布式队列编程:从模型、实战到优化(6)
高可用性的消息中间件应该具备如下特征:
高可靠的消息中间件应该确保从发送者接收到的消息不会丢失.中间件代理服务器的宕机并不是小概率事件,所以保存在内存中的消息很容易发生丢失.大部分的消息中间件都依赖于消息的持久化去降低消息丢失损失,即将接收到的消息写入磁盘.即使提供持久化,仍有两个问题需要考虑:
确认机制本质上是通讯的握手机制(Handshaking).如果没有该机制,消息在传输过程中丢失将不会被发现.高敏感的消息要求选取具备确认机制的消息中间件.当然如果没有接收到消息中间件确认完成的指令,应用程序需要决定如何处理.典型的做法有两个:
客户端接口所支持语言采用现存消息中间件就意味着避免重复造轮子.如果某个消息中间件未能提供对应语言的客户端接口,则意味着极大的成本和兼容性问题. 投递策略(Delivery policies)投递策略指的是一个消息会被发送几次.主要包含三种策略:最多一次(At most Once )、最少一次(At least Once)、仅有一次(Exactly Once). 在实际应用中,只考虑消息中间件的投递策略并不能保证业务的投递策略,因为接收者在确认收到消息和处理完消息并持久化之间存在一个时间窗口.例如,即使消息中间件保证仅有一次(Exactly Once),如果接收者先确认消息,在持久化之前宕机,则该消息并未被处理. 从应用的角度,这就是最多一次(At most Once).反之,接收者先处理消息并完成持久化,但在确认之前宕机,消息就要被再次发送,这就是最少一次(At least Once). 如果消息投递策略非常重要,应用程序自身也需要仔细设计. 消费者优化消费者是分布式队列编程中真正的数据处理方,数据处理方最常见的挑战包括:有序性、串行化(Serializability)、频次控制、完整性和一致性等. 挑战有序性在很多场景下,如何保证队列信息的有序处理是一个棘手的问题.如下图,假定分布式队列保证请求严格有序,请求ri2和ri1都是针对同一数据记录的不同状态,ri2的状态比ri1的状态新.T1、T2、T3和T4代表各个操作发生的时间,并且 T1 < T2 < T3 < T4(”<“代表早于). 采用多消费者架构,这两条记录被两个消费者(Consumer1和Consumer2)处理后更新到数据库里面.Consumer1虽然先读取ri1但是却后写入数据库,这就导致,新的状态被老的状态覆盖,所以多消费者不保证数据的有序性. 串行化很多场景下,串行化是数据处理的一个基本需求,这是保证数据完整性、可恢复性、事务原子性等的基础.为了在并行计算系统里实现串行化,一系列的相关理论和实践算法被提出.对于分布式队列编程架构,要在在多台消费者实现串行化非常复杂,无异于重复造轮子. 频次控制有时候,消费者的消费频次需要被控制,可能的原因包括:
完整性和一致性完整性和一致性是所有多线程和多进程的代码都面临的问题.在多线程或者多进程的系统中考虑完整性和一致性往往会大大地增加代码的复杂度和系统出错的概率. 单例服务优化几乎所有串行化理论真正解决的问题只有一个:性能. 所以,在性能允许的前提下,对于消费者角色,建议采用单实例部署.通过单实例部署,有序性、串行化、完整性和一致性问题自动获得了解决.另外,单实例部署的消费者拥有全部所需信息,它可以在频次控制上采取很多优化策略. 天下没有免费的午餐.同样,单实例部署并非没有代价,它意味着系统可用性的降低,很多时候,这是无法接受的.解决可用性问题的最直接的思路就是冗余(Redundancy).最常用的冗余方案是Master-slave架构,不过大部分的Master-slave架构都是Active/active模式,即主从服务器都提供服务. 例如,数据库的Master-slave架构就是主从服务器都提供读服务,只有主服务器提供写服务.大部分基于负载均衡设计的Master-slave集群中,主服务器和从服务器同时提供相同的服务.这显然不满足单例服务优化需求. 有序性和串行化需要Active/passive架构,即在某一时刻只有主实例提供服务,其他的从服务等待主实例失效.这是典型的领导人选举架构,即只有获得领导权的实例才能充当实际消费者,其他实例都在等待下一次选举.采用领导人选举的Active/passive架构可以大大缓解纯粹的单实例部署所带来的可用性问题. (编辑:ASP站长网) |