chord: A Scalable Peer-to-peer Lookup Service for Internet Application
在 P2P 系统中,有效地定位分布在网络中不同节点的数据资源一直是研究的重点。 Chord 是麻省理工学院 (MIT) 提出的一种基于 DHT (分布式哈希方法)技术的结构化 P2P 路由协议,具有完全分布式、负载均衡、可用性及可扩展性好、命名方式灵活等特点。本文在研究 Chord 的结构、查找算法、节点的插入退出算法的基础上进一步分析了此算法的效率。
1. 引言
采用 P2P 技术的网络应用和服务已经成为互联网的重要组成部分。P2P (Peer-to-Peer) 网络是建立在 Internet 上的覆盖 (overlay) 网。P2P 网络改变了传统客户机/服务器模式集中存储和处理资源的方法,将网络上的资源有效地组织起来。P2P 使得资源提供者和资源接收者之间能够直接相互交换信息。
P2P 网络的可用性依赖于对数据的高效查找和提取方法。如何高效快速地定位 P2P 网络上的资源是 P2P 网络实现最关键的问题。按照资源组织与定位方法, P2P 网络可分为无结构 P2P 网络和有结构 P2P 网络。无结构 P2P 网络是基于泛洪搜索,搜索较慢而且消耗大量的资源,扩展性较差。基于分布式哈希表 (Distributed Hash Table) 的结构化 P2P 网络搜索速度快,产生的查询消息少,资源消耗少,因此对它的研究和应用得到了较快的发展。其中 Chord 算法以其路由表具有良好的结构性、查找具有确定性、有较好的可扩展性等优点得到了广泛研究 [1] 。本文介绍了结构化 P2P 覆盖网络模型中具有代表性的 Chord 算法,并对此算法进行分析。
2. Chord 概述
2.1 DHT 介绍
DHT 的主要思想是:首先,每条文件索引被表示成一个 (K, V) 对,K 称为关键字,可以是文件名(或文件的其他描述信息)的哈希值,V 是实际存储文件的节点的 IP 地址(或节点的其他描述信息)。所有的文件索引条目(即所有的 (K, V) 对)组成一张大的文件索引哈希表,只要输入目标文件的 K 值,就可以从这张表中查出所有存储该文件的节点地址。然后,再将上面的大文件哈希表分割成很多局部小块,按照特定的规则把这些小块的局部哈希表分布到系统中的所有参与节点上,使得每个节点负责维护其中的一块。这样,节点查询文件时, 只要把查询报文路由到相应的节点即可(该节点维护的哈希表分块中含有要查找的 (K,V)对)。这里面有个很重要的问题,就是节点要按照一定的规则来分割整体的哈希表,进而也就决定了节点要维护特定的邻居节点,以便路由能顺利进行 [2] 。
2.2 Chord 概述
Chord 的实现方式如下:给定一个关键字 Key,将其映射到某个节点。为此,采用相容哈希为每个节点和关键字产生一个 m 位的 ID,并按照 ID 大小构成环形拓扑。图 1 表示了 m = 6 的一个 Chord 环,它可以容纳 26 = 64 个节点。运行 Chord 的主机相互连接构成 Chord 网络,这是一个建立在 IP 网络之上的覆盖 (overlay) 网络。每个节点 n 有 2 个邻居:以顺时针为正方向排列在 n 之前的第1个节点称为 n 的前继 (predecessor),在 n 之后的第1个节点称为 n 的后继 (successor),资源存放于其关键字 ID 的后继。为了路由的需要,节点保存前继和后继信息,并维护一张最多 m 项的路由表,称之为 Finger 表,其中,第 k 项保存 ID 为 (n.id + 2k−1) mod 2m 的后继。图 1 显示了节点 N8 的 Finger 表及其生成方法 [3] 。
2.3 Chord 查找
Chord 采取幂次逼近查询法 [4] 。任何一个节点收到查询关键字 K 的请求时,首先检查 K 是否落在该节点标识和它的后继节点标识之间,如果是的话,这个后继节点就是存储目标 (K, V) 对的节点。否则,节点将查找它的指针表,找到表中节点标识符最大但不超过 K 的第一个节点,并将这个查询请求转发给该节点。通过重复这个过程,最终可以定位到 K 的后继节点,即存储有目标 (K, V) 对的节点。图 1 中带有标号的点划线表示了在节点 N8 上使用幂次逼近算法查询资源 ID54 的过程,其中,N 是网络中已存在节点总数。他的过程为:1. N8 先查询自己的 finger 表。但 N8 最远只记录到 ID42 的后继 N42,因此,它把请求转发到 N42;2. N42 查询自身的 finger 表,发现顺时针方向距离 ID54 的最近节点为 N51,请求被转发到 N51;3. N51 经过计算得知 ID54 应该保存在其后继 N56 上,故返回结果 N56,即找到目标接点。
2.4 节点的加入
新节点的加入需要一个称为向导的已知节点协助,任何一个运行在 Chord 网络中的节点都可以充当这个角色。加入过程包括新节点本身的 Join 操作和被其他节点发现2个阶段 [5] ,如图 2 所示。假设 np 和 ns 是 Chord 网络中相邻两节点,n 为新节点,它加入网络后应该位于 np 和 ns 节点之间。在第1阶段中,n 请求向导为它查找后继 (即 ns),并初始化自身 Finger 表和后继表。按照 Finger 表定义,第1个需要查找后继的表项位置 join 操作完成后节点的情形如图 2(a) 所示。此时只有 n 对自身属性进行了设置,其他节点并不知道新节点的加入,因此,第2阶段引入了 stabilize 操作,所有节点定期检查其后继的前继,并向自己的直接后继发送 Notify 消息。在图2的例子中:(1) 若 np 比 n 先向 ns 发送 Notify,此次 stabilize 并不改变网络状态;(2) 若 n 先向 ns 发送 Notify,ns 计算 n 的 ID 得知 n 比 np 更接近自己而认为有一个新节点加入, ns 由此把前继修改成 n,然后,np 在查看其后继(此时还是 ns)的前继(此时已改为 n)也发现了 n 的加入,np 把其后继修改成 n 并向 n 发送 Notify,把 n 的前继修改成 np,这样,np、n、ns 构成了完整的弦环,如图 4(b) 所示。当只有一个节点时,Chord 约定它的前继、后继都指向自身。
2.4 节点的失效
节点的失效是节点没有通知其他节点而突然离开网络,这通常由主机崩溃或 IP 网络断开等意外原因造成,此时失效节点的前继保存的后继信息变得不可用,从而造成 Chord 环的断裂。为了处理这个问题,需要周期性对节点的前继和后继进行探测。如果节点 n 发现其后继已经失效,则从后继表中顺序查找第1个可用节点替换,并按照节点加入时的算法重建 Finger 表,然后通知前继。前继拷贝 n 的后继表,并添加 n 作为直接后继,如果有必要,去掉最后一个表项,以避免后继表的过度膨胀。对前继节点失效的处理需要借助于 Notify 消息。考虑图 2(b) 的例子,ns 虽然能够感知 n 的失效却无法进行修复。由于上述对后继失效的处理过程能够保证 Chord 环后继链的正确性,因此 np 通过在 stabilize 中向新后继 ns 发送 Notify,把 ns 的前继改成 np。值得注意的是,其他节点也可能在 Finger 表项中保存有失效节点的记录,因此需要多次 stabilize,把失效信息扩散到 Chord 网络中。虽然这种方法最终能够保证 Chord 网络的完整性,但在节点频繁进出的情况下,其效率仍须更深入地研究。
2.5 节点的退出
由于节点失效处理方法是稳定的,因此节点的退出可看作为失效而不采取其他附加措施。但基于效率的考虑,节点 n 退出时进行如下操作:(1) 把 n 后继节点的前继改成 n 的前继;(2) 把 n 前继节点的后继改成 n 的后继;(3) 从 n 前继的后继表中删除 n。
3. Chord 算法分析
假设 Chord 环的大小为 2m ,网络节点数为 N = 2n (m >> n),且所有节点均匀分布在 Chord 环中,则任意2个相邻节点之间的间隔 Δ = 2m/2n ,而路由表的大小为 m。如果第1个节点是 N0 ,则第2个节点,第3个节点,…,第 2n 个节点分别是 N0+Δ ,N1+Δ ,…, [Math Processing Error] ,其中,节点 Id 的路由表可表示为 (Id + 2i-1) mod 2m ,(1 ≤ i ≤ m)。经研究发现,只有当 2i-1 ≤ Δ (i ≤ m-n+1) 时,在路由表中才有冗余,当 i > m-n+1 时,路由表中存在冗余的概率小到可以忽略不计。因此,有 successor((Id + 21-1) mod 2m) = successor((Id + 22-1) mod 2m) = … = successor((Id + 2(m-n+1) - 1) mod 2m) = Id + Δ,很显然,这些都属于节点 Id 的路由表上的冗余信息, 由此可得冗余度 red = (m – [n]) / m × 100%,相应的冗余量 R(N) = m × red。如在大小为 2m (m = 20) 的 Chord 环中,共有 10 000 个网络节点,约 213.29,即 n = 14,则 red = (20 – 14) / 20 × 100% = 30%。在图 1 中,m = 6,节点数为10,约 23.32,即 n = 4,则 red = (6 – 4) / 6×100% = 33.3%,其冗余量 R(N) = 2。冗余度 red 与 m 和 n 的关系如图 3 所示。
4. 结论
由此可见,以 2i 为跨度来寻找路由表中的后继节点,这会在 finger 中产生一定的冗余量,导致 Chord 的查询效率不高。根据 finger 表的构造思想,产生不同程度的冗余信息是必然的,如果能去掉这些冗余,添加相应数量的其他有效信息,会大大提高 Chord 的查询效率。