理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

本系列文章着重学习和研究OpenStack Swift,包括环境搭建、原理、架构、监控和性能等。

(1)OpenStack + 三节点Swift 集群+ HAProxy + UCARP 安装和配置

(2)原理、架构和性能

(3)监控

1. 架构

1.1 总体架构

Swift 的总体架构非常的清晰和独立:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

# 分层(Tier) 组件(Service) 功能(Function) 特性 部署考量
1 访问层(Access Tier) Load Balancer 硬件(比如F5)或者软件(比如HAProxy)负载均衡器,将客户端的请求按照配置的策略分配到无状态的 proxy service。    按照实际需求选择硬件还是软件LB
2 Proxy Server
  • 提供 REST API 给客户端
  • 无状态的 WSGI 服务,由多个 Proxy Server 组成一个集群
  • 将客户端请求转给某个存储节点上的 Account,Container 或者 Object 服务

是 CPU 和网络带宽敏感的

  • 分配更多的CPU和网络带宽,比如 10GbE 网络
  • 使用 Memcached 来做 token、account 和 container 数据缓存
  • 和存储服务分开部署
  • 使用负载均衡器
  • 往往每五个存储节点使用两个 proxy server 节点,然后再按比例扩展
  • 将 public network (用于客户端访问)和 backend network (用于访问存储服务)分开
  • 需要考虑安全性,可以将 SSL Termination 放在 Proxy Server 上
3 存储层 (Capactity Tier)

Account Server

提供 Account 操作 REST API 是磁盘性能和网络带敏感的
  • 对 Account 和 Container 服务使用更好的磁盘,比如 SAS 或者 SSD,来提高性能;
  • 使用 1GbE 或者 10GbE 网络
  • 需要的话,可以将 replicaiton network 独立出来
  • 需要的话,可以将 Account 和 Container 服务部署到独立的服务器上
4 Container Server 提供 Container 操作 REST API
5 Object Server 提供 Object 操作 REST API
 6 Consistency Servers 包括 Replicators, Updaters 和 Auditors 等后台服务,用于保证 object 的一致性

这是一张比较经典的 Swift 物理部署图:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

1.2 网络架构

以一个对外提供对象存储服务的集群为例,其网络架构可以为:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

  • 外部流量被放在一个单独的(上图中紫色)VLAN 中,终点为 LB
  • 控制(管理)网络连接所有节点
  • Swift 前端(front end / public)网络连接 LB 和 所有 Proxy server 节点
  • Swift 后端 (backend / private) 网络连接所有 Proxy server 节点和 存储节点
  • 需要的话,还可以从后端网络中分离出复制(replication)网络

在网络带宽选择上,

  • 考虑到复制数据的容量较大(往往是几个TB起步),后端网络往往是用 10GbE 网络
  • 根据前端负载,前端网络可以使用 1GbE 网络,或者有条件时使用 10GbE 网络
  • 管理/IPMI网络往往是用 1GbE 网络

这是 SwiftStack 的一个例子:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

2. 数据存放

2.1 Swift 的数据存放

2.1.1 Swift 的数据模型

Swift 的数据模型使用了以下三个概念来(见下图1):

  • Account: 账户/租户。Swift 是天生支持多租户的。如果使用 OpenStack Keystone 做用户校验的话,account 与 OpenStack project/tenant 的概念相同。Swift 租户的隔离性体现在metadata上,而不是体现在 object data 上。数据包括自身元数据 和 container 列表,被保存在 SQLite 数据库中。
  • Container: 容器,类似于文件系统中的目录,由用户自定义,它包含自身的元数据和容器内的对象列表。数据保存在 SQLite 数据库中。在新版中,Swift 支持在容器内添加文件夹。
  • Object: 对象,包括数据和数据的元数据,以文件形式保存在文件系统上。

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]     理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

(图1)                                                     (图2)

  • Containers 是用户创建的,用来 hold objects。
  • objects 可以是 0 bytes 长度,或者包含数据。
  • container 中的 object 最大大小为 5GB;超过的话,会做特殊处理
  • 每个 object 使用它的 name 来被 referenced;Swift 没有目录概念
  • 在 object name 中可以使用任意的可以被 ‘URL-encoded’ 的 字符,最大长度为 URL - coded 之后 1034 个字符
  • object name 中可以带 '/' 字符,它会带来目录结构的幻觉(illusion),比如 dir/dir2/name。即使看起来象个目录,但是它仍然只是一个 object name。此时,不需要 dir 或者 dir/dir2 container 的存在。
  • 如果一个 container 所有 objects 的大小为0,那么它将看起来象一个目录。
  • 客户端使用 HTTP 或者 HTTPS 访问 Swift,包括读、写、删除 objects。也支持 COPY 操作,它会创建一个新的 object,使用一个新的 object name,包含老 object 的 data。没有 rename 操作,它会首先 copy 出一个新的,然后再将老的删除。

