注意

本文档适用于 Ceph 开发版本。

Mantle

警告

Mantle 用于研究和开发元数据平衡算法,不应用于生产 CephFS 集群。

Mantle 是一个可编程的元数据平衡器,它集成在 MDS 中。

默认情况下(没有 Mantle),多个、活跃的 MDS 可以迁移目录以平衡元数据负载。何时、何地以及迁移多少的策略硬编码到元数据平衡模块中。

Mantle 通过保护平衡负载的机制(迁移、复制、碎片化)并使用 Lua 抑制平衡策略来工作。Mantle 基于 [1],但当前的实现没有该论文中的以下功能:不好的主意。具有

  1. 平衡 API:在论文中,用户填写何时、何地、多少以及负载计算策略。目前,Mantle 仅要求 Lua 策略返回目标负载表(例如,发送到每个 MDS 的负载量)

  2. “多少”挂钩:在论文中,有一个挂钩允许用户控制“碎片选择器策略”。目前,Mantle 没有这个挂钩。

  3. “瞬时 CPU 利用率”作为指标。

[1] 超级计算 ‘15 论文:http://sc15.supercomputing.org/schedule/event_detail-evid=pap168.html

使用 vstart 快速入门

警告

使用 vstart 开发平衡器很困难,因为在一台节点上运行所有守护进程和客户端可能会导致系统过载。让系统运行一段时间,即使可能会有很多丢失心跳的警告和很多缓慢的 MDS 警告。在大多数情况下,本指南将有效,但有时在使用 vstart 开发时,所有 MDS 都会锁定,您实际上看不到它们溢出。最好在多节点集群上运行此操作。

作为前提条件,我们假设您已经安装了mdtest或者拉取了Docker 镜像。我们使用 mdtest,因为我们需要生成足够的负载以超过平衡器中任意设置的 MIN_OFFLOAD 阈值。例如,这不会创建足够的元数据负载:

while true; do
  touch "/cephfs/blah-`date`"
done

Mantle withvstart.sh

  1. 启动 Ceph 并调整日志记录,以便我们可以看到迁移发生:

cd build
../src/vstart.sh -n -l
for i in a b c; do
  bin/ceph --admin-daemon out/mds.$i.asok config set debug_ms 0
  bin/ceph --admin-daemon out/mds.$i.asok config set debug_mds 2
  bin/ceph --admin-daemon out/mds.$i.asok config set mds_beacon_grace 1500
done
  1. 将平衡器放入 RADOS:

bin/rados put --pool=cephfs_metadata_a greedyspill.lua ../src/mds/balancers/greedyspill.lua
  1. 激活 Mantle:

bin/ceph fs set cephfs max_mds 5
bin/ceph fs set cephfs_a balancer greedyspill.lua
  1. 在另一个窗口中挂载 CephFS:

  bin/ceph-fuse /cephfs -o allow_other &
  tail -f out/mds.a.log


Note that if you look at the last MDS (which could be a, b, or c -- it's
random), you will see an attempt to index a nil value. This is because the
last MDS tries to check the load of its neighbor, which does not exist.
  1. 运行一个简单的基准测试。在我们的案例中,我们使用 Docker mdtest 镜像来创建负载:

for i in 0 1 2; do
  docker run -d \
    --name=client$i \
    -v /cephfs:/cephfs \
    michaelsevilla/mdtest \
    -F -C -n 100000 -d "/cephfs/client-test$i"
done
  1. 完成后,您可以使用以下命令杀死所有客户端:

for i in 0 1 2 3; do docker rm -f client$i; done

输出

查看第一个 MDS(可以是 a、b 或 c)的日志,我们看到每个人都没有负载:

2016-08-21 06:44:01.763930 7fd03aaf7700  0 lua.balancer MDS0: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=1.0 queue_len=0.0 cpu_load_avg=1.35 > load=0.0
2016-08-21 06:44:01.763966 7fd03aaf7700  0 lua.balancer MDS1: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=0.0 queue_len=0.0 cpu_load_avg=1.35 > load=0.0
2016-08-21 06:44:01.763982 7fd03aaf7700  0 lua.balancer MDS2: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=0.0 queue_len=0.0 cpu_load_avg=1.35 > load=0.0
2016-08-21 06:44:01.764010 7fd03aaf7700  2 lua.balancer when: not migrating! my_load=0.0 hisload=0.0
2016-08-21 06:44:01.764033 7fd03aaf7700  2 mds.0.bal  mantle decided that new targets={}

