OpenStack Swift多region下删除object出现404的问题及解决方法

2017-06-29 Lingxian Kong 更多博文 » 博客 » GitHub »

原文链接 https://lingxiankong.github.io/2017-06-29-swift-404.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


这篇博客我会尽量以白话文的方式来描述问题和解决思路。

问题

Swift 支持多 region 部署,并且 object 的多个副本存储在不同的 region 以提高可用性。一般场景下,用户通常只在一个 region 上传和下载对象文件,只有当 region 出现故障时(比如网络瘫痪)才会到另一个 region 读取副本数据。而且对数据存储要求比较严格的企业(特别是政府部门),确实会对数据的副本数和存储位置有严格的要求,所以提供对象存储的云服务厂商,一般都采取多 region 部署方案。

我们 Catalyst Cloud 基于 OpenStack Swift 对外提供对象存储服务,作为在新西兰唯一的一家公有云服务提供商,我们目前有三个 region,用户在某个 region 上传的对象全局可见。但我们从一开始就碰到一个问题,用户上传和下载对象都正常,但删除对象时,经常会出现404错误。虽然之后用户读取对象列表时发现这个被删除的对象确实不存在了,但这个404错误还是对用户造成困惑。特别是当用户使用脚本做定期备份时,通常一次成功备份后会立即把之前的备份删除,但如果删除时遇到404,也许用户的脚本就会报错,此时用户会认为此次备份失败,还要花费不必要的时间和精力去排错。我们已经遇到一些客户抱怨了。而且,404错误也让我们针对 Swift 的功能测试无法正常执行,因为社区的 swift tempest 测试用例中,在清理环境时必定会删除测试期间创建的对象,404错误则会导致清理失败,从而导致 tempest 测试用例失败。一个未经过功能测试的服务跑在公有云上,想想还是挺胆大的。

分析

那为什么会出现404错误呢?

首先得说说 write affinity 了。Swift 文档对 write affinity 有详细的介绍。简单的说,就是因为多个 region 一般都部署在物理位置彼此相隔很远的地方,用户在上传对象时,为了保证数据一致,Swift 会保证一定数量的对象副本在多个 region 完全上传完成后,才告诉用户该对象上传成功,而对象上传到其他 region 的延时较高,会导致整个对象上传的过程比较耗时,用户体验差。write affinity 就是为了解决上传到其他 region 的耗时问题,通过在本地 region 选出一些节点来代替其他 region 的节点,对象副本优先本地上传,然后由 Swift 在后台完成到其他 region 的转移工作。这样既能保证对象有多个副本,又能保证对象最终确实存储在不同 region,同时,还能够大大提升用户对象的上传速度,一石三鸟。

删除对象的时候,Swift 会根据 ring 算出对象应该在哪些 region 的哪些节点存储,然后请求对应的节点删除所有的对象副本。

也许细心的读者已经发现了问题所在。如果删除对象时,Swift 还没有做完后台转移工作,对象在其他 region 的节点根本不存在,当然会出现404错误了。

解决

其实解决思路比较简单。既然对象的上传会优先找一些本地节点,那删除时也把这些节点考虑在内,问题就会得到优雅的解决。思路很简单,但在实际 fix 的过程中要注意几点:

  • Swift 通过 ring 文件计算对象存储节点位置时,无论何时调用,返回节点的顺序是固定的。即便是找替代的本地节点,顺序也是固定的。
  • Swift 向不同节点发送请求后,会根据返回的结果计算出一个合理的最终答案。有时哪怕有些节点还没有返回应答,Swift 其实就已经能拿到最终结果,也就没有必要去等待这些反应迟钝的节点。一种特殊情况是节点的返回状态出现一半一半的答案,此时 Swift 也会特殊处理。
  • 删除对象时,那些本地的替代节点在不同 region 的个数可能会不一样,这时就需要对不同的 region 进行不同配置。比如,我们有2个 region,每个 region 有3个节点,总共6个节点,对象副本数配置为3。因为第二个 region 的3个节点存储容量较小,所以配置的 weight 值比较小,最终上传对象时,往往会选择第一个 region 的2个节点以及第二个 region 的1个节点。这时,在第一个 region 配置要请求的替代节点的个数就是1,而第二个 region 要配置2才不会造成在第二个 region 删除对象时出现404.

我的解决思路已经想社区提交了 patch,截止写这篇博客的时候,仍在 review,但大家对该方法基本没有分歧,merge 只是时间问题。

测试验证

