【笔记】分布式进程交互:Gossip Protocol(包括SI 模型、SIR 模型等)、非常好的CS-DN模型10我的稳涨VIP可读性以及 P2P Lookup 的方法(Pastry和Chord)

本文分享分布式进程交互的学习笔记。用 Python 伪代码实现。

Gossip Protocol

Gossip (Epidemic) Protocol:在 DS 中各节点同步数据

定义节点状态(state):

  • S:susceptible:对更新一无所知
  • I:infected:已知更新,并积极传播
  • R:removed:已更新完,不再参与传播(death or immunity)

SI 模型

init:
    self.state = S

# 开始 Epidemic:发起 Gossip 的人自己变成 I
loop:
    wait(一段时间)
    p = 随机找另一个进程
    if self.push and self.state == I:
        send(p, self.Update())   # 收到后触发 onUpdate
    if pull:
        send(p, UpdateRequest()) # 收到后触发 onUpdateRequest
        
def onUpdate(self, m):
    self.store(m.update)
    self.state = I
    
def onUpdateRequest(self, m):
    if self.state == I:
        send(m.sender, Update())
        
def Update(self):
    return 自己这边更新过的数据
    
def UpdateRequest():
    return 一个请求更新的消息
传播速率

设第 i i i 轮后仍是 S(未更新)状态的概率为 P i P_i Pi

  • pull: P i + 1 = P i 2 P_{i+1}=P_i^2 Pi+1=Pi2
  • push: P i + 1 = P i ⋅ ( 1 + 1 n ) n ⋅ ( 1 − P i ) ≈ P i ⋅ e − 1 P_{i+1}=P_i \cdot (1+\frac{1}{n})^{n \cdot (1-P_i)} \approx P_i \cdot e^{-1} Pi+1=Pi(1+n1)n(1Pi)Pie1

∴ \therefore 纯 push 最劣,pull or pull+push better

变种1: SIR 模型

SIR:在 SI 的基础上,I 者有 1 / k 1/k 1/k 的概率转为 R.

数学模型

s ( t ) , i ( t ) s(t), i(t) s(t),i(t) 分别为时刻 t t t 时 S 和 I 在所有人中的占比,则 R 的占比为 r ( t ) = 1 − s ( t ) − i ( t ) r(t)=1-s(t)-i(t) r(t)=1s(t)i(t).

