注意

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

libcephfs代理的设计

问题描述

当应用程序通过libcephfs.so library, a cache is created locally inside the process. The libcephfs.so implementation already deals with memory usage of the cache and adjusts it so that it doesn’t consume all the available memory. However, if multiple processes connect to CephFS through different instances of the library, each one of them will keep a private cache. In this case memory management is not effective because, even configuring memory limits, the number of libcephfs instances that can be created is unbounded and they can’t work in a coordinated way to correctly control ressource usage. Due to this, it’s relatively easy to consume all memory when all processes are using data cache intensively. This causes the OOM killer to terminate those processes.

提出的解决方案

高层次方法

主要思想是创建一个libcephfs_proxy.so library that will provide the same API as the original libcephfs.so, but won’t cache any data. This library can be used by any application currently using libcephfs.so in a transparent way (i.e. no code modification required) just by linking against libcephfs_proxy.so instead of libcephfs.so, or even using LD_PRELOAD.

一个新的libcephfsd daemon will also be created. This daemon will link against the real libcephfs.so library and will listen for incoming connections on a UNIX socket.

当应用程序启动并通过libcephfs_proxy.so library, it will connect to the libcephfsd daemon through the UNIX socket and it will forward all CephFS requests to it. The daemon will use the real libcephfs.so to execute those requests and the answers will be returned back to the application, potentially caching data in the libcephfsd process itself. All this will happen transparently, without any knowledge from the application.

守护进程将在不同应用程序之间共享低级别的libcephfs.so mounts between different applications to avoid creating an instance for each application, which would have the same effect on memory as linking each application directly to the libcephfs.so library. This will be done only if the configuration defined by the applications is identical. Otherwise new independent instances will still be created.

一些libcephfs.so functions will need to be implemented in an special way inside the libcephfsd daemon to hide the differences caused by sharing the same mount instance with more than one client (for example chdir/getcwd cannot rely directly on the ceph_chdir()/ceph_getcwd()libcephfs.so).

Initially, only the subset of the low-level interface functions of libcephfs.so that are used by the Samba’s VFS CephFS module will be provided.

常见组件的设计

网络协议

由于通过UNIX套接字的连接是到同一机器上运行的另一个进程,并且我们需要传递的数据相当简单,我们将通过使用代码本身实现的非常简单的序列化来避免通用XDR编码/解码和RPC传输的所有开销。对于未来,我们可能会考虑使用cap'n proto (https://capnproto.org),它声称编码和解码没有开销,并且如果网络协议需要在将来修改,将提供一种简单的方法来支持向后兼容性。

功能协商

在通过UNIX套接字进行初始连接时,客户端将发起它想要启用的功能的协商。目前它支持一个功能位图,表示支持/需要/期望的功能,但它允许通过添加更多字段来扩展包含协商数据的结构,而不会破坏向后兼容性。

该结构本身包含结构的版本和大小,以及支持的最小所需版本,允许其他对等方即使不完全理解其内容也能读取传输的数据,并调整到双方都知道的最高版本。

客户端将发送支持的功能位图、必须可用的功能位图(否则连接无法继续)以及客户端希望启用的功能位图。当服务器收到它时,它将在双方创建一个支持的功能位图,并验证服务器所需的所有功能是否由客户端支持。它还将将其期望的功能添加到客户端期望的功能中。这些数据将发送回客户端,客户端将进行最终检查并决定最终启用的功能集。一旦这个功能集发送到服务器,两个对等方都可以开始使用启用的功能。

设计的libcephfs_proxy.so library

该库基本上将连接到监听libcephfsd daemon is listening, wait for requests coming from the application, serialize all function arguments and send them to the daemon. Once the daemon responds it will deserialize the answer and return the result to the application.

本地缓存

虽然该库的主要目的是避免每个进程上的独立缓存,但初步测试表明,当所有请求都通过代理守护进程时,基于元数据操作和/或小文件的工作负载性能大幅下降。为了最小化这一点,应该实现元数据缓存。元数据缓存比数据缓存小得多,并将提供内存使用和性能之间的良好权衡。

为了以安全的方式实现缓存,需要在数据变得过时之前正确使其失效。目前libcephfs.so提供了失效通知,可以用来实现这一点,但其语义尚未完全理解,因此libcephfs_proxy.so库中的缓存将在未来版本中设计和实现。

设计的libcephfsd守护进程

守护进程将是一个常规进程,将集中来自同一机器上其他进程的libcephfs请求。

进程维护

由于进程将作为独立的守护进程工作,将提供一个简单的systemd单元文件来将其作为常规系统服务进行管理。很可能未来这将集成在cephadm内部。

如果libcephfsd daemon crashes, we’ll rely on systemd to restart it.

特殊函数

一些函数需要在libcephfsd daemon to provide correct functionality since forwarding them directly to libcephfs.so could return incorrect results due to the sharing of low-level mounts.

底层struct ceph_mount_info的共享

代理的主要目的是当进程访问相同数据时,避免为每个进程创建一个新的挂载点。为了能够提供这一点,我们需要“虚拟化”挂载点,并让应用程序相信它正在使用自己的挂载点,而实际上它可能在使用一个共享的挂载点。