工作开始后,MDS0 获得 约 1953 个单位的负载。贪婪溢出平衡器规定负载的一半发送到您的邻居 MDS,所以我们看到 Mantle 尝试将 1953 个负载单位发送到 MDS1。

2016-08-21 06:45:21.869994 7fd03aaf7700  0 lua.balancer MDS0: < auth.meta_load=5834.188908912 all.meta_load=1953.3492228857 req_rate=12591.0 queue_len=1075.0 cpu_load_avg=3.05 > load=1953.3492228857
2016-08-21 06:45:21.870017 7fd03aaf7700  0 lua.balancer MDS1: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=0.0 queue_len=0.0 cpu_load_avg=3.05 > load=0.0
2016-08-21 06:45:21.870027 7fd03aaf7700  0 lua.balancer MDS2: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=0.0 queue_len=0.0 cpu_load_avg=3.05 > load=0.0
2016-08-21 06:45:21.870034 7fd03aaf7700  2 lua.balancer when: migrating! my_load=1953.3492228857 hisload=0.0
2016-08-21 06:45:21.870050 7fd03aaf7700  2 mds.0.bal  mantle decided that new targets={0=0,1=976.675,2=0}
2016-08-21 06:45:21.870094 7fd03aaf7700  0 mds.0.bal    - exporting [0,0.52287 1.04574] 1030.88 to mds.1 [dir 100000006ab /client-test2/ [2,head] auth pv=33 v=32 cv=32/0 ap=2+3+4 state=1610612802|complete f(v0 m2016-08-21 06:44:20.366935 1=0+1) n(v2 rc2016-08-21 06:44:30.946816 3790=3788+2) hs=1+0,ss=0+0 dirty=1 | child=1 dirty=1 authpin=1 0x55d2762fd690]
2016-08-21 06:45:21.870151 7fd03aaf7700  0 mds.0.migrator nicely exporting to mds.1 [dir 100000006ab /client-test2/ [2,head] auth pv=33 v=32 cv=32/0 ap=2+3+4 state=1610612802|complete f(v0 m2016-08-21 06:44:20.366935 1=0+1) n(v2 rc2016-08-21 06:44:30.946816 3790=3788+2) hs=1+0,ss=0+0 dirty=1 | child=1 dirty=1 authpin=1 0x55d2762fd690]

最终负载会移动:

2016-08-21 06:47:10.210253 7fd03aaf7700  0 lua.balancer MDS0: < auth.meta_load=415.77414300449 all.meta_load=415.79000078186 req_rate=82813.0 queue_len=0.0 cpu_load_avg=11.97 > load=415.79000078186
2016-08-21 06:47:10.210277 7fd03aaf7700  0 lua.balancer MDS1: < auth.meta_load=228.72023977691 all.meta_load=186.5606496623 req_rate=28580.0 queue_len=0.0 cpu_load_avg=11.97 > load=186.5606496623
2016-08-21 06:47:10.210290 7fd03aaf7700  0 lua.balancer MDS2: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=1.0 queue_len=0.0 cpu_load_avg=11.97 > load=0.0
2016-08-21 06:47:10.210298 7fd03aaf7700  2 lua.balancer when: not migrating! my_load=415.79000078186 hisload=186.5606496623
2016-08-21 06:47:10.210311 7fd03aaf7700  2 mds.0.bal  mantle decided that new targets={}

实现细节

大部分实现都在 MDBalancer 中。指标通过 Lua 栈传递给平衡器策略,并返回一个负载列表给 MDBalancer。它与当前的平衡器实现一起存在,并通过 Ceph CLI 命令(“ceph fs set cephfs balancer mybalancer.lua”)启用。如果 Lua 策略失败(无论什么原因),我们会回退到原始的元数据负载平衡器。平衡器存储在 RADOS 元数据池中,MDSMap 中的字符串告诉 MDS 使用哪个平衡器。

