欢迎光临散文网 会员登陆 & 注册

FAST23-Citron: Distributed Range Lock Management with One-sided

2023-03-24 16:29 作者:HXK是条鱼  | 我要投稿

作者信息:http://storage.cs.tsinghua.edu.cn/~gj/

暂时没看到开源,他们组很强,但是开源的东西,相对比较少,貌似也就近几年开始有些开源。

摘要

范围锁(range lock)允许并发访问共享存储(shared storage)的不相交部分。然而,现有的范围锁管理器依赖于集中的 CPU 资源来处理锁请求,这会导致服务器端的 CPU 瓶颈并且在分布式场景中表现不佳。

该文提出了 Citron,一种支持 RDMA 的分布式范围锁管理器(distributed range lock manager, DRLM)Citron 通过在范围锁获取和释放路径中仅使用单边 RDMA 来绕过(bypasses)服务器端 CPU。Citron 使用名为线段树(segment tree)的静态数据结构来管理范围锁,该结构能有效地适应动态定位和动态大小的范围,且仅需要客户端进行有限且几乎恒定的同步成本Citron 还可以在微秒级别内自行扩展,以适应运行时不断增长的共享存储。实验结果表明,在各种工作负载下,Citron 的吞吐量为基于 CPU 的方法的 3.05 倍,并且尾时延比基于 CPU 的方法低 76.4%。

背景

RDMA

提到现成的 RNIC(例如,从 Mellanox Connect-IB 到 NVIDIA ConnectX-7)也支持掩码原子 verbs,其性能类似于标准原子 verbs,但有更大的灵活性。

对于 masked-CAS,用户需要提供一个比较位掩码和一个交换位掩码。比较和交换步骤均针对相应的位掩码执行。 屏蔽掉的位将不会被比较或交换。

对于 masked-FAA,用户需要提供一个将 8 个字节拆分为不同字段的位掩码。位掩码中的每个设置位表示字段的左边界,FAA 在每个字段内单独执行。边界可以出现在任何位置;允许非字节对齐的字段。

Distributed Range Lock Management

CPU-based centralized solutions. 如 Figure 1(a)所示,现有的多数的 DRLM 都极度的依赖服务端的 CPU,范围锁操作通过 RPC 接口,基本上都由服务端的 CPU 执行,这可能会限制吞吐并存在较高的排队延迟了。用有限的 CPU 资源执行复杂的范围锁操作不仅会成为吞吐量的瓶颈,而且会对 CPU 需求高的其他服务造成损害;使用 RPC 范例,服务端 CPU 从 RNIC 端队列中获取并处理 RPC 请求,在高并发情况下,请求会在 RNIC 中排队,导致排队延迟高,并且 CPU 不能处理同一队列中的其他范围锁请求,即使它们在逻辑上不冲突。

Figure 1(c)是在测试平台上运行 eRPC 来证明高延迟的实验结果,客户端同步向一台服务器发送 RPC,RPC 处理程序运行 100 ns,测量服务器端排队延迟,即 RPC 到达 NIC 和 CPU 处理 RPC 之间的时间。对于 32 个客户端,平均排队时延为 5.4 µs,比 RDMA 往返时间 (RTT) 的 2 倍还要多。p99 时延甚至达到 9.8 µs(4-5 RTT)。

Mutex-based decentralized solutions.  将范围划分为多个段并将每个段与互斥体相关联是分散的范围锁管理的一个想当然的解决方案,该方案只在访问粒度是静态且事先已知的情况下才有效。在未对齐或动态大小(unaligned or dynamically-sized)范围的情况下,该解决方案的吞吐可能会下降 92%,尾时延提高 5.65 倍。

Figure 1: Flaws of RPC-based DRLMs and our motivation.

设计

如 Figure 1(b)所示,基于单边 RDMA 的 DRLM 可以消除排队延迟并通过将所有锁操作卸载到 RNIC 的定制 ASIC 来提供更高的吞吐量,充分发挥 RNIC 的性能潜力。

Challenges and Design Principles

Challenge 1. 需要一个单边 RDMA 感知的数据结构,能够有效地管理动态定位和大小的范围锁,并解决它们之间的冲突。

--> Static tree structure for dynamic ranges.  Citron 将每个请求范围(requested range)尽可能精确地映射到静态数据结构线段树(segment tree)恒定数量的节点,以有效地管理动态范围锁条目。

