作者介绍:胡成,多年分布式存储研发经验,曾参与139邮箱后台分布式存储系统研发,参与pacewear服务端穿戴云系统研发。
因工作需要,针对PB级海量数据的存储底层进行方案选型,分析并向团队成员介绍目前比较主流的两大对象存储系统:openstack swift 和 Ceph ,整理了一些资料,发布于此,此文是第一篇,swift 详解。
本文借鉴了swift官方资料,以及网络公开可查的技术文档或者图片,非绝对原创,向开源领域的贡献者和传播者致敬。
openstack swift 架构简单介绍
Swift简介
Swift 是 OpenStack 的对象存储组件,无需采用RAID(磁盘冗余阵列),也没有中心单元或主控结点。Swift通过在软件层面引入一致性哈希技术和数据冗余性,牺牲一定程度的数据一致性来达到高可用性(High Availability,简称HA)和可伸缩性,支持多租户模式、容器和对象读写操作,适合解决互联网的应用场景下非结构化数据存储问题。
Swift特点
·极高的数据持久性(Durability)。
·完全对称的系统架构:“对称”意味着Swift中各节点可以完全对等,能极大地降低系统维护成本。
·无限的可扩展性:一是数据存储容量无限可扩展;二是Swift性能(如QPS、吞吐量等)可线性提升。
·无单点故障:Swift的元数据存储是完全均匀随机分布的,并且与对象文件存储一样,元数据也会存储多份。整个Swift集群中,也没有一个角色是单点的,并且在架构和设计上保证无单点业务是有效的。
逻辑架构
物理架构
Swift主要组件
Swift proxy node
Swift-proxy server
Proyx Server 向用户提供了一套操作Swift的API接口,对于每个Swift请求,PorxyServer首先会通过Ring查找被操作实体对象的物理位置,随后会将请求发送过去,可以理解为ProxyServer是Swift对外的统一入口和出口,会根据环的信息来查找服务地址并转发用户请求至相应的账户、容器或者对象服务;由于采用无状态的 REST 请求协议,可以进行横向扩展来均衡负载。
另外,当某个服务器因为故障无法添加对象时,ProxyServer 会通过Ring的hash计算,重新获取一个可用的服务器,并将请求切换至该服务器。
Swift Ring
Ring是swift中非常核心的组件,用于记录存储对象与物理位置间的映射关系。在涉及查询Account、Container、Object信息时就需要查询集群的Ring信息,为了将虚拟节点(partition,分区)均衡地映射到一组物理存储设备上,并提供一定的冗余度而设计的。
Ring还提供一个基于一致性Hash所构建的环,它决定着数据如何在集群中分布。 Proxy Server 根据account,container 和 object 各自的 Ring 来确定各自数据的存放位置,其中 account 和 container 数据库文件也是被当作对象来处理的。
Swift根据设置的partition_power决定集群中的分区(partition)数量(2的partition_power次方),并根据一致性哈希算法将分区分配到不同的node上,并将数据分布到对应的分区上。
Ring使用zone来保证数据的物理隔离。 每个partition的replica都确保放在了不同的zone中,Zone只是个抽象概念,它可以是一个磁盘(disk drive),一个服务器(server),一个机架(cabinet),一个交换机(switch),甚至是一个数据中心(datacenter),以提供最高级别的冗余性。
Weight权重参数是个相对值,可以来根据磁盘的大小来调节,权重越大表示可分配的空间越多,可部署更多的分区。
Ring中保存的数据结构如下:
List of Devices,表示集群中设备的列表,在Ring类内部被称为devs;
Partition Assignment List,用于存放每个replica与device间映射关系,在Ring类内部被称为_replica2part2dev_id;
Partition Shift Value,表示计算数据hash的移位量,在Ring类内部称为_part_shift。
当集群中发生存储节点宕机、新增(删)存储节点、新增(删)zone等必须改变partition和node间的映射关系时,还可以对Ring文件通过重新平衡(rebalance)来进行更新。当虚节点需要移动时,环会确保一次移动最少数量的虚节点数,并且一次只移动一个虚节点的一个副本。
总的来说,ring提供一致性hash算法,实现了对象到虚拟节点(partition)的映射,通过Ring保存的数据结构,可以检索到partition到(基于replica配置的多副本多磁盘)存储磁盘的映射关系,其中Ring引入一致性哈希的原因是为了减少由于增加结点导致数据项移动的数量来提高单调性;引入partition的原因是为了减少由于节点数过少导致移动过多的数据项;引入replica的原因是防止数据单点、提高冗余性;引入zone的原因是为了保证分区容错性;引入weight的原因是为了保证partition分配的均衡。
Swift storage node
swift-account server
每个存储节点中有一个Account Server ,负责对处理对Account的GET,HEAD,PUT,DELETE,REPLICATION请求的服务进程,并提供账户元数据和统计信息,维护所含账户列表的服务,每个账户的信息被存储在一个 SQLite 数据库中。
swift-container server
每个存储节点存在一个Container Server,负责处理Container的GET,HEAD,PUT,DELETE,REPLICATION请求的服务进程,并提供容器元数据和统计信息,维护所含容器列表的服务,每个容器的信息也存储在一个 SQLite 数据库中。
swift-object server
每个存储节点存在一个Object Server,一个简单的BLOB存储服务器,提供对象元数据和 内容服务,可以存储,检索和删除保存在本节点的对象。对象以二进制的形式保存在文件系统, 元数据以XATTR的形式保存在文件的扩展属性上,所以要求底层的文件系统支持XATTR特性。 元数据会作为文件属性来存储,建议采用支持扩展属性的 XFS文件系统。
Replicator
会检测本地分区副本和远程副本是否一致,具体是通过对比散列文件和高级水印来完成,发现不一致时会采用推式 (Push)更新远程副本,例如对象复制服务会使用远程文件拷贝工具 rsync 来同步;另外一个任务是确保被标记删除的对象从文件系统中移除。
Updater
当对象由于高负载的原因而无法立即更新时,任务将会被序列化到在本地文件系统中进行排队,以便服务恢复后进行异步更新;例如 成功创建对象后容器服务器没有及时更新对象列表,这个时候容器的更新操作就会进入排队中,更新服务会在系统恢复正常后扫描队列并进行相应的更新处理。
Auditor
检查对象,容器和账户的完整性,如果发现比特级的错误,文件将被隔离,并复制其他的副本以覆盖本地损坏的副本;其他类型的错误会被记录到日志中。
Account Reaper
移除被标记为删除的账户,删除其所包含的所有容器和对象。
高性能索引和分布均匀性
一致性hash和Ring介绍
面对海量级别的对象,需要存放在成千上万台服务器和硬盘设备上,首先要解决寻址问题,即如何将对象分布到这些设备地址上。Swift采用一致性hash算法来实现PUT,GET账户,容器和对象文件的高效寻址,一致性hash算法介绍:
一致性散列技术,通过计算可将对象均匀分布到虚拟空间的虚拟节点上,在增加或删除节点时可大大减少需移动的数据量;虚拟空间大小通常采用 2 的 n 次幂,便于进行高效的移位操作;
一致性hash和Ring应用举例
如图所示,以逆时针方向递增的散列空间有 4 个字节长共 32 位,整数范围是[0~2**32-1];将散列结果右移 m 位,可产生 2**32/2**m个虚拟节点(partition),例如 m=29 时可产生 8 个虚拟节点(partition)。在实际部署的时候需要经过仔细计算得到合适的虚拟节点(partition)数,以达到存储空间和工作负载之间的平衡。
通过hash将对象映射到环上,可以找到对象对应的虚拟节点(partition)。然后通过独特的数据结构 Ring(环)再将虚拟节点映射到实际的物理存储设备上,完成寻址过程,实现了对象到dev的计算寻址:
其中Ring(环)的数据结构由以下信息组成:
存储设备列表、设备信息包括唯一标识号(id)、区域号(zone)、权重(weight)、IP 地址(ip)、端口(port)、设备名称(device)、元数据(meta)。
分区(partition)到设备映射关系(replica2part2dev_id 数组)
计算分区号的位移(part_shift 整数,即图 1 中的 m)
以查找一个对象的计算过程为例:
用对象的层次结构 account/container/object 作为键,使用 MD5 散列算法得到一个散列值,对该散列值的前 4 个字节进行右移操作得到分区索引号(partition),移动位数32-PARTITION_POWER(实例图中的m);
按照分区索引号(partition)在分区到设备映射表(replica2part2dev_id)里查找该对象所在分区(partition)的对应的所有设备编号,这些设备会被尽量选 择部署在不同区域(Zone)内,区域只是个抽象概念,它可以是某台机器,某个机架,甚至某个建筑内的机群,以提供最高级别的冗余性,建议至少部署 5 个区域;权重参数是个相对值,可以来根据磁盘的大小来调节,权重越大表示可分配的空间越多,可部署更多的分区。
一致性hash和Ring配置注意事项
在设置虚结点数的时候,需要对系统预期的规模做充分考虑,假如集群的规模不会超过6000个结点,那么可以将虚结点数设置为结点数的100倍。这样,变动任意一个结点的负载仅影响1%的数据项。此时有6百万个vnode数,使用2bytes来存储结点数(0~65535)。基本的内存占用是6*(10^6)*2bytes=12Mb,对于服务器来说完全可以承受。
假设有65536(2^16)个node,有128(2^7)倍的partition数(2^23,则PARTITION_POWER=23)。由于MD5码是32位的,使用PARTITION_SHIFT(等于32-PARTITION_POWER)将数据项的MD5哈希值映射到partition的2^23的空间中。
一致性hash总结
总的来说,Swift中存在两种映射关系,对于一个文件,通过哈希算法(MD5)找到对应的虚节点(一对一的映射关系),虚节点再通过映射关系(ring文件中二维数组)找到对应的设备(多对多的映射关系),这样就完成了一个对象所有replica存储在设备上的映射。
存储容量及可扩展性(extendibility)
swift存储容量和可扩展性
数据存储容量无限可扩展。
Swift性能(如QPS、吞吐量等)可线性提升,由于Swift是全然对称的架构,扩容仅仅需简单地新增机器,系统会自己主动完毕数据迁移等工作,使各存储节点又一次达到平衡状态。
基于集群所有角色和节点的可平衡扩展性,swift集群容量可达PB级存储。
系统可用性(Availability)
系统可用性保证机制
由于swift集群的以下特性:
架构对称性
对称性是指Swift架构设计上,每个服务器节点的功能和作用是相等的,而对称性的便利就是系统维护简单,且不会因为某个节点挂掉,对服务造成影响。
无单点故障
Swift采用对称性设计,所以每个节点的地位完全相等,所以没有一个节点是单点的。即系统的性能不会因为某个节点的失效而造成整个系统不可用。此外Swift对元数据(数据的描述信息,如所有者,权限,类型等)的处理与对象文件的存储方式相同,即都是采用完全多份均匀随机分布存储。
可线性水平扩展
Proxy Server 节点可以基于负载均衡的部署下线性平衡扩展,无状态部署;
基于第三节的存储节点可扩展性分析,各存储节点可以无限扩容,并且基于replica的分区隔离机制(多zone分区隔离),单个节点的故障,或者单个机房集群的故障,swift都可以提供正常服务。
根据以上特性的支持,swift具备较高的系统可用性。
数据同步机制
数据同步说明
在系统配置了对象的replication参数之后,每个对象在不同的zone里面会拥有replication份数的副本(replica),对象在不同存储设备间的复制,依赖rsync服务。由于swift牺牲一定的可用性,采用最终一致性原则,如果replication参数设置为N,当proxy server 完成了M份的成功写入,M>N/2,则向客户端返回成功,剩下的N-M份副本,由rsync服务完成对象同步,另外需要同步的情况包括:某个节点上replica遭到损害,则从其他replica复制对象信息,推送到此存储社保的partition内,以及某个节点或者存储设备整体损坏,则需要从设备上的partition的其他副本,推送对象信息到Ring重新分配的新节点上。
数据一致性保证(Consistency)
数据一致性保证说明
Swift数据是最终一致的。
按 照 Eric Brewer 的 CAP(Consistency,Availability,Partition Tolerance)理论,无法同时满足 3 个方面,Swift 放弃严格一致性(满足 ACID 事务级别),而采用最终一致性模型(Eventual Consistency),来达到高可用性和无限水平扩展能力。为了实现这一目标,Swift 采用 Quorum 仲裁协议(Quorum 有法定投票人数的含义):
(1)定义:N:数据的副本总数;W:写操作被确认接受的副本数量;R:读操作的副本数量
(2)强一致性:R+W>N, 以保证对副本的读写操作会产生交集,从而保证可以读取到最新版本;如果 W=N,R=1,则需要全部更新,适合大量读少量写操作场景下的强一致性;如果 R=N,W=1,则只更新一个副本,通过读取全部副本来得到最新版本,适合大量写少量读场景下的强一致性。
(3)弱一致性:R+W<=N,如果读写操作的副本集合不产生交集,就可能会读到脏数据;适合对一致性要求比较低的场景。
Swift 针对的是读写都比较频繁的场景,所以采用了比较折中的策略,即写操作需要满足至少一半以上成功 W >N/2,再保证读操作与写操作的副本集合至少产生一个交集,即 R+W>N。Swift 默认配置是 N=3,W=2>N/2,R=1 或 2,即每个对象会存在 3 个副本,这些副本会尽量被存储在不同区域的节点上;W=2 表示至少需要更新 2 个副本才算写成功;当 R=1 时意味着某一个读操作成功便立刻返回,此种情况下可能会读取到旧版本(弱一致性模型);当 R=2 时,需要通过在读操作请求头中增加 x-newest=true 参数来同时读取 2 个副本的元数据信息,然后比较时间戳来确定哪个是最新版本(强一致性模型);如果数据出现了不一致,后台服务进程会在一定时间窗口内通过检测和复制协议来 完成数据同步,从而保证达到最终一致性。如图 2 所示:
Quorum 协议示例
Swift保持一致性的真正难点在于,由于数据的损坏或者物理硬件故障造成了数据的不一致性!
存储系统一般采用完全均匀随机多备份的方式避免丢失的数据,不过也因此带来了多个备份之间的数据可能不一致的问题。比如一个文件有3个备份,分别存放在A、B、C服务器上,但是由于A服务器突然断电等意外情况,等重启之后A的数据肯定一B和C的不同
Swift主要采用下面三个服务来保证在遇到故障时保证数据的一致性:
Auditor:审计服务,审计器会反复检测账户、容器、对象的一致性。一旦发现某个文件的数据不完整,会立刻将此文件隔离。然后Auditor会通知Replication复制器,让其从其他一直的副本复制并替换此文件。如果出现其他错误,如所有的副本都挂了,则会将此错误信息记录入日志
Updater:更新器的主要作用是延迟更新。延迟的主要原因是为了因对用户数据上传的过程中出现故障或者异常。在正常的情况下,其更新顺序为:用户上传数据成功后,Object Server向Container Server发起通知,通知Container Server某个Container中新加入了一个Object。Container Server收到该通知,更新好Object列表后,会向Account Server发起通知。Container Server收到通知并更新Container 列表。而这是理想的更新顺序。
Replicator:复制器负责用完整的副本替换掉损坏的数据。通常会每隔一段时间自动扫描一下本地文件的hash值,并将此hash值与远端的其他副本进行对比,如果不同,则会做出相应的复制替换操作
其中,Replicator 服务以可配置的间隔来周期性启动,默认是 30s,它以 replication 为最小单位,以 node 为范围,周期性地执行数据拷贝。详细过程请参考文末的参考文档。考虑到Swift 实现的是最终一致性而非强一致性,它不合适于需要数据强一致性的应用,比如银行存款和订票系统等。需要做 replication 的情形包括但不限于:
Proxy server 在写入第三份时失败,它依然会向客户端返回成功,后台服务会写第三份拷贝。
后台进程发现某个replication 数据出现损坏,它会在新的位置重新写入。
在跨 Region 的情况下,Proxy server 只会向它所在 Region 的存储上写入,远处 region 上的数据由后台进程复杂写入。
在更换磁盘或者添加磁盘的情况下,数据需要重新平衡时。
数据持久及可靠性(Durability)
数据持久及可靠性说明
Swift引入了replica机制防止数据单点、提高冗余性,引入zone分区概念保证分区容错性,把集群的device分配到每一个Zone中,对机器的物理位置进行隔离,以满足分区容忍性。
在swift 中当中同一个Partition的Replica不能在同一个Zone内,也就是说全部的数据备份应该 分布在不同的zone中。当单个数据以多副本的形式存在与不同的物理位置,再依据数据一致性保证机制,具备损坏数据自动迁移恢复的功能,swift集群中的数据具备极高的持久可靠性(引用网络上Swift在新浪测试环境中的实践数据,从理论上测算过,Swift在5个Zone、5×10个存储节点的环境下,数据复制份是为3,数据持久性的SLA能达到10个9)。
传输性能
传输性能介绍
客户端通过proxy代理服务与对象存储节点交互。
在Swift中,客户端必须联系Swift网关,这会带来一些潜在的单点故障。为了解决这个问题,许多Swift环境为Swift网关实现了高可用性。
Swift的设计在传输速度和延迟方面都很短缺。这些问题的一个主要原因是进出Swift集群的流量流经代理服务器。
故障迁移
故障迁移介绍
当proxy server 在put get 交互的过程中发现某个存储节点,或者某个存储设备不可用,会通知Ring builder 隔离该节点,Ring可以从_replica2part2dev_id检索出该节点对应的原partition信息以及replica分布信息,从一致性hash环上给该节点对应的partition重新分配节点或者磁盘,由于单个partition分布在不同磁盘设备上的数据完全一直,因此可以从partition对应的其他replica所在节点磁盘目录,复制账户,容器,及对象信息到新分配的节点磁盘目录。以对象为例:在objects目录下存放的是各个partition目录,其中每个partition目录是由若干个suffix_path名的目录和一个hashes.pkl文件组成,suffix_path目录下是由object的hash_path名构成的目录,在hash_path目录下存放了关于object的数据和元数据,因此可以以partition目录为单位将整体复制到新分配的节点磁盘。
跨地域支持
跨地域支持说明
Swift引入了regin的隔离类型,支持节点的跨区域部署,并提供对象同步的最终一致性保证,即一些数据对象从第一次拷贝之后的副本是以异步的方式写入。这有可能会造成因为未完成的更新而返回错误的数据,但当副本位于不同的地理区域时运作良好,相对ceph的强一致性要求,当对象在跨区域间冗余备份的时候,请求响应的时间会较swift长,因此swift更适合跨区域部署,ceph更适合单个区域内的大规模集群部署。
元数据存储机制
元数据存储机制介绍
对象以二进制文件的形式存储,它的元数据存储在文件系统的扩展属性(xattr)中,建议采用默认支持扩展属性(xattr)的XFS文件系统。
成熟稳定度
成熟稳定度
Swift对象存储目前在大型公司的生产环境应用案例比ceph少,当数据量到达PB级,成熟稳定度不如ceph。
可维护性
可维护性
Swift采用的原理比较简单。其架构设计、代码和算法都比较易懂、且还提供了较高的可靠性、且维护也比较容易。