2.1.2 选择数据存放位置

Swift 保存每个对象为多分拷贝,它按照物理位置的特点,尽量将这些拷贝放在不同的物理位置上,来保证数据的地理位置上的可靠性。它主要考虑以下几种位置属性:

  • Region:地理位置上的区域,比如不同城市甚至不同国家的机房,这主要是从灾备方面考虑的。
  • Zone:一个数据中心根据物理网络、供电、空调等基础设施分开的独立的域,往往将一个机架(Rack)内的服务器分在一个 Zone 内。
  • Node (节点):物理服务器
  • Disk (磁盘):物理服务器上的磁盘

Swift 在确定对象的放置位置时,会尽量将对象及其拷贝放在不会同时损失的物理位置上。见上图2.

2.1.3 保证数据一致性

对象及其拷贝放置在某个磁盘上后,Swift 会使用Replicators, Updaters 和 Auditors 等后台服务来保证其数据的最终一致性。

  • Replicator – 拷贝对象,确保系统的最终一致性(Replicate objects and make a system in a consistent state);恢复磁盘和网络错误(Recover disk failure, network outages situation)
  • Updater – 更新元数据(Update metadata),从容器和账户元数据高负载导致的问题上恢复(Recover failure caused by container, account metadata high load)
  • Auditor – 删除问题账户,容器和对象,然后从别的服务器上拷贝过来(Delete problematic account, container or objects and replicate from other server);恢复数据库和文件数据错误(Recover dbs or files which have bit rot problem.

其中,Replicator 服务以可配置的间隔来周期性启动,默认是 30s,它以 replication 为最小单位,以 node 为范围,周期性地执行数据拷贝。详细过程请参考文末的参考文档。考虑到Swift 实现的是最终一致性而非强一致性,它不合适于需要数据强一致性的应用,比如银行存款和订票系统等。需要做 replication 的情形包括但不限于:

  • Proxy server 在写入第三份时失败,它依然会向客户端返回成功,后台服务会写第三份拷贝
  • 后台进程发现某个replication 数据出现损坏,它会在新的位置重新写入
  • 在跨 Region 的情况下,Proxy server 只会向它所在 Region 的存储上写入,远处 region 上的数据由后台进程复杂写入
  • 在更换磁盘或者添加磁盘的情况下,数据需要重新平衡时

2.2 Swift  是如何实现这些需求的:使用 Ring + 哈希算法

Swift 根据由管理员配置的 Ring 使用相对简单直接的算法来确定对象的存放位置。对象会以文件的形式保存在本地文件系统中,使用文件的扩展属性来保存对象的元数据,因此,Swift 需要支持扩展属性的文件系统,目前官方推荐是 XFS。

2.2.1 Ring 的内容和算法

简单来说,Swift 的 Proxy Server 根据account,container 和 object 各自的 Ring 来确定各自数据的存放位置,其中 account 和 container 数据库文件也是被当作对象来处理的。

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

因此,Swift 需要 Ring 的配置文件分布在所有 Proxy节点上。同时,Consistency Servers 需要它来确定后台对象拷贝的位置,因此也需要部署在所有存储节点上。Ring 以文件的形式保存:

  • object.ring.gz
  • container.ring.gz
  • account.ring.gz

在分析 Ring 是如何工作的之前,先来看看 Ring 的几个关键配置:

  • Region,zone 和 disk:前面说过了,略过
  • partition:Swift 再将每个磁盘分成若干 partition (分区)。这是后端一致性检查服务处理拷贝(replication)的基本单位。
  • Replica:对象和拷贝的总份数,常规推荐值是 3。

管理员使用 Swift 提供的 ring 生成工具(swift-ring-builder,位于源码的bin目录下,是swift最基本的命令,它与swift/common/ring/下的文件一起实现ring文件创建,添加,平衡,等操作),加上各种配置参数,得出该ring的内容。以 Object ring 为例,

root@swift1:/etc/swift# swift-ring-builder object.builder
object.builder, build version 6
1024 partitions, 3.000000 replicas, 1 regions, 3 zones, 6 devices, 0.00 balance, 0.00 dispersion
The minimum number of hours before a partition can be reassigned is 1
The overload factor is 0.00% (0.000000)
Devices: id region zone ip address port replication ip replication port name weight partitions balance meta
0 1 1 9.115.251.235 6000 9.115.251.235 6000 sdb1 100.00 512 0.00
1 1 1 9.115.251.235 6000 9.115.251.235 6000 sdc1 100.00 512 0.00
2 1 2 9.115.251.234 6000 9.115.251.234 6000 sdb1 100.00 512 0.00
3 1 2 9.115.251.234 6000 9.115.251.234 6000 sdc1 100.00 512 0.00
4 1 3 9.115.251.233 6000 9.115.251.233 6000 sdb1 100.00 512 0.00
5 1 3 9.115.251.233 6000 9.115.251.233 6000 sdc1 100.00 512 0.00

该 Ring 的配置为:1 个 region,3 个 zone,3 个 node,6 个磁盘,每个磁盘上 512 个分区。

内部实现上,Swift 将该 Ring 的配置保存在其 _replica2part2dev 数据结构中:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

其读法是:

  • 行:将集群所有的分区都顺序编号,每个分区有唯一的一个ID
  • 列:包含 Ring 中的 id,该 id 唯一确定了一个 disk;和 replica 的编号。

因此,Swift 通过该数据结构可以方便地查到某个 replica 应该通过哪些节点上的存储服务放在哪个 disk 上。

除了生成 Ring 外,对 Ring 的另一个重要操作是 rebalance(再平衡)。在你修改builder文件后(例如增减设备),该操作会重新生成ring文件,使系统中的partition分布平衡。当然,在 rebalance 后,需要重新启动系统的各个服务。 详情可以参考 OpenStack Swift源码分析(二)ring文件的生成

2.2.2 数据放置和读取过程

当收到一个需要保存的 object 的 PUT 请求时,Proxy server 会:

  1. 根据其完整的对象路径(/account[/container[/object]])计算其哈希值,哈希值的长度取决于集群中分区的总数。
  2. 将哈希值的开头 N 个字符映射为数目同 replica 值的若干 partition ID。
  3. 根据 partition ID 确定某个数据服务的 IP 和 port。
  4. 依次尝试连接这些服务的端口。如果有一半的服务无法连接,则拒绝该请求。
  5. 尝试创建对象,存储服务会将对象以文件形式保存到某个磁盘上。(Object server 在完成文件存储后会异步地调用 container service 去更新container数据库)
  6. 在3份拷贝中有两份被成功写入后, Proxy Server 就会向客户端返回成功。

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

当 Proxy server 收到一个获取对象的 GET 请求时,它:

(1)(2)(3)(4)同前面的 PUT 请求,确定存放所有 replica 的 所有磁盘

(5)排序这些 nodes,尝试连接第一个,如果成功,则将二进制数据返回客户端;如果不成功,则尝试下一个。直到成功或者都失败。

应该说该过程蛮简单直接,这也符合Swift的总体设计风格。至于具体的哈希算法实现,有兴趣可以看相关论文。大致来说,它实现的是 “unique-as-possible” 即 “尽量唯一” 的算法,按照 Zone,Node 和 Disk 的顺序。对于一个 replica,Swift 首先会去选择一个没有该对象 replica 的 zone,如果没有这样的 zone,选择一个已使用 zone 中的没用过的 node,如果没有这样的 node,就选择已使用的 node 上的一个没使用过的 disk。

2.2.3 Hash 计算和对象位置查找

(1)存放的目录取决于哈希值

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

这里也表明,对象的存放目录和其名称是直接关联的。因此,在Swift中的,对对象重命名,意味着对象位置的修改,该过程会产生数据拷贝,而且在很多时候是需要跨节点的远程拷贝。在某些应用中,比如 Hadoop 大数据应用,如果采用 Swift 作为存储,在其mapreduce 过程中,是会产生文件 rename 操作的,这在 Swift 中会带来严重的性能下降。

(2)获取某对象的存放路径

root@swift1:~/s1# swift-get-nodes /etc/swift/object.ring.gz AUTH_dea8b51d28bf41599e63464828102759/container1/

Account         AUTH_dea8b51d28bf41599e63464828102759
Container container1
Object
Partition
Hash 456a95e2e66aad55d72756c6b0cd3b75 Server:Port Device 9.115.251.235: sdb1
Server:Port Device 9.115.251.234: sdc1
Server:Port Device 9.115.251.233: sdc1 curl -I -XHEAD "http://9.115.251.235:6000/sdb1/277/AUTH_dea8b51d28bf41599e63464828102759/container1/1"
curl -I -XHEAD "http://9.115.251.234:6000/sdc1/277/AUTH_dea8b51d28bf41599e63464828102759/container1/1"
curl -I -XHEAD "http://9.115.251.233:6000/sdc1/277/AUTH_dea8b51d28bf41599e63464828102759/container1/1"

(3)远程登录后者 ssh 后可以看到保存对象的文件

root@swift1:~/s1# ls /srv/node/sdb1/objects//b75/456a95e2e66aad55d72756c6b0cd3b75 -l
total
-rw------- swift swift Nov : 1447003035.84393.dataroot@swift1:~/s1# cat /srv/node/sdb1/objects//b75/456a95e2e66aad55d72756c6b0cd3b75/1447003035.84393.data
222222222222

2.2.4 对象分段

Swift 对于小的文件,是不分段直接存放的;对于大的文件(大小阈值可以配置,默认是 5G),系统会自动将其分段存放。用户也可以指定分段的大小来存放文件。比如对于 590M 的文件,设置分段大小为 100M,则会被分为 6 段被并行的(in parallel)上传到集群之中:

root@controller:~/s1# swift upload container1 -S  tmpubuntu
tmpubuntu segment
tmpubuntu segment
tmpubuntu segment
tmpubuntu segment
tmpubuntu segment
tmpubuntu segment
tmpubuntu
root@controller:~/s1# swift list container1 admin-openrc.sh
cirros-0.3.-x86_64-disk.raw
tmpubuntu

从 stat 中可以看出它使用一个 manifest 文件来保存分段信息:

root@controller:~/s1# swift stat container1 tmpubuntu
Account: AUTH_dea8b51d28bf41599e63464828102759
Container: container1
Object: tmpubuntu
Content Type: application/octet-stream
Content Length:
Last Modified: Fri, Nov :: GMT
ETag: "fa561512dcd31b21841fbc9dbace118f"
Manifest: container1_segments/tmpubuntu/1446907333.484258/591396864/100000000/
Meta Mtime: 1446907333.484258
Accept-Ranges: bytes
Connection: keep-alive
X-Timestamp: 1447439512.09744
X-Trans-Id: txae548b4b35184c71aa396-0056462d72

但是 list 的时候依然只看到一个文件,原因是因为 manifest 文件被保存到一个独立的 container (container1_segments)中。这里可以看到 6 个对象:

root@controller:~/s1# swift list container1_segments
tmpubuntu/1446907333.484258///
tmpubuntu/1446907333.484258///
tmpubuntu/1446907333.484258///
tmpubuntu/1446907333.484258///
tmpubuntu/1446907333.484258///
tmpubuntu/1446907333.484258///

每个对象大小是100M(考虑到存储效率,不建议每个对象大小小于100M):

root@controller:~/s1# swift stat container1_segments tmpubuntu/1446907333.484258///
Account: AUTH_dea8b51d28bf41599e63464828102759
Container: container1_segments
Object: tmpubuntu/1446907333.484258///
Content Type: application/octet-stream
Content Length:

而且用户可以单独操作比如修改某一段。Swift 只会负责将所有段连接为用户所见的大的对象。

关于大文件支持的更多细节,可以参考 官方文档 和 Rackspace 的文档。从上面的描述可以看出,Swift 对文件分段的支持是比较初级的(固定,不灵活),因此,已经有人提出 Object stripping (对象条带化)方案,比如下面的方案,不知道是否已经支持还是将要支持。

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

2.3 Region

通过将对象存放在不同物理位置上的 Region 内,可以进一步增强数据的可用性。其基本原则是:对于 N 份 replica 和 M 个 region,每个 region 中的 replica 数目为 N/M 的整数,剩余的 replica 在 M 个region 中随机选择。以 N = 3, M = 2 为例,一个 region 中有 1 个 replica,另一个 region 中有两个 replica,如下图所示:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

对于一个 PUT 操作来说,Proxy server 只会将 replica 写入它所在的 region 中的 node,远端 region 中的 replica 由 replicator 写入。因此,Swift 的算法应该尽量保证 proxy server 所在的 region 中的 replica 份数相对多一些,这也称为 replica 的 proxy server 亲和性。

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

显然,跨 region 的数据复制加重了对网络带宽的要求。

两种形式的 Region:

(1)远端 region 实时写入 replica

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

(2)远端 region 的 replica 异步写入

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

2.4 Storage Polices (存储策略)

上面的描述中,一个Swift 集群只支持一套 Ring 配置,这意味着整个机器的配置是唯一的。类似 Ceph 中 pool 的定义,Swift 在 2.0 版本(包含在 OpenStack Juno 版本中)中,添加了一个非常大的功能:Storage policy。在新的实现中,一个 Swift 可以由多套 Ring 配置,每套 Ring 的配置可以不相同。比如,Ring 1 保存 3 份对象拷贝,Ring 2 保存 2 份对象拷贝。几个特点:

  • Policy 被实现在 container 级别
  • 创建 container 时可以指定 policy。一旦被指定,不可以修改。
  • 一个 policy 可以被多个 container 共享使用

通过应用该新的功能,Swift 用户可以制定不同的存储策略,来适应不同应用的存储需求。比如对关键应用的数据,制定一个存储策略使得数据被保存到 SSD 上;对于一般关键性的数据,指定存储策略使得数据只保存2份来节约磁盘空间。比如说:

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

详细信息,请参考 OpenStack 官方文档 和 SwiftStack 官方文档

3. 版本及主要功能

3.1 Juno以及之前主要版本和功能

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

(1)Large object support

(2)Static web hosting

(3) S3 compatible API

(4) Object expiration

(5) Temp url

(6) Global cluster

(7) Storage policy

3.2 Kilo 版本中的更新

新功能

纠删码(beta)

Swift现在支持纠删码(EC)存储策略类型。这样部署人员、以极少的RAW容量达到极高的可用性,如同在副本存储中一样。然而,EC需要更多的CPU和网络资源,所以并不适合所有应用场景。EC非常适合在一个独立的区域内极少访问的、大容量数据。

Swift纠删码的实现对于用户是透明的。对于副本存储和纠删码存储的类型,在API上没有任何区别。

为了支持纠删码,Swift现在需要依赖PyECLib和liberasurecode。liberasurecode是一个可插件式的库,允许在你选择的库中实现EC算法。

更详细文档请参阅 http://swift.openstack.org/overview_erasure_code.html

复合型令牌(Composite tokens)

复合型令牌允许其他OpenStack服务以客户端名义将数据存储于Swift中,所以无论是客户端还是服务在更新数据时,都不需要双方彼此的授权。

一个典型的例子就是一个用户请求Nova存放一个VM的快照。Nova将请求传递给Glance,Glance将镜像写入Swift容器中的一组对象中。在这种场景下,用户没有来自服务的合法令牌时,无法直接修改快照数据。同样,服务自身也无法在没有用户合法令牌的情况下更新数据。但是数据的确存在于用户的Swift账户中,这样使得账户管理更简单。

更详细的文档请参阅http://swift.openstack.org/overview_backing_store.html

更小规模、不平衡集群的数据位置更新

Swift数据的存放位置现在根据硬件权重决定。当前,允许运维人员逐渐的添加新的区域(zones)和地域(regions),而不需要立即触发大规模数据迁移。同时,如果一个集群是非平衡的(例如,在一个区域(zones)的集群中,其中一个的容量是另外一的两倍),Swift会更有效的使用现有空间并且当副本在集群空间不足时发出警告。

全局性集群复制优化

区域(regions)之间复制时,每次复制只迁移一个副本。这样远程的区域(region)可以在内部复制,避免更多的数据在广域网(WAN)拷贝。

已知问题

  • 作为beta更新,纠删码(EC)的功能接近完成,但是对于某些功能仍然不完整(像多范围(multi-range)读取),并且没有一个完整的性能测算。这个功能为了持久性依赖于ssync。部署人员督促我们做更大规模的测试,并且不要在生产环境部署中使用纠删码存储策略。

升级提示

像往常一样,你能在不影响最终用户体验的前提下,升级到这个版本的Swift。

  • 为了支持纠删码,Swift需要一个新的依赖PyECLib(和liberasurecode等)。并且eventlet的最低版本要求也升高了。

3.3 Liberty 版本中的更新

L版本中Swift 没有加入大的新功能,详细情况可以参考 官方文档

3.4 优势

理解 OpenStack Swift (2):架构、原理及功能 [Architecture, Implementation and Features]

其它参考文档:

http://www.florentflament.com/blog/openstack-swift-ring-made-understandable.html

https://www.mirantis.com/blog/configuring-multi-region-cluster-openstack-swift/

OpenStack Swift源码分析

上一篇:HTML5游戏开发系列教程4(译)


下一篇:Maven学习总结(三)——使用Maven构建项目