Challenge 2. 为了实现低时延和高吞吐,必须根据复杂的范围锁语义调整锁协议,以缩短关键路径长度。

--> Minimized synchronization overhead.  Citron 协议与 RDMA 语义以及线段树的布局紧密耦合,使同步成本最小化接近常数。获取锁最少仅需要两个往返。

Challenge 3. 真实的存储资源不总是固定大小的,因此还必须有效地处理可能动态增长的存储大小。

--> Runtime capacity expansion.  虽然线段树是静态的,但较小的树可以被视为较大树的子树,Citron 利用这个特性,使用少量的服务端 CPU 周期在运行时扩展树的容量。

Basic Assumptions

Address space. Citron 在抽象地址空间 [0, ∞) 内维护范围锁。

Cluster infrastructure. 除了一个在 DRAM 中托管 Citron 组件的 lock server,Citron 需要有一个集群管理器(cluster manager,CM)和一个元数据服务器(metadata server,MDS)。CM 协调配置更改并检测 client 故障。MDS 维护 Citron 组件的地址,以便使用单边 RDMA。CM 和 MDS 不必在独立的机器上运行:它们可以在 RPC 接口后面的 lock server 上运行。

Clock well-behavedness.  Citron 假定所有 client 的时钟都表现良好,即它们以几乎相同的速度前进。Citron 不要求时钟同步(synchronized)。

Components of Citron

Citron 使用 lock tree 和 spillover mutex 来维护范围锁。lock tree 负责 [0,N) 内的锁,spillover mutex 体负责 [N,∞),其中 N 在初始化时指定。Citron 还包括一个最大化器(maximizer),使 client 能够扩展 lock tree,即增加 N。

Lock tree. Lock tree 是一个完美平衡的线段树,其中每个节点代表一个连续的范围。根(root)代表整个范围[0,N);对于每个非根节点,它和它的兄弟节点每个都接收到由其父节点表示的范围的相等且连续的份额(均分)。这样的结构决定了任何一个节点所代表的范围都只与它的祖先和它的后代相交。正统的线段树是二叉树,该文将 Citron 中的 lock tree 定义为四元线段树,即所有内部节点的度(即子节点数)均为 4,且叶节点表示的范围大小为 64 。这些设计旨在限制树的高度,从而限制每个锁请求发布的需要的 RDMA verbs 的数量。

因为所有内部节点都具有相同的度,所以 lock tree 中不需要指针,所有节点都按级别顺序放置在一个连续的平面数组中,并由正整数索引。 树导航(tree navigation)只是简单的节点索引算法。如 Figure 2 显示了 lock tree 的前三个层和节点索引,其中节点的宽度对应于它们表示的范围,可以很容易地得到对于索引为 x 的节点,

Figure 2: The structure and node indices of the lock tree.

Spillover mutex. 溢出互斥量表示 [N,∞),即 lock tree 处理范围锁请求的越界部分。它可以采用任何对单边RDMA 友好的设计,该文使用 DSLR (SIGMOD 18 的一篇文章) 来实现这个互斥量。

Maximizer. 最大化器是一个初始为零的 8 字节变量,可由单边 RDMA 访问。当 client 锁定不包含在 [0,N) 内的范围时,client 会修改此变量。

Formats of Lock Tree Nodes

Lock tree 中的所有节点都是 8 字节的变量,可由各种 RDMA 单边原语访问。内部节点(Internal nodes)和叶节点(Leaf nodes)具有不同的格式,并由不同的 RDMA 原子动词操作,如 figure 3 所示。

Figure 3: 64-bit representations of internal and leaf nodes.

Leaf nodes. 叶节点是 8 字节位图(bitmap),其中每个位都与一个共享存储单元相关联。位图中设置位表示某个 client 占用资源的对应单位,反之亦然。client 使用 RDMA masked-CAS 来设置和清除 64 位中的每一位。

Internal nodes.  每个内部节点分为六个字段。Exp 和 Occ 是标志位,其余四个是计数器。client 使用 RDMA masked-FAA 来修改这些字段。{TCnt,TMax} 和 {DCnt,DMax} 是遵循 Lamport 面包店算法思想的两个计数器对。具体来说,在每个计数器对中,Max 是下一个可用的“票号”,Cnt 是当前持有锁的票号。client 通过在 Max 字段上执行 FAA 获得票证,轮询 Cnt 字段直到匹配票证,进入临界区,最后 client 完成操作后在 Cnt 字段上执行 FAA。{TCnt,TMax} 计算“本节点”(this node)的锁请求,而 {DCnt,DMax} 计算后代的锁请求。