将指标暴露给 Lua

指标直接作为全局变量暴露给 Lua 代码,而不是使用定义良好的函数签名。有一个全局的“mds”表,其中每个索引是一个 MDS 编号(例如,0),每个值是一个指标和值的字典。Lua 代码可以使用类似以下方式获取指标:

mds[0]["queue_len"]

这与 OSD 中的 cls-lua 不同,后者具有定义良好的参数(例如,输入/输出缓冲列表)。直接暴露指标使得在不更改 Lua 侧 API 的情况下更容易添加新指标;我们希望 API 随着我们探索哪些指标重要而增长和缩小。这种方法的缺点是,编写 Lua 平衡器策略的人必须查看 Ceph 源代码以查看哪些指标被暴露。我们认为 Mantle 开发者无论如何都会与 MDS 内部进行接触。

暴露给 Lua 策略的指标与已经存储在 mds_load_t 中的指标相同:auth.meta_load()、all.meta_load()、req_rate、queue_length、cpu_load_avg。

编译/执行平衡器

这里我们使用lua_pcall instead of lua_call因为我们要处理 MDBalancer 中的错误。我们不想错误向上传播到调用链。cls_lua 类想要自己处理错误,因为它必须优雅地失败。对于 Mantle,我们不在乎 Lua 错误是否导致我们的平衡器崩溃——在这种情况下,我们会回退到原始平衡器。

使用lua_call而不是lua_pcall的性能提升在这里不会得到利用,因为平衡器默认每 10 秒被调用一次。

将策略决策返回给 C++

我们强制 Lua 策略引擎返回一个值表,对应于要发送到每个 MDS 的负载量。这些负载直接插入到 MDBalancer 的“my_targets”向量中。我们不允许 MDS 返回一个 MDS 和指标的值表,因为我们希望决策完全在 Lua 侧做出。

通过栈迭代 Lua 返回的表。在 Lua 行话中:将一个虚拟值推到栈上,下一个迭代器用(k,v)对替换栈顶。读取每个值后,弹出该值,但保留下一个调用lua_next.

从 RADOS 读取

当 MDS Map 中的平衡器版本发生变化时,所有 MDS 都会从 RADOS 读取平衡代码。平衡器同步从 RADOS 拉取 Lua 代码。我们使用超时来做这件事:如果异步读取在平衡器滴答间隔的一半内没有返回,操作将被取消并返回连接超时错误。默认情况下,平衡器滴答间隔为 10 秒,因此 Mantle 将使用 5 秒超时。这种设计允许 Mantle 在任何与 RADOS 相关的问题发生时立即返回错误。

我们使用这种实现,因为我们不想在全局 MDS 锁内进行阻塞的 OSD 读取。这样做如果任何 OSD 不响应,将会导致 MDS 集群崩溃——这在 ceph-qa-suite 中通过将所有 OSD 设置为 down/out 并确保 MDS 集群保持活跃来测试。

一种方法是在处理 MDS Map 时异步触发读取并在后台填充 Lua 代码。我们不能这样做,因为 MDS 不支持守护进程本地回退,并且平衡器假设所有 MDS 同时做出相同决定(例如,导入器、导出器等)。

调试

Lua 策略中的日志记录将出现在 MDS 日志中。语法与 cls 日志记录接口相同:

BAL_LOG(0, "this is a log message")

它是通过将一个包装dout日志记录框架dout_wrapper) 的函数传递给 Lua 并使用lua_register()原语实现的。Lua 代码实际上是在调用 C++ 中的dout函数。

警告和信息消息使用 clog/Beacon 进行集中处理。成功消息仅在第一个 MDS 发生版本更改时发送,以避免向ceph -w工具发送垃圾信息。这些消息用于集成测试。

测试

测试是通过 ceph-qa-suite(tasks.cephfs.test_mantle)完成的。我们不测试无效平衡器日志记录和加载实际的 Lua VM。

由 Ceph 基金会带给您

Ceph 文档是一个社区资源,由非盈利的 Ceph 基金会资助和托管Ceph Foundation. 如果您想支持这一点和我们的其他工作,请考虑加入现在加入.