我的测试环境有三个 region,每个 region 有1个存储 host,每个 host 配置三个节点(node),以下所有的操作均在第一个 region 内进行:

root@lingxian-dev-opxy1:~# swift-ring-builder /etc/swift/object.builder
/etc/swift/object.builder, build version 18
1024 partitions, 3.000000 replicas, 3 regions, 3 zones, 9 devices, 33.59 balance, 49.41 dispersion
The minimum number of hours before a partition can be reassigned is 1 (0:00:00 remaining)
The overload factor is 0.00% (0.000000)
Ring file /etc/swift/object.ring.gz is up-to-date
Devices:    id  region  zone      ip address  port  replication ip  replication port      name weight partitions balance flags meta
             0       1     1      10.0.2.241  6000      10.0.3.241              6000         0   1.00        342   33.59
             1       1     1      10.0.2.241  6000      10.0.3.241              6000         1   1.00        341   33.20
             2       1     1      10.0.2.241  6000      10.0.3.241              6000         2   1.00        341   33.20
             3       2     1       10.0.2.12  6000       10.0.3.12              6000         0   1.50        341  -11.20
             4       2     1       10.0.2.12  6000       10.0.3.12              6000         1   1.50        342  -10.94
             5       2     1       10.0.2.12  6000       10.0.3.12              6000         2   1.50        341  -11.20
             6       3     1       10.0.2.13  6000       10.0.3.13              6000         0   1.50        341  -11.20
             7       3     1       10.0.2.13  6000       10.0.3.13              6000         1   1.50        341  -11.20
             8       3     1       10.0.2.13  6000       10.0.3.13              6000         2   1.50        342  -10.94

在应用我的 patch 之前,我们先上传对象。这里我已经创建一个名为'lingxian'的 container,我们随便上传一个文件:

root@lingxian-dev-conf0:~# swift --insecure upload lingxian openrc
openrc
root@lingxian-dev-conf0:~# swift --insecure list lingxian
openrc

同时,查看 swift 计算出的该文件所在的节点信息,可以看到对象的三个副本应该最终存储在三个不同的 region 中:

root@lingxian-dev-opxy1:~# swift-get-nodes /etc/swift/object.ring.gz AUTH_1979617c425a412f8ad5347f2208c16f lingxian openrc
Account   AUTH_1979617c425a412f8ad5347f2208c16f
Container lingxian
Object    openrc

Partition 623
Hash      9be3551338801a37d260dae915f0dc35

Server:Port Device  10.0.2.12:6000 2
Server:Port Device  10.0.2.241:6000 2
Server:Port Device  10.0.2.13:6000 1
Server:Port Device  10.0.2.12:6000 1   [Handoff]
Server:Port Device  10.0.2.241:6000 0  [Handoff]
Server:Port Device  10.0.2.12:6000 0   [Handoff]
......

在 object 被 replicate 到其他 region 之前(可以通过停止swift-object-replicator服务来模拟),可以看看对象在不同 host 上的存储情况。因为我们配置了 write affinity,所以对象副本此时应该全部存储在第一个 region 的 host 上,在其他两个 region 中不存在:

root@lingxian-dev-ostor001:~# ll /srv/node/*/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
-rw------- 1 swift swift 189 Jun 28 11:36 /srv/node/0/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
-rw------- 1 swift swift 189 Jun 28 11:36 /srv/node/1/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
-rw------- 1 swift swift 189 Jun 28 11:36 /srv/node/2/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
root@lingxian-dev-ostor002:~# ll /srv/node/*/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
ls: cannot access /srv/node/*/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data: No such file or directory
root@lingxian-dev-ostor003:~# ll /srv/node/*/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data
ls: cannot access /srv/node/*/objects/623/c35/9be3551338801a37d260dae915f0dc35/1498649773.38690.data: No such file or directory

删除对象,果不其然,看到404:

root@lingxian-dev-conf0:~# swift --insecure delete lingxian openrc
Error Deleting: lingxian/openrc: Object DELETE failed: https://lingxian-dev-opxy1.openstacklocal:8443/v1/AUTH_1979617c425a412f8ad5347f2208c16f/lingxian/openrc 404 Not Found  [first 60 chars of response] <html><h1>Not Found</h1><p>The resource could not be found.<

然后,按照我的 patch 修改 swift-proxy 节点上的相应文件,重启 swift-proxy 服务,再重新上传文件并在 replication 发生之前删除文件,你将看不到404错误。具体命令与上述一样,我就不再赘述。