Exp(代表扩展)通知 client lock tree 扩展事件。Occ(代表已占用)如果已设置,则会阻止后代的冲突的锁请求。设置了 Occ 标志的节点将称为占用节点(occupied node)。

Lock Acquisition

下面是该文主要的锁策略。后面基本是围绕下面的算法展开的。

算法 1
算法 2

算法 2 中的 k 和 m,先提前提一嘴,之后还会讲,k 大致就是将实际要锁的范围划分为 k 个 lock tree 中的节点,m 和锁冲突通知的深度有关。TCnt 和 DCnt 在算法 3 释放锁的过程中更改。

算法 1 显示了 client 如何获取范围 [l, r) 上的锁,主要就是在判断了要上锁的范围是不是在当前的 lock tree 中。 文章表示 spillover mutex 已经得到充分研究,所以该文省略了有关 spillover mutex 的详细信息并专注于 lock tree。 现在假设 [l, r) 完全包含在 [0, N) 中,算法 2 显示了整个锁获取过程,包括两个步骤:

  1. 将要锁的范围适当地分成子范围,使得每个子范围对应于单个树节点;

  2. 按升序获取每个子范围的锁。

对于每个子范围对应的节点,上面的步骤 2 进一步分解为四个阶段:

2(a).  如果是内部节点(internal node),则锁定节点;

2(b).  等待直到该节点所有的祖先节点的锁都被释放;

2(c).  如果该节点是叶子节点则锁定节点,否则占据(occupy)它;

2(d).  通知该节点的祖先并等待其后代节点。

低(low)和高(high)描述远离和靠近根的树节点;范围小的锁请求具有更高的优先级。

步骤 1:分割范围

对应算法 2 代码的 4-7 行

这一步不需要网络的交互,因为 client 事先知道 lock tree 的结构并且可以在本地进行所有计算。使用线段树,任何连续范围都可以表示为 O(logN) 个树节点的聚合,在最坏的情况下,必须锁定 Θ(logN) 个节点以精确锁定范围 [l, r),但是这可能会导致较高的时延,因为必须按顺序锁定节点以防止死锁。一个想当然的方法是简单地锁定完全覆盖 [l, r) 的最低节点,但是这可能会导致严重的冲突,如覆盖 [N/2−1,N/2+1] 范围的最低节点就是根节点,与所有其他锁定请求冲突。

Citron 允许 client 最多锁 k 个节点来达到平衡。为了减少错误的锁冲突,Citron 尝试最小化覆盖但未请求(unrequested)的范围。 该优化目标可以表述为树背包问题(tree knapsack problem)并通过现有算法解决。Citron 背包算法的时间复杂度为O(k的2次方 logN),如果经过适当优化,通常可以在 0.5 µs 内完成(没提具体的算法,就这么提了一句)。 增加 k 以时延换取更少的错误冲突,反之亦然。Citron 为了低时延,将 k 固定为 2。

算法 2 中的过程 AcquireLockOnTree 显示了此步骤的工作原理。首先运行背包算法来找到要锁定的节点的最佳组合(第 5 行)。 然后,按顺序锁定节点(第 6-7 行)。

步骤 2:获取锁

a. 锁定内部节点

对应算法 2 代码的 9-11 行,主要是为了解决不同 client 对同一内部节点访问的冲突,保证同一时刻只能有一个 client 对该内部节点进行操作。

工作流程遵循 Lamport 的面包店算法。 具体来说, client 首先增加节点的 TMax 字段以获得“票”, 之后轮询 TCnt 字段,直到它与“票”的 TMax 字段相匹配(第 10-11 行)。

b. 等待节点的祖先节点

对应算法 2 代码的 12-19 行,主要是为了避免与祖先节点的冲突,如果祖先节点正被锁住或者先发出锁定请求,则等待祖先节点执行操作。

从这个阶段开始,Citron 开始解决不同节点之间的冲突,主要原则是在多个并发的锁请求中,Citron 优先考虑范围最小的节点,因为它通常对时延最敏感。(但是暂时看下来该算法貌似只是优先考虑小的节点,但是小的节点不一定是小的锁范围的,可能是大范围刚好拆成一个大节点和小节点)

