文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

携程基于BookKeeper的延迟消息架构落地实践

2024-12-02 01:33

关注

一、背景

QMQ延迟消息是以服务形式独立存在的一套不局限于消息厂商实现的解决方案,其架构如下图所示。

QMQ延迟消息服务架构

延迟消息从生产者投递至延迟服务后,堆积在服务器本地磁盘中。当延迟消息调度时间过期后,延迟服务转发至实时Broker供消费方消费。延迟服务采用主从架构,其中,Zone表示一个可用区(一般可以理解成一个IDC),为了保证单可用区故障后,历史投递的待调度消息正常调度,master和slave会跨可用区部署。

1.1 痛点

此架构主要存在如下几点问题:

如果将消息的业务层和存储层分离出来,各自演进协同发展,各自专注在擅长的领域。这样,消息业务层可以做到无状态化,轻松完成容器化改造,具备弹性扩缩容能力;存储层引入分布式文件存储服务,由存储服务来保证高可用与数据一致性。

1.2 分布式文件存储选型

对于存储服务的选型,除了基本的高可用于数据一致性特点外,还有至关重要的一点:高容错与低运维成本特性。分布式系统最大的特点自然是对部分节点故障的容忍能力,毕竟任何硬件或软件故障是不可百分百避免的。因此,高容错与低运维成本将成为我们选型中最为看重的。

2016年由雅虎开源贡献给Apache的Pulsar,因其云原生、低延迟分布式消息队列与流式处理平台的标签,在开源社区引发轰动与追捧。在对其进行相关调研后,发现恰好Pulsar也是消息业务与存储分离的架构,而存储层则是另一个Apache开源基金会的BookKeeper。

二、BookKeeper

BookKeeper作为一款可伸缩、高容错、低延迟的分布式强一致存储服务已被部分公司应用于生产环境部署使用,最佳实践案例包括替代HDFS的namenode、Pulsar的消息存储与消费进度持久化以及对象存储。

2.1 基本架构

BookKeeper基本架构

2.2 基本特性

a)基本概念

b)数据读写

BookKeeper数据读写

bookie客户端通过创建而持有一个ledger后便可以进行entry写入操作,entry以带状方式分布在enemble的bookie中。entry在客户端进行编号,每条entry会根据设置的副本数(Qw)要求判定写入成功与否;

bookie客户端通过打开一个已创建的ledger进行entry读取操作,entry的读取顺序与写入保持一致,默认从第一个副本中读取,读取失败后顺序从下一个副本重试。

c)数据一致性

持有可写ledger的bookie客户端称为Writer,通过分布式锁机制确保一个ledger全局只有一个Writer,Writer的唯一性保证了数据写入一致性。Writer内存中维护一个LAC(Last Add Confirmed),当满足Qw要求后,更新LAC。LAC随下一次请求或定时持久化在bookie副本中,当ledger关闭时,持久化在Metadata Store(zookeeper或etcd)中;

持有可读ledger的bookie客户端称为Reader,一个ledger可以有任意多个Reader。LAC的强一致性保证了不同Reader看到统一的数据视图,亦可重复读,从而保证了数据读取一致性。

d)容错性

典型故障场景:Writer crash或restart、Bookie crash。

Writer故障,ledger可能未关闭,导致LAC未知。通过ledger recover机制,关闭ledger,修复LAC;

Bookie故障,entry写入失败。通过ensemble replace机制,更新一条新的entry路由信息到Metadata Store中,保障了新数据能及时成功写入。历史数据,通过bookie recover机制,满足Qw副本要求,夯实了历史数据读取的可靠性。至于副本所在的所有bookie节点全部故障场景,只能等待修复。

e)负载均衡

新扩容进集群的bookie,当创建新的ledger时,便自动均衡流量。

2.3 同城多中心容灾

上海区域(region)存在多个可用区(az,available zone),各可用两两间网络延迟低于2ms,此种网络架构下,多副本分散在不同的az间是一个可接受的高可用方案。BookKeeper基于Zone感知的ensemble替换策略便是应对此种场景的解决方案。

基于Zone感知策略的同城多中心容灾

开启Zone感知策略有两个限制条件:a)E % Qw == 0;b)Qw > minNumOfZones。其中E表示ensemble大小,Qw表示副本数,minNumOfZones表示ensemble中的最小zone数目。

譬如下面的例子:

minNumOfZones = 2
desiredNumZones = 3
E = 6
Qw = 3
[z1, z2, z3, z1, z2, z3]

故障前,每条数据具有三副本,且分布在三个可用区中;当z1故障后,将以满足minNumOfZones限制生成新的ensemble:[z1, z2, z3, z1, z2, z3] -> [z3, z2, z3, z3, z2, z3]。显然对于三副本的每条数据仍将分布在两个可用区中,仍能容忍一个可用区故障。

DNSResolver

客户端在挑选bookie组成ensemble时,需要通过ip反解出对应的zone信息,需要用户实现解析器。考虑到zone与zone间网段是认为规划且不重合的,因此,我们落地时,简单的实现了一个可动态配置生效的子网解析器。示例给出的是ip精确匹配的实现方式。