守护进程将跟踪用于连接到卷的Ceph帐户、配置文件以及在任何挂载卷之前所做的任何特定配置更改。只有当所有设置都与另一个已经挂载的实例完全相同时,挂载才会被共享。守护进程不会理解CephFS设置或任何潜在的设置之间的依赖关系。出于这个原因,将进行非常严格的比较:配置文件需要相同,并且之后所做的任何其他更改都需要设置为完全相同的值,并按相同的顺序,以便两个配置可以被认为是相同的。

确定两个配置是否相同的检查将在挂载卷之前执行(即ceph_mount()。这意味着在配置阶段,我们可能会有许多同时分配的挂载,但尚未挂载。但是其中只有一个将成为真正的挂载。其他的将保持未挂载,并且最终将在用户卸载并释放它们时被销毁。

以下函数将受到影响:

  • ceph_create

    这个函数将分配一个新的ceph_mount_info结构,提供的id将被记录以供将来比较可能匹配的挂载。

  • ceph_release

    这个函数将释放一个未挂载的ceph_mount_info结构。未挂载的结构不会与任何人共享。

  • ceph_conf_read_file

    这个函数将读取配置文件,计算校验和并制作副本。副本将确保自校验和计算以来配置文件没有变化,校验和将记录以供将来比较可能匹配的挂载。

  • ceph_conf_get

    这个函数将获取请求的设置,并记录它以供将来比较可能匹配的挂载。

    即使这可能看起来不必要,因为守护进程将配置视为一个黑盒,也可能有一些动态设置,它可以根据外部因素返回不同的值,因此守护进程还要求任何请求的设置返回相同的值,以考虑两个配置相同。

  • ceph_conf_set

    这个函数将记录修改后的值以供将来比较可能匹配的挂载。

    在正常情况下,即使挂载卷之后,也可能设置某些设置。代理不会允许这样做,以避免与其他共享相同挂载的客户端发生潜在干扰。

  • ceph_init

    这个函数将是一个空操作。调用这个函数会触发分配一些资源并启动一些线程。如果这个ceph_mount_info结构最终没有挂载,因为它与一个已经存在的挂载匹配,这将浪费资源。

    只有在挂载时(即ceph_mount())没有与已经存在的挂载匹配,挂载才会被初始化并同时挂载。

  • ceph_select_filesystem

    这个函数将记录选择的文件系统以供将来比较可能匹配的挂载。

  • ceph_mount

    这个函数将尝试找到一个与为这个ceph_mount_info结构定义的所有配置都匹配的活动挂载。如果没有找到,它将被挂载。否则,将共享已经存在的挂载。

    未挂载的ceph_mount_info结构将保留在挂载的关联结构周围。

    所有“真实”挂载都将针对卷的绝对根(即“/”)进行,以确保它们以后可以与其他客户端共享,无论它们是否使用相同的挂载点。这意味着挂载后,守护进程将需要解析并存储“虚拟”挂载点的根inode。

    当前工作目录(CWD)也将初始化为相同的inode。

  • ceph_unmount

    这个函数将客户端从挂载的ceph_mount_info结构中分离,并将其重新附加到其中一个关联的未挂载结构。如果这是挂载的最后一个用户,它将被最终卸载。

    调用此函数后,客户端将继续使用一个仅由其自己使用的私有ceph_mount_info结构,因此可以安全地进行其他配置更改和操作。

将访问限制在预期的挂载点

由于有效挂载点可能与真实挂载点不匹配,如果不小心处理,一些函数可能会返回有效挂载点之外的inode。为了避免这种情况并提供用户应用程序期望的结果,我们需要在libcephfsd守护进程。

有三个特殊情况需要考虑:

  1. 处理以“/”开头的路径

  2. 处理包含“..”(即父目录)的路径

  3. 处理包含符号链接的路径

当发现这些特殊路径时,需要以特殊的方式处理,以确保返回的inode是客户端期望的。

以下函数将受到影响:

  • ceph_ll_lookup

    Lookup接受“..”作为解析的名称。如果父目录是“虚拟”挂载点的根(这可能不一定是真实挂载点),我们将需要返回挂载时存储的对应于“虚拟”挂载点的inode,而不是真实的父目录。

  • ceph_ll_lookup_root

    这个函数需要返回挂载时存储的根inode。

  • ceph_ll_walk

    这个函数将在守护进程内部完全重新实现,以能够正确解析每个路径组件和符号链接,并正确处理“/”和“..”。

  • ceph_chdir

    这个函数将解析传递的路径,并将其与相应的inode存储在当前的“虚拟”挂载中。不会调用真实的ceph_chdir()

  • ceph_getcwd

    这个函数只需返回先前ceph_chdir()调用中存储在“虚拟”挂载中的路径。

处理AT_FDCWD

任何接收文件描述符的函数也可能接收特殊值AT_FDCWD。这些函数需要检查该值并使用“虚拟”CWD。

测试

代理应该对任何已经使用libcephfs.so。这也适用于测试脚本和应用程序。因此,任何现有的针对常规libcephfs.so库的测试也可以用来测试代理。

由 Ceph 基金会带给您

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