该节点的所有祖先都与该节点冲突,如果该节点的一个祖先持有锁,client 必须等到它被释放。代码 15 行表示,如果根节点标记了拓展,则锁操作直接中断。

此阶段由多次迭代组成,在每次迭代中,client 先读取节点的祖先节点(第 14 行),然后查看祖先节点的 Occ 标志以确认是否存在被占用的节点。如果没有 next 应该等于 nil,然后 17 行直接 break 退出迭代。如果有,则选择最低的一个节点(最近的一个祖先节点)赋值给 next(第 16 行),之后 client 一直等到 next 节点的锁被释放(第 18 行),第 19 行以刚刚的节点为对象,重复新的一次迭代。因为节点是从低到高检查的,且越高的节点和其他节点冲突的概率越大,祖先节点被占用的情况下,他的后代节点一定不会被锁,所以迭代完成的瞬间,能保证这一刻该节点的祖先节点没有被占用,没有冲突。

c. 占用或锁定节点

对应算法 2 代码的 20-28 行,对于叶子节点直接用原子操作 CAS 去加锁,内部节点用 FAA 将 Occ 加一表示抢占。

Client 目前可以确保节点的祖先没有持有锁(内部节点仅仅 Occ 位被标记不代表加锁,但是不加锁 Occ 位一定不被标记)。

如果节点是内部节点,client 使用 MaskedFAA 设置其 Occ 标志(阶段 a 保证了同一时刻只会有一个 client 占用该节点,所以直接加一就行)。通过设置节点的 Occ 标志,新到达的节点后代的锁请求将在步骤 2(b) 中检测并等待 ,这确保了内部节点有限的等待时间。

如果是叶子节点,因为前面提到过,叶子节点的大小为 64,内部还可以划分,所以 21 行首先将需要锁定的范围复制给 mask(应该是 64 位中需要的地方为 1 ,其他为 0 这个样子),然后代码 22 行 MaskedCAS 检测需要的位置是否被占用,没被占用的话应该是 0,如果是 0 的话,改为 1 (就是 mask),返回 true;但是前面的阶段 a 和 b 都是针对检查内部节点的(b 检测祖先节点,祖先节点只会是内部节点),无法保证该叶子节点中被需要的位置没有被占用或该时刻没有其他 client 正在锁定,所以可能会出现冲突,导致 MaskedCAS 失败(即没有 swap),之后就需要返回到步骤 2(b) 的开头(第 26 行),因为其他 client 可能已经设置了该叶子节点祖先的 Occ 标志。为了避免饥饿,MaskedCAS 失败多了,Citron 提供了一种变通方法,选择锁定该叶子节点的其父节点,重新启动锁获取过程,因为上面提了通过设置内部节点的 Occ 标志,可以避免后代节点的锁请求。

d. 通知祖宗节点,等待子孙节点

对应算法 2 代码的 29-37 行,给低的节点优先级,避免上下节点的冲突。

上面的阶段只是勉强知道了那个时刻,节点的祖先没有持有锁,但是无法保证之后的祖先子孙锁操作不会产生冲突,比如有 a,b,c 3个锁请求,都是内部节点,且 a 是 b 的父节点,b 是 c 的父节点,同时到达,他们同时执行完前面的阶段,并将各自的 Occ 位标记,但是他们无法感知各自的存在,但是他们之间会产生冲突。因此需要一种机制来检测。

有 2 种想当然的方案,第一个解决方案,由子孙节点去通知所有的祖先节点,但是这可能会导致小范围锁定请求的高延迟(越小的节点越低,需要通知的祖先越多);第二种解决方案,由祖先节点去检查该节点的所有后代是否存在可能的冲突,但是这可能会导致网络流量过大,因为节点的后代数量随着节点的高度呈指数增长。

然后 Citron 提出了一种折中的方案,维护一个全局一致的参数 m,从该节点的父节点开始,通知到第 m 个祖先,并检查 m 层内所有该节点的后代。就是对之前的方案进行了折中。lock tree 的节点在内存中是按层序排列的,且是连续的,所以检测后代时,只需要 RDMA READ m 次。 在 Citron 的实现中 m = 4。