public class ConfigurableDNSToSwitchMapping extends AbstractDNSToSwitchMapping {

private final Map<String, String> mappings = Maps.newHashMap();

public ConfigurableDNSToSwitchMapping() {
super();
mappings.put("192.168.0.1", "/z1/192.168.0.1"); // /zone/upgrade domain
mappings.put("192.168.1.1", "/z2/192.168.1.1");
mappings.put("192.168.2.1", "/z3/192.168.2.1");
}

@Override
public boolean useHostName() {
return false;
}

@Override
public List<String> resolve(List<String> names) {
List<String> rNames = Lists.newArrayList();
names.forEach(name -> {
String rName = mappings.getOrDefault(name, "/default-zone/default-upgradedomain");
rNames.add(rName);
});
return rNames;
}
}

可配置化DNS解析器示例

数据副本分布在单zone

当某些原因(譬如可用区故障演练)导致只有一个可用区可用时,新写入的数据的全部副本都将落在单可用区,当故障可用区恢复后,仍然有部分历史数据只存在于单可用区,不满足多可用区容灾的高可用需求。

AutoRecovery机制中有一个PlacementPolicy检测机制,但缺少恢复机制。于是我们打了个patch,支持动态机制开启和关闭此功能。这样,当可用区故障恢复后可以自动发现和修复数据全部副本分布在单可用区从而影响数据可用性的问题。

三、弹性架构落地

引入BookKeeper后,延迟消息服务的架构相对漂亮不少。消息业务层面和存储层面完全分离,延迟消息服务本身无状态化,可以轻易伸缩。当可用区故障后,不再需要主从切换。

延迟消息服务新架构

3.1 无状态化改造

存储层分离出去后,业务层实现无状态化成为可能。要达成这一目标,还需解决一些问题。我们先看看BookKeeper使用上的一些约束:

上述两个主要问题,决定我们实现无状态和弹性扩缩容时,必需自行解决读写资源分配的问题。为此,我们引入了任务协调器。

我们首先将存储资源进行分片管理,每个分片上都支持读写操作,但同一时刻只能有一个业务层节点来读写。如果我们把分片看作资源,把业务层节点看作工作者,那么任务协调器的主要职责为:

由于是分布式环境,协调器自身完成上述职责时需要保证分布式一致性,当然还要满足可用性要求。我们选择了基于ZooKeeper进行选主的一主多从式架构。

如图所示,协调器对等部署在业务层应用节点中。运行时,协调器通过基于ZooKeeper的leader竞选机制决出leader节点,并由leader节点负责前述任务分配工作。

协调器选举的实现参考ZooKeeper官方文档,这里不再赘述。

3.2 持久化数据

原有架构将延迟消息根据调度时间按每10分钟桶存储在本地,时间临近的桶加载到内存中,使用HashedWheelTimer来调度。该设计存在两个弊端:

弊端1的话,单机本地10万+文件还不算多大问题,但改造后这些桶信息以元信息的方式存储在ZooKeeper上,我们的实现方案决定了每个桶至少占用3个ZooKeeper节点。假设我们要部署5个集群,平均每个集群有10个分片,每个分片有10万个桶,那使用的ZooKeeper节点数量就是1500万起,这个量级是ZooKeeper难以承受的。

弊端2则无论新老架构,都是个潜在问题。一旦某个10分钟消息量多一些,就可能导致消息延迟。往内存加载时,应该有更细的颗粒度才好。

基于以上问题分析,我们参考多级时间轮调度的思路,略加变化,设计了一套基于滑动时间分桶的多级调度方案。

如上表所示,最大的桶是1周,其次是1天,1小时,1分钟。每个级别覆盖不同的时间范围,组合起来覆盖2年的时间范围理论上只需286个桶,相比原来的10万多个桶有了质的缩减。

同时,只有L0m这一级调度器需要加载数据到HashedWheelTimer,故而加载粒度细化到了1分钟,大大减少了因不能完整加载一个桶而导致的调度延迟。

多级调度器以类似串联的方式协同工作。

每一级调度器收到写入请求时,首先尝试委托给其上级(颗粒度更大)调度器处理。如果上级接受,则只需将上级的处理结果向下返回;如果上级不接受,再判断是否归属本级,是的话写入桶中,否则打回给下级。

每一级调度器都会将时间临近的桶打开并发送其中的数据到下一级调度器。比如L1h发现最小的桶到了预加载时间,则把该桶的数据读出并发送给L0m调度器,最终该小时的数据被转移到L0m并展开为(最多)60个分钟级的桶。

四、未来规划

目前bookie集群部署在物理机上,集群新建、扩缩容相对比较麻烦,未来将考虑融入k8s体系;bookie的治理与平台化也是需要考虑的;我们目前只具备同城多中心容灾能力,跨region容灾以及公/私混合云容灾等高可用架构也需要进一步补强。

来源:携程技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