分布式块存储 ZBS 的自主研发之旅|元数据管理
重点内容
元数据管理十分重要,犹如整个存储系统的“大黄页”,如果元数据操作出现性能瓶颈,将严重影响存储系统的整体性能。
如何提升元数据处理速度与高可用是元数据管理的挑战之一。SmartX 分布式存储 ZBS 采用 Log Replication 的机制,在元数据存储方案上选择将 LevelDB 和 Zookeeper 相结合,从而以更加精简的架构实现了高可靠、高性能与轻量级的元数据服务。
更多 ZBS 架构设计与技术解读,请阅读文末电子书《分布式块存储 ZBS 自主研发之旅》。
在之前的几篇关于 SmartX 分布式块存储 ZBS 架构设计文章中,已对 ZBS 存储的整体架构设计、接入协议 NVMe-oF 和数据同步协议 RDMA 进行了较为全面的介绍。为了帮助用户更好地理解 ZBS 存储底层的设计实现,本篇文章将涉足存储系统中非常重要的组件之一:元数据管理。
元数据是什么?通常得到相对简单的答案是描述数据的数据。这句解释并没有错,但是有些抽象,并不容易理解。元数据本身并不会暴露自己给使用者,仅是为存储系统或文件系统服务,我们可以将元数据想象成过去查公司电话和地址的大黄页或是图书的索引,用于记录数据的存储位置。当用户需要读取或修改某个文件时,这个文件对应存储的具体位置,就是由元数据进行管理的。当然,除了记录存储位置,元数据还会存储一些数据的属性信息,来扩展数据的管理能力,例如数据块什么时间创建、什么时间发生修改、数据块是否需要回收、数据块的操作权限等。
所以,在存储系统内部,元数据管理十分地重要,凡是涉及到存储系统的数据访问,都会经过元数据的查询或更新操作,如果元数据操作出现性能瓶颈,将会严重影响存储系统的性能表现。本文通过多个维度展开介绍元数据管理的现状以及未来的发展趋势,同时分享 ZBS 的元数据设计思想。
元数据管理演变
最简单的元数据管理 Meta Server(元数据服务)+ Meta DB(数据库):这种方案算是初代经典的设计思想,后续方案在此基础上进行延展。
基于高可用架构的元数据管理:这个方案的前置条件需要部署多节点,合理布局 Master 和 Slave 角色,从而提高节点容错性。
基于内存的元数据管理:Meta Server 通过将元数据全部加载到内存,加速元数据的访问速度,从而满足元数据访问更高的性能要求。由于采用内存加载元数据,在设计上,需要评估数据规模。
在海量数据规模下,单节点内存已不能承载全部的元数据,将元数据分区(分布式架构横向扩展)则是一条主要的技术发展方向:将元数据按照规则进行分拆,通过多个 Meta Server 服务来管理各自维护的元数据。此方案还需要一层路由或代理服务角色,用来处理请求转发。
除元数据横向扩展外,另一条技术方向是分层(Tier Layer)管理元数据,这里称为纵向扩展。设计思想是将热点数据做内存缓存(Cache Layer)、冷数据做持久化保存在磁盘(Persisted Layer),根据算法,实现冷热数据的交换。
元数据管理的挑战
设计和管理存储系统的元数据是一个复杂的任务,面临着多个挑战。以下是一些与元数据管理相关的主要挑战:
一致性和完整性:元数据的一致性和完整性是关键问题。当多个用户或应用程序同时对存储系统进行读写操作时,确保元数据的一致性和完整性需要采用适当的锁定和同步机制,以及正确的事务处理策略。
性能和可伸缩性:元数据的管理对存储系统的性能和可伸缩性有重要影响。元数据的访问和更新操作需要快速响应,并能够处理高并发的请求。设计高效的元数据存储和检索机制,以及合理的缓存策略,可以提高存储系统的性能和可伸缩性。
元数据的存储和备份:元数据本身也需要进行存储和备份。元数据的存储可能需要考虑容错和冗余,以防止元数据丢失或损坏。
元数据的查询和检索:存储系统中的元数据通常需要进行查询和检索操作。元数据的查询可能涉及复杂的条件和关联关系,需要设计高效的查询引擎和索引策略。
元数据的演化和版本管理:随着存储系统的演化和升级,元数据的结构和内容可能会发生变化。管理元数据的演化和版本也是挑战之一,需要考虑向后兼容性和数据迁移策略,以确保元数据的连续性和可靠性。
存储系统的元数据管理涉及到数据量和复杂性、一致性和完整性、性能和可伸缩性、存储和备份、查询和检索、演化和版本管理等多个挑战。解决这些挑战需要综合考虑系统架构、存储技术、数据管理策略等多方面的因素。
ZBS Meta 元数据服务
ZBS 对元数据的需求
ZBS 存储的定位是分布式块存储系统,主要应用场景包括云计算 IaaS、虚拟化、裸金属数据库和容器。基于这些应用场景,ZBS 对元数据管理有如下几个需求:
由于元数据的重要性,对元数据的第一个需求就是可靠性。元数据必须是保存多份,同时元数据服务还需要提供 Failover 的能力。
第二个需求就是高性能。尽管可以对 I/O 路径进行优化,使得大部分 I/O 请求都不需要访问元数据服务,但永远都有一些 I/O 请求还是需要修改元数据,比如数据分配等。为避免元数据操作成为系统性能的瓶颈,元数据操作的响应时间必须足够短。同时由于分布式系统的集群规模在不断的扩大,对于元数据服务的并发能力也有较高的要求。
最后一个需求是轻量级。由于定位 ZBS 大部分使用场景是私有部署,也就是将 ZBS 部署在客户的数据中心内,且由客户自己运维,这个场景和很多互联网公司自己来运维自己的产品是完全不同的。所以对于 ZBS 来说,更强调整个存储系统,尤其是元数据服务的轻量级,以及易运维的能力。希望元数据服务轻量到和数据服务混合部署在一起,这样的好处是可以三节点起步,无需独立的管理角色节点。
如果大家了解 HDFS 的话,HDFS 中的元数据服务的模块叫做 Namenode,这是一个非常重量级的模块。Namenode 需要被独立部署在一台物理服务器上,且对硬件的要求非常高,也非常不易于运维,无论是升级还是主备切换,都是非常重的操作,非常容易因操作问题而引发故障。
ZBS 元数据存储技术实现思考
提及元数据存储,第一个想到的方案就是关系型数据库,例如 MySQL,以及一些成熟的 KV 存储引擎,例如 LevelDB,RocksDB 等。这种类型的存储最大的问题就是无法提供可靠的数据保护和 Failover 能力。LevelDB 和 RocksDB 虽然非常轻量级,但都只能把数据保存在单机上。尽管 MySQL 提供一些主备方案,但 MySQL 的主备方案过于笨重,在故障切换场景中并不灵活,且缺乏简易的自动化运维方案,所以并不是一个十分好的选择。
其他的方案还包括分布式数据库,例如 MongoDB 和 Cassandra。这两种分布式数据库都可以解决数据保护和提供 Failover 机制。但是他们都不提供或不能全面支持 ACID 机制,所以在上层实现时会比较麻烦,需要额外的工作量。其次就是这些分布式数据库在运维上也相对复杂,不是很易于自动化运维。
基于 Paxos 或者 Raft 协议自己实现一个框架?这样实现的代价会非常大,对于一个初创公司并不是一个最佳选择(SmartX 成立时间是 2013 年,当时 Raft 也只是刚刚提出)。
还可以选择 Zookeeper。Zookeeper 基于 ZAB 协议(Zookeeper Atomic Broadcast),可以提供一个稳定可靠的分布式存储服务(崩溃恢复和消息广播),确保事务的一致性。但 Zookeeper 的最大的问题是存储的数据容量非常有限,为了提高访问速度,Zookeeper 把存储的所有数据都缓存在内存中,所以这种方案导致元数据服务所能支撑的数据规模严重受限于服务器的内存容量,使得元数据服务无法做到轻量级,也无法和数据服务混合部署在一起。
最后还有一种方案是基于 DHT(Distributed Hash Table)的路线(很多开源类产品,例如 Ceph,都是基于 DHT 实现)。这种方案的好处即元数据中不需要保存数据副本的存储位置,而是根据一致性哈希的方式计算出来,这样就极大地降低了元数据服务的存储压力和访问压力。但使用 DHT ,就丧失了对数据副本位置的控制权,在实际生产环境中,非常容易造成集群中的数据不均衡的现象。同时在运维过程中,如果遇到需要添加节点、移除节点、添加磁盘、移除磁盘的情况,由于哈希环会发生变化,一部分数据需要重新分布,会在集群中产生不必要的数据迁移,而且数据量往往非常大。而这些运维操作在客户的数据中心几乎每天都会发生。大规模的数据迁移很容易影响到线上的业务的性能,所以 DHT 使得运维操作变得非常麻烦而不可控。
ZBS 元数据设计实现
通过以上分析,可以看出,每种技术路线均有各种各样的问题,并不能直接使用。ZBS 采用 Log Replication 的机制,设计上,选择 Raft 可能要优于 ZK,但综合评估实现复杂性和代价,最终选择 LevelDB 和 Zookeeper 相结合,并同时避开他们自身的问题,实现元数据管理服务。
这里简单地介绍一下 Log Replication。简单来说,把数据或者状态看作是一组对数据操作的历史集合,而每一个操作都可以通过被序列化成 Log 记录下来。如果可以拿到所有的 Log,并按照 Log 里面记录的操作重复一遍,那么就可以完整的恢复数据的状态。任何一个拥有 Log 的程序都可以通过重放 Log 的方式恢复数据。如果对 Log 进行复制,实际上也就相当于对数据进行了复制。这就是 Log Replication 最基本的想法。
ZBS 元数据的关键能力设计:
可靠性 - 在单 Master 架构中,元数据必须保存多份,同时元数据服务需要提供容错和快速切换的能力。ZBS 选择采用单机 KV DB 来实现 Meta 本地元数据持久化,利用 Log Replication 机制实现元数据在多节点间的数据同步。当 Meta Lead 失效,Follower 节点通过 Zookeeper 选主快速接管元数据服务。
高性能 - 最小化元数据服务在存储 I/O 路径中的参与度。ZBS 将控制平面与数据平台进行分离,使大部分 I/O 请求不需要访问元数据服务,当需要修改元数据时,比如数据分配,元数据操作的响应时间必须足够快。ZBS 定义数据块 Extent 为 256 MiB,在 2 PiB 集群存储容量规模下(当前版本下,SmartX 单集群容量上限),元数据可全部加载到内存中操作,提高查询效率。
轻量级 - 为了让架构简单,ZBS 并没有拆分成管理节点和数据节点,而是尽可能保证性能和安全性的前提下,降低元数据的存储空间和内存消耗。元数据服务与数据服务混合部署在同一个节点,三节点即可部署交付,而无需独立部署管理节点。
ZBS 元数据服务的具体实现:
集群部署多个(3 或 5)Meta Server,每个 Server 本地运行了一个 LevelDB 数据库,Meta Server 通过 Zookeeper 进行选主,Leader 节点对外提供服务,其他 Meta Server 进入 Standby 状态。
当 Leader 节点接收到元数据的更新操作后,会将这个操作序列化成一组操作日志,并将这组日志写入 Zookeeper。由于 Zookeeper 是多副本的,所以一旦 Log 数据写入 Zookeeper,也就意味着 Log 数据是安全的。
为了避免 Zookeeper 消耗过多内存,ZBS 会对 Zookeeper 中的 Log 定期执行清理。只要 Log 已经被所有的 Meta Server 同步完, Zookeeper 中保存的 Log 就可以被删除了,以节省空间。
当日志提交成功后,Meta Server 就可以将对元数据的修改同时提交到本地的 LevelDB 中。这里 LevelDB 中存储的是一份全量的数据,而不需要以 Log 的形式存储。
对于非 Leader 的 Meta Server 节点,会异步的从 Zookeeper 中拉取 Log,并将通过反序列化,将 Log 转换成对元数据的操作,再将这些修改操作提交到本地的 LevelDB 中。这样就能保证每一个 Meta Server 都可以保存一个完整的元数据。
Meta Server Failover 逻辑非常简单。当 Leader 节点发生故障,Standby 状态的 Meta Server 通过 Zookeeper 再重新进行一次选主,选出一个新的 Meta Leader。这个新的 Leader 首先从 Zookeeper 上同步所有还未拉取的日志,反序列化后提交到本地的 LevelDB 中,然后就可以对外正常提供元数据服务。
ZBS 元数据服务特点:
架构简单,由 Zookeeper 负责选主和 Log Replication,由 LevelDB 负责本地元数据的存储。
处理性能高,Zookeeper 和 LevelDB 本身的性能都很出色,而且在部署时,将 Zookeeper 和 LevelDB 运行在 SSD 高速存储介质。在实际测试中,对于单次元数据的修改都是在毫秒级完成。在并发的场景下,可以对元数据修改的日志做 Batch,以提高并发能力。
切换速度快,Meta Server Failover 的时间就是集群选主,再加上 Log 同步的时间,可以做到秒级恢复元数据服务。
元数据服务对资源消耗非常小,可以做到和数据服务混合部署,从而实现集群三节点起步。
未来元数据管理的趋势和发展
随着存储系统的不断发展和变化,元数据管理也在不断演化和改进。正如前文对元数据管理技术路线的分析,通过分区(分布式架构)实现元数据管理,可以更好地面对数据激增、架构横向扩展、查询速度提升、降低切换时间等要求与挑战。
元数据在存储系统中发挥着重要的作用,未来的元数据管理也将面临更多的挑战和机遇。通过合理的元数据管理策略和技术实现,可以更好地管理和维护数据,并提高存储系统的可靠性、稳定性和高效运行。
这里留个彩蛋,ZBS 将在下一个大版本更新时进一步优化元数据管理机制,待正式发布后,再分享其背后的思考和设计实现。
想了解更多 ZBS 的架构原理和技术细节?欢迎阅读电子书《分布式块存储 ZBS 的自主研发之旅》(在 SmartX 用户社区中下载)。
推荐阅读: