本文作者magiccao和littleorca来自携程消息队列团队。目前主要从事消息中间件的开发和弹性架构的演进,同时关注网络/性能优化、应用监控和云原生等领域。一、背景QMQ延迟消息是一套以服务的形式独立存在的解决方案,不局限于消息厂商的实现。其架构如下图所示。QMQ延迟消息服务架构延迟消息从生产者投递到延迟服务后,会累积在服务器的本地磁盘中。当延迟消息调度时间到期时,将延迟服务转发给实时Broker,供消费者消费。延时服务采用主从架构,其中Zone代表可用区(一般可以理解为IDC)。为了保证单个可用区故障后历史投递消息的正常调度,会跨可用区部署master和slave。1.1痛点该架构主要存在以下问题:服务有状态,无法弹性伸缩;主节点故障后,需要进行主从切换(自动或手动);缺乏一致性协调器来确保数据的一致性。如果将消息的业务层和存储层分开,则各自协同演化发展,各自专注于擅长的领域。这样消息业务层可以做到无状态,轻松完成容器化改造,具备弹性伸缩的能力;存储层引入分布式文件存储服务,存储服务保证高可用和数据一致性。1.2分布式文件存储的选择对于存储服务的选择,除了高可用和数据一致性的基本特征外,还有一个很关键的点:高容错性和低运维成本。分布式系统最大的特点自然是它对部分节点故障的容忍度。毕竟,任何硬件或软件故障都是不可避免的。所以高容错和低运维成本会成为我们选型的重中之重。Pulsar于2016年由雅虎开源贡献给Apache,以其云原生、低延迟分布式消息队列和流处理平台等标签在开源社区引起了轰动和流行。对其进行相关研究后发现,Pulsar恰好是一个将消息服务与存储分离的架构,而存储层是另一个Apache开源基金会BookKeeper。2.BookKeeper作为一种可扩展、高容错、低延迟的分布式强一致性存储服务,BookKeeper已经被一些公司部署在生产环境中。最佳实践案例包括替换HDFSnamenode和Pulsar的消息存储和消费进度Persistence和对象存储。2.1基本架构BookKeeper基本架构Zookeeper集群用于存储节点发现和元信息存储,提供强一致性保证;Bookie存储节点,提供数据存储服务。在写入和读取过程中,Bookie节点之间不需要相互通信。Bookie启动时,会在Zookeeper集群中注册自己,暴露服务;Client是一种胖客户端类型,负责与Zookeeper集群和BookKeeper集群直接通信,根据元信息完成多副本的写入,保证数据可以重复读取。2.2基本特征a)基本概念Entry:数据载体的基本单元Ledger:条目集合的抽象,类似于文件Bookie:账本集合的抽象,物理存储节点Ensemble:账本bookie集合b)数据读写BookKeeper数据读写bookieclient端通过创建持有一个账本后,可以进行条目写入操作,条目以带状方式分布在enemble的bookie中。条目在客户端进行编号,每个条目会根据设置的份数(Qw)判断写入是否成功;bookie客户端通过打开一个已创建的账本来读取条目,条目的读取顺序与写入输入一致,默认从第一个副本开始读取。如果读取失败,将依次从下一个副本开始重试。c)数据一致性持有可写账本的bookie客户端称为Writer。分布式锁机制保证一个账本全局只有一个Writer。Writer的唯一性保证了数据写入的一致性。Writer内存中维护了一个LAC(LastAddConfirmed),当满足Qw要求时更新LAC。LAC与下一个请求或时间一起保存在bookie副本中。当账本关闭时,它被持久化在元数据存储(zookeeper或etcd)中;持有可读账本的bookie客户端称为reader,一个账本可以有多个Reader。LAC的强一致性保证了不同Reader看到的是统一的数据视图,可以重复读取,从而保证了数据读取的一致性。d)容错的典型故障场景:Writer崩溃或重启,Bookie崩溃。Writer故障,ledger可能没有关闭,导致LAC未知。通过账本恢复机制,关闭账本,修复LAC;Bookie故障,条目写入失败。通过集成替换机制,将新的入口路由信息更新到MetadataStore,保证新数据能够及时成功写入。历史数据通过bookierecover机制,满足Qw副本的要求,巩固了历史数据读取的可靠性。至于副本所在的所有bookie节点的故障场景,只能等待修复。e)将新扩展的bookie负载均衡到集群中。当一个新的账本被创建时,流量会自动平衡。2.3上海大区(region)存在多个可用区(az,availablezone)用于同城多中心容灾,每两个可用区或两个可用区的网络延迟小于2ms。在这种网络架构下,多个副本分散在不同的az可接受的高可用性解决方案中。BookKeeper的zone-awareensemblereplacementstrategy就是针对这种场景的解决方案。基于Zone-aware策略的同城多中心容灾启用Zone-aware策略有两个限制条件:a)E%Qw==0;b)Qw>minNumOfZones。其中E表示合奏的大小,Qw表示副本数,minNumOfZones表示合奏中的最小区域数。例如下面的例子:minNumOfZones=2desiredNumZones=3E=6Qw=3[z1,z2,z3,z1,z2,z3]故障前每条数据三份,分布在三个可用区;当z1失败时,将生成满足minNumOfZones约束的新系综:[z1,z2,z3,z1,z2,z3]->[z3,z2,z3,z3,z2,z3]。显然,三副本的每份数据仍然会分布在两个可用区中,仍然可以容忍一个可用区故障。DNSResolver客户端选择bookie组成ensemble时,需要通过IP解密对应的zone信息,用户需要自己实现一个resolver。考虑到zone之间的网段被认为是规划好的,不会重叠,所以我们在落地的时候,简单实现了一个子网解析器,可以动态配置生效。该示例显示了精确IP匹配的实现。publicclassConfigurableDNSToSwitchMappingextendsAbstractDNSToSwitchMapping{privatefinalMap