为了确保子孙节点在祖先节点检查前完成了通知,祖宗需要检查节点的后代之前等待一段时间 Twait,子孙确保在 Twait 内完成通知,若完不成则终止请求。为了确定 Twait,文章通过计算 RDMA 往返的最大次数,并为每个往返预留一个时间单位来确实,论文测试 RDMA RTT 约为 2 µs; 保守地为每次往返保留 5 µs,然后需要 3 个 RTT, 因此,Twait = 15 µs(具体的看论文)。

回到算法 2 代码,首先 29 行获取当前的时间,之后 30 行获取需要通知的祖先节点,然后 31 行为这些祖先节点的 DMax 位加一,并读取根节点的信息,32 行如果通知操作的时间超出了则终止,33 行是检测是否 lock tree 在拓展,34 行对于内部节点(叶节点没有后代)等待 Twait 时间,之后的代码通过检测子孙内部节点的 Dcnt 和 DMax 值是否相等来判断子孙节点是否有冲突,若有则等待,反复检查。Dcnt 在节点释放锁时改变。若子孙没有冲突,Dcnt = DMax,返回 Acquired。

步骤 2(d) 依赖于正确选择的 m 和 Twait 才能表现良好,然后论文还提了一下参数调整,这里不写了。

Lock Release

 算法 3

算法 3 是锁释放过程,对于范围内的所有节点,如果节点是叶子节点,使用 MaskedCAS 解锁(第 3-5 行,这次反过来赋值为 0);对于内部节点,使用 MaskedFAA 清除 Occ 标志,将 TCnt 计数器加一(第 7 行)。之后对于在锁获取期间已被通知的所有节点的祖先,使用 MaskedFAA 将他们的 DCnt 计数器加一(第 8-9 行)。 在后面是拓展时的操作。上面的操作需要的 RDMA verbs 都可以一起批处理以减少延迟。

Proof Sketch of Correctness

Figure 4: Demonstration of the lock acquisition workflow and the lock conflicts resolved by each phase. Table rows are the time dimension (Prev = Previous, Con = Concurrent, Fut = Future), and table columns are the space dimension (Anc = Ancestors, Desc = Descendants). Lock conflicts occurring at any time and any position will all be resolved.

Citron 中的范围锁由 lock tree 上的节点和可能的 spillover mutex 组成,所有这些都是单独和顺序获取的。 spillover mutex 的正确性已经被其他论文证明,只需要证明锁在单个树节点上的正确性即可。

如图4所示(图没啥多的解释。。),无论冲突什么时候发生,Citron 都能解决,具体来说,步骤 2(a)+(c) 确保节点处不存在冲突的 client:2(a) 用于内部节点,2(c) 用于叶节点。步骤 2(b) 和 2(c)+(d) 分别确保节点的祖先和后代不存在持有的锁。 (具体说步骤2(a)保证了同一内部节点不会产生冲突,步骤2(c)避免了叶子节点之间的冲突,步骤2(b)将冲突的节点拉到了同一起始时间,之后在步骤2(d)中,因为都是先执行通知祖先的操作,祖先节点在子孙节点执行并释放前被只能等待,最低的节点最早执行,避免了冲突)

Liveness. Liveness 意味着没有无限长的临界区(等待时间),锁获取过程总是在一些有限的时间返回(尽管结果可能是 Aborted)。具体来说,第 1 步是有限的。步骤 2(a) 使用 Lamport 的面包店算法,它是无饥饿的。由于饥饿避免机制,步骤 2(c) 对于内部节点显然是有限的,对于叶节点也是有限的。对于步骤 2(b) 和 2(d),client 在每个阶段只能等待有限数量的其他 client,因为这些 client 最终会中止或释放他们的锁,所以这些阶段也是有限的。 综上所述,锁获取需要的时间是有限的。

不过暂时没有看到对于返回 Aborted 的情况的处理,返回 Aborted 时,可能更改了 TMax,Occ,DMax 等,应该会有相应的处理?

Fast Path Optimization

就是操作的优化,减少 RDMA RTT。

当 Citron 未处于严重争用状态时,多项优化适用于锁获取路径。首先,RDMA 在同一 QP 中对任何单边 verbs 的保序性,可以将锁获取路径中的 RDMA 动词一起批处理(具体看论文)。

其次,如果节点的子节点都是叶节点,可以显式锁定所有节点的后代以跳过步骤 2(d) 中的等待时间(具体看论文)。

通过这些优化,乐观地说,获取锁只需要两次 RDMA 往返,确保低延迟。

Scaling the Lock Tree

Scale up