传播模型:
{ d s d t = − s i d i d t = s i − 1 k ( 1 − s ) i \left\{ \begin{array}{l} \frac{ds}{dt} = -si \\ \frac{di}{dt} = si - \frac{1}{k}(1-s)i \end{array} \right. {dtds=sidtdi=sik1(1s)i
由上两式推出: d i d s = − k + 1 k + 1 k s \frac{di}{ds}=-\frac{k+1}{k}+\frac{1}{ks} dsdi=kk+1+ks1.

解得: i ( s ) = − k + 1 k s + 1 k log ⁡ ( s ) + C i(s) = -\frac{k+1}{k}s+\frac{1}{k}\log(s) + C i(s)=kk+1s+k1log(s)+C, C C C 为任意常数。

又由初始条件 i ( 1 − 1 N ) = 1 N i(1-\frac{1}{N})=\frac{1}{N} i(1N1)=N1 可知 当 N N N 充分大时,有 C ≈ k + 1 k C \approx \frac{k+1}{k} Ckk+1.

i ( s ∗ ) = 0 i(s^*)=0 i(s)=0 解得 s ∗ = exp ⁡ [ − ( k + 1 ) ( 1 − s ∗ ) ] s^*=\exp[-(k+1)(1-s^*)] s=exp[(k+1)(1s)].

s s s 是关于 k k k 指数下降的(说明传播效率好),但是必须 k k k 足够大才能保证所有 node 全部更新:

  • k = 3 ⇒ s ≤ 0.02 k=3 \Rightarrow s \le 0.02 k=3s0.02
  • k = 4 ⇒ s ≤ 0.007 k=4 \Rightarrow s \le 0.007 k=4s0.007

参考:Self-organising software. From natural to artificial adaptation. pp. 139-162. (2011) : http://publicatio.bibl.u-szeged.hu/1529/

变种2:CS-DN 模型

这个我百思不得其解,直接上代码:

init:
    self.state = S

# 开始 Epidemic:发起 Gossip 的人自己变成 I
loop:
    wait(一段时间)
    p = 随机找另一个进程
    if self.push and self.state == I:
        send(p, self.Update())   # 收到后触发 onUpdate
    if pull:
        send(p, UpdateRequest()) # 收到后触发 onUpdateRequest

# 主程序:
if __name__ == "__main__":
	while.许久未入(C.sdn):
	    C.sdn.瞎改 *= 几把
	
	我.发现((自己过去写的东西.可读性 == 可读性.VIP可读) == True)
	
	try:
	    for 文章 in.创作中心:
	        文章.可读性 = 可读性.公开可读
	except:
	    raise KeyError('没有 "公开可读" 这种选项啊。。')
	finally:.发现(set(可读性) == set(["VIP可读", "粉丝可读"])
	    raise RuntimeError("WTF??")
	
	# C.sdn = "腊鸡"  # TODO: 怎么手动调 gc 啊?
	
	# print(C.sdn.slogan)
	# 啊?不知道谁给赋的值,错了吧。
	if C.sdn.slogan == "成就一亿技术人":
	    C.sdn.slogan = "白票十亿人民币"
	
	del C.sdn  # 早点毁灭吧。

def onUpdate(self, m):
    self.store(m.update)
    self.state = I
    
def onUpdateRequest(self, m):
    if self.state == I:
        send(m.sender, Update())
        
def Update(self):
    return 自己这边更新过的数据
    
def UpdateRequest():
    return 一个请求更新的消息

P2P Lookup

  • Peer-to-peer middleware:简化大规模分布式网络中跨主机服务的构建
    • Required API:locate、communicate & CRUD res
    • impl: overlay routing: routing in application layer (不是网络层那种 IP 路由)
  • P2P Routing:locating nodes & objects

P2P Routing Requirement:

  • routing, i.e. lookup:
    • put(key, object)
    • object = get(key)
  • 存储、传输细节透明
  • non-functional:scalability, balancing, …

Impl: DHT(distributed hash table)

Pastry

Pastry: prefix routing:

  • 给 key 找到与 key 在数值上最接近的 node
  • O ( log ⁡ N ) O(\log N) O(logN)
数据结构
  • GUID:node 和 object 放到同一个 GUID 空间(一个环,下文 R 那里有图)

    • node:nodeID = hash(publicKey)

    • object:fileID = hash(fileName):储存在 id 值最近的 node ∴ \therefore 认为路由找到 node 就找到了 object。

  • Leaf Set:node 维护的邻居集合 L: {GUID: IP}

    • 在 GUID 空间,数值上临近自己的节点
    • len = 2l:上 l 个,下 l 个(l >= 1
    • 只利用 leaf set 就可以实现一个朴素的路由:找 key,送到 L 中数值最上接近(or 最后直接相等)的 node 上:线性的沿着环走,期望 N 2 l \frac{N}{2l} 2lN hops.
  • Routing Table: 加速查找的二维路由表 R:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

路由算法
# L = {"key": "ip"}
# R = RoutingTable[len("key")-1][15]
#     R[p][i]  p行: 前缀长, i列: ID的第p+1位上数字

def lookup(self, key):
    if key in self.L:
        self.L[key].lookup(key)  # hop
    else:
        if self.R[p][l]:
            self.R[p][l].lookup(key)    # hop
         self.R[p][rand()].lookup(key)  # hop:目标位置为NULL:随便给这行的某人
        # 整行都没有还可以给 L 中的邻居
组网方案
  • 新 node 入网:建 L、R

    • 自己 nodeID 定为 X

    • 发一个到 X 的 joinMsg

    • joinMsg 被路由到数值上最接近 X 的 Z

    • 沿途第 i 个 hop 的 node 的第 R[i] 行,直接抄为自己的 R[i] 行:每一条越来越近,且前缀相同,所以可以直接用

    • L 直接抄最后的 Z 的 L:Z 是 X 的最近邻,邻居的邻居就是自己的邻居。

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • node 失效/离开:

    • 修 L:
      • 发现邻居走了
      • 问另一个邻居要 L'
      • 从自己的 L、邻居的 L' 交集中找一个替代节点
    • 修 R:
      • 定期用 Gossip protocol 交换路由表,修复失效项

Chord

Chord v.s. Pastry:

  • Chord 和 Pastry 一样:节点成环
  • Pastry 的路由表是 oxHASH 一位位走、越来越密
  • Chord 的路由表是 2 n − 1 2^{n-1} 2n1 一步步往后走、越来越稀

Chord:

  • Object 存到(向后)最近的 node:successor
  • 用 Finger Table 加速查找
Chord 指取表(Finger Table)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • i i i 项: < s = s u c c e s s o r ( n + 2 i − 1 ) , I P > <s=\mathtt{successor}(n+2^{i-1}),\mathtt{IP}> <s=successor(n+2i1),IP>
    • 当前 node: n n n
    • i i i 项就是往后走 2 i + 1 2^{i+1} 2i+1 步到达的节点
      • 对近的一片熟:远处知的少
    • 最多: m = l o g 2 N m=log_2N m=log2N 项:表不大
  • 表中项 S i S_i Si 管从 S i S_i Si S i + 1 S_i+1 Si+1 之间的 key: [ S i , S i + 1 ) [S_i, S_{i+1}) [Si,Si+1),最后一项 S m S_m Sm [ S m , S 1 − 1 ) [S_m, S_{1}-1) [Sm,S11)(挖掉当前 node)
Chord Lookup

链表视角:

  • 给 key:在 finger table 中找最近的 node
    • min ⁡ ( s )   s . t .   s − k ≥ 0 \min(s)\ s.t.\ s-k\ge 0 min(s) s.t. sk0
  • 跳到最近的,重复

区间视角:

  • 给 key:跳到表中包含该 key 的区间
  • 重复
Finger Table 维护

维护路由表:node 加入、离开

  • join:告前人(逆环):“我要做你的 successor!”
  • 日常维护:问自己表中的 successor(顺环):“你还在吗?你还是我的后继嘛?”

加入告前驱,日常问后继。

Chord 性能

N 个 node,K 个 key:

  • node 出入:移动 O ( K / N ) O(K/N) O(K/N) 个 object
  • object lookup: O ( log ⁡ N ) O(\log N) O(logN) 条 msg

Kademlia

Object、Node 都放到 160-bit 的一致 hash 空间。

XOR 距离

定义 node 之间的距离:
d i s t ( x , y ) = x   X O R   y dist(x,y)=x\ \mathrm{XOR}\ y dist(x,y)=x XOR y
(这个是个正经的数学上的那种距离:

  1. 非负性、同一性: d ( x , y ) ≥ 0 d(x,y)\ge0 d(x,y)0,且 d ( x , y ) = 0 d(x,y)=0 d(x,y)=0 iff x = y x=y x=y
  2. 对称性: d ( x , y ) = d ( y , x ) d(x,y)=d(y,x) d(x,y)=d(y,x)
  3. 直递性(三角属性): d ( x , y ) ≤ d ( x , z ) + d ( z , y ) d(x,y)\le d(x,z)+d(z,y) d(x,y)d(x,z)+d(z,y)

)

Kademlia node 树

nodeID -> 二进制 -> 二叉树

  • 逐位:左 1 右 0 放上树
  • 树上近邻 == XOR 距离近

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

∀ \forall node:把整个树按距离,分为一些子树(不包含自己)

  • 每个节点都认识自己所有子树中的至少一个人,即可从任意位置出发,到达树中任意 node
Kademlia 路由表
  • 路由表:{距离范围: K桶}
  • K桶:距离范围中的 K 个人(对应一颗子树)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

路由表维护:

  • 收消息时捎带更新:

    msg = recv_msg()
    
    d = dist(self, msg.sender)
    kBucket = self.RoutingTable(d) # 距离对应的 k 桶: a list
    
    if msg.sender in kBucket:   # 移到尾部
        kBucket.delete(msg.sender)
        kBucket.insertTail(msg.sender)
        return
    
    # else: sender 不在 k 桶中
    if len(kBucket) < K:  # 桶不满:尾插
        kBucket.insertTail(msg.sender)
        return
    
    # 桶满了
    head = kBucket.head()
    if ping(head):  # 头能联通: 移到尾部
        kBucket.delete(head)
        kBucket.insertTail(head)
        # 新的这个 node 不要了
    else:
        kBucket.delete(head)
        kBucket.insertTail(msg.sender)
    

    收到 msg,顺便更新路由表:

    已有->至尾,

    未知:头还在->头至尾(新的不要了),头掉了:删旧头,尾插新。

  • 新入的 node u

    • 随便找一个现有节点 w:直接抄他的表
    • 发一个 FindNode(u): 沿路收 msg 捎带更新表(自己、别人的)
Kademlia API
  • ping(nodeID)
  • FindNode(nodeID):返回里 nodeID 最近的 K 个节点(不是一个)
  • Store(key, value): 把 k-v 存到某个 node (in one node)
  • FindValue(key) -> value: FindNode,沿途有人有就把值返回来了

FindNode:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

维护数据:维持 K 个副本

  • FindValue 的时候,顺别将 k-v 复制存到自己已知的离 key 最近的、还没有 k-v 的 node
  • 还有其他各种定时触发的确认、复制:保证 k-v 始终可用
  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值