存储资源的大小可以随着写入而增长,可能会出现不包含在 [0,N) 内的越界锁定请求,并将争用 spillover mutex。利用线段树的结构自相似性,即小树可以看作是大树的子树,Citron 提供了在运行时扩展锁树的选项。 Citron 使用 MaskedCAS 来决定 lock tree 应该扩展到多大。当比较掩码为零时,MaskedCAS 提供按位或(OR)语义。出于这个原因,Citron 包含一个最大化器:client 可以使用单边 MaskedCAS 将越界锁请求的右边界与最大化器进行或运算,从而能够检测到此类锁请求。最大化器的值最多是实际最大值的 2 倍。 

client 扩大 lock tree 可以通过 RDMA 读取最大化器,并在发现非零值时执行扩大。算法 4 显示了按比例放大的过程。client 首先获取 spillover mutex(第 2 行)以确保锁定安全并防止同时进行扩展尝试。之后 client 向 server 发送一个 RPC 以分配线段树的扩展部分(第 3 行)。旧的 lock tree 并没有移动,它将与新分配的节点组成一棵新的树,如图5。client 然后设置旧树的前 m 层所有节点的 Exp 位以让其他 client 感知放大事件(第 4-5 行)。 之后还需要将这些节点的 DCnt 和 DMax 计数器传播给它们的新祖先(第 7-8 行)。一个被占用的节点占一个额外的 DMax 单位。最后使用更新的配置更新元数据服务,包括 lock tree 的原始部分和扩展部分的地址,清除最大化器,并释放溢出互斥体以完成锁树的扩展(第 9-11 行)。

算法 4
 Figure 5: Demonstration of a lock tree scale-up process.

Handling scale-ups in lock acquisition. 在获取锁时(算法 2),另一个 client A 必须考虑到其他的 client 如 B 可以同时扩展 lock tree。具体来说,在步骤 2(b) 中,A 在读取根节点时检查 Exp 标志(第 15 行),在步骤 2(d) 中,A 需要在通知节点的祖先后读取根节点(第 31 行)。如果节点的需要通知的最高的祖先和根节点的 Exp 标志都已设置(第 33 行),则此时一定在进行树的扩展。A 平凡地处理并发扩展(即 A 中止并重试)。Handling scale-ups in lock release. Lock tree 也可以在 client A 获得锁之后和释放锁之前按比例放大。执行拓展的 client 将代表 A 通知节点的新祖先。因此,A 还应该添加节点新祖先的 DCnt 计数器(算法 3,第 10-12 行)。

Scale down

与 scaling up 不同,scaling down 不能由写入触发,在大多数情况下本质上是一个阻塞操作。例如,在文件系统中,调用 ftruncate 来缩小文件将获取其 inode 互斥锁并阻止所有其他 I/O 尝试。在阻塞缩小操作期间,Citron 可以通过删除除子树之外的所有节点来安全地缩小其锁树。

Handling Client Failures

要启用恢复,所有 client 必须同意一个租约时间 T_lease,并且必须在 T_lease 内释放范围锁。

Detection. Citron 依靠集群管理器 (cluster manager,CM) 来检测 client 故障。CM 通知 lock server 销毁故障 client 连接的 RDMA QP。

Recovery. 如果 client 在锁定获取期间在某个地方等待的时间长于 T_lease,则会检测到失败,包括算法 2 中的第 11、18 和 36 行,在第 22 行失败太多次,也会怀疑失败。(具体看原文)

实验

搞不动了。。直接放几张图吧。

个人看法

这篇文章是第一个使用单边 RDMA 原语针对分布式范围锁管理优化的文章。其中关于锁的部分,不是特别的了解,所以花了不少的时间来看这篇文章。说是 RDMA 的吧,其实感觉也没有那么深,更像是套个 RDMA 的幌子,感觉最近的很多文章主要集中在单边 RDMA 原语(基本只用单边)去适配优化某一应用,或者发掘出一些之前没被注意到的 RDMA 特性,如这篇文章的 masked-CAS 和 masked-FAA,其他文章中提到的 DTC,网卡内存等。

题外话,写这个太花时间了,打算先水一水论文(毕不了业了),之后写不写看情况。

如果问题,欢迎指正~~~~~~~~~~~~~~


FAST23-Citron: Distributed Range Lock Management with One-sided 的评论 (共 条)

分享到微博请遵守国家法律