常常阅读 ljzn(就是在下)的专栏的朋友们可能知道,他平时最爱两样技术:beam虚拟机和 bitcoin网络。究其缘由,多是二者都在追求构建一个永生的网络集群。目标相似,那么实现方法必定会类似,咱们如今就来盘点一些 distributed erlang 和 bitcoin network 究竟有多少相似的地方。node
Bitcoin 的矿工节点之间是高度联通的,这是由比特币的挖矿机制决定的。新的区块头中须要包含以前一个区块头的hash,换句话说,矿工必须时刻观察网络中是否有新的区块出现,而后跟在最新的区块后面进行挖矿。不然,矿工挖到的块极可能变成孤块,得不到任何奖励。每慢一秒钟观察到新的区块,矿工的算力就被浪费了1秒。另外,矿工也会主动把本身挖到的块发送给其它全部矿工,由于每慢一秒发出去,本身的块变成孤块的可能性就增长一分。因此最好的策略是链接到全部其它矿工的节点。shell
分布式 Erlang 的节点之间是全联通的。咱们能够作一个小小的测试:网络
$ iex --sname bob@localhost
$ iex --sname carl@localhost
iex(carl@localhost)2> Node.connect :bob@localhost true iex(carl@localhost)3> Node.list [:bob@localhost]
iex(bob@localhost)4> Node.list [:carl@localhost, :alice@localhost]
zhanglinjie@MacBook-Pro-2 ~ % iex --sname alice@localhost Erlang/OTP 22 [erts-10.7.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] Interactive Elixir (1.10.3) - press Ctrl+C to exit (type h() ENTER for help) iex(alice@localhost)1> Node.connect :bob@localhost true
iex(carl@localhost)4> Node.list [:bob@localhost, :alice@localhost]
Bitcoin 使用区块来更新整个帐本的状态,能够说整个区块链只有一个锁 ——— 区块。例如,目前的区块高度是 60001,那么所有的矿工都在竞争 60002 区块的全部权,而获胜的矿工将可使用 60002 区块来更新帐本状态,以后所有矿工再进入到 60003 区块的竞争中去。dom
当出现竞争状况的时候,Bitcoin采起的是一种 “博弈” 模式,好比矿工 A 和矿工 B 同时爆出了高度 60002 的区块,而且第一时间发布到网络中。矿工 C 首先接受到矿工 A 的区块 a,因而接受了区块 a 并在其基础上开始挖 60003 区块。接着在很短的时间内,矿工 C 又收到了矿工 B 的区块 b,此时矿工 C 不会马上将其丢弃,由于 a 和 b 区块都有可能成为被全网认可的 60002 区块,同时,a 和 b 区块都有可能变成孤块。因此,最好的策略是,将两个区块都保存下来做为候选,同时判断哪一个区块更有可能被全网接受,就在那个区块上接下去挖。async
那么占据大量算力的矿工有可能实现区块垄断吗?(从分布式锁的语境来看,即拒绝交出锁的全部权)。实际上这种行为是不经济的,假设刚才的状况中,时间 T0 爆出区块 a 和 b. 假设在时间 T1 有矿工在区块 b 以后爆出了新块 b' , 这时矿工A想用区块 a 去赢得竞选就很难。由于矿工在区块 a 上挖了9分钟没有出块并不意味着下一分钟内继续在 a 上挖出块的可能性就会增长,道理和扔了9次硬币都是反面,第10次正面的几率不会增长同样。这时矿工 A 若是继续在 a 上挖,它的对手是全网全部的其它矿工,由于它们都已经在 b’ 上开始挖矿。分布式
这时候对于矿工 A 来讲 good ending 是挖到新块 a', 使得 a' 和 b' 继续公平竞争(高度相同); bad ending 是其它矿工 在时间 T2 挖到 b'', 区块a 将落后两个区块, 被其它矿工接受的可能性继续下降. good ending 和 bad ending 发生的几率比等于矿工 A 和网络中其他矿工的算力比. 且 good ending 的收益是 0 (由于还要继续竞争), bad ending 的收益是 -(A 的算力在 T1 到 T2 这段时间出块的可能性)*(b'' 的区块奖励)
. 这样计算出选择 "看到 b' 以后继续在 a 上挖矿" 的收益指望始终是负的.区块链
在分布式 erlang里提供了全局锁的功能.测试
:lock1
, 元组的第二个元素是请求者的 idiex(alice@localhost)12> :global.set_lock {:lock1, self()} true
iex(carl@localhost)6> :global.set_lock {:lock1, self()}
iex(alice@localhost)13> :global.del_lock {:lock1, self()} true
iex(carl@localhost)6> :global.set_lock {:lock1, self()} true
从内部实现来看,分布式erlang的全局锁采起的是一种 “退缩” 策略。核心的代码是这段:优化
check_replies([{_Node, false=Reply} | _T], Id, Replies) -> TrueReplyNodes = [N || {N, true} <- Replies], ?trace({check_replies, {true_reply_nodes, TrueReplyNodes}}), gen_server:multi_call(TrueReplyNodes, global_name_server, {del_lock, Id}), Reply;
这里的逻辑能够这样解释:节点 A 想得到分布式锁 L, 它告诉其它节点,我要占有锁 L。其它节点收到消息后,若是检查一下本身的本地状态,若是发现锁 L 没人占用,就会更新锁状态,而后回复 true,反之回复 false。节点 A 在所有回复都是 true 的状况下才会完成锁的占有,只要有1个节点返回 false,节点 A 就会马上 ”退缩“,而且发消息给全部返回了 true 的节点,让它们赶快把刚才记录删除。code
因此在出现竞争状况的时候,双方均可能会退缩,而且重试,直到一个占有流程中不存在任何冲突时才会成功。
Bitcoin 使用随机数来保证每一个用户拥有惟一的地址,用户使用 256bits 的私钥生成公钥(进而转换成地址),重复的可能性微乎其微。缺点也很明显,地址是人类难以记忆的的长度,须要第三方的注册名管理服务,例如 交易所,钱包 等来帮助管理。
目前 Bitcoin 里还没有有原生的管理 “可读的注册名” -> "地址“ 的映射的手段,但咱们可能经过比特币脚原本实现,这依旧是十分前沿的技术,暂且不表。
在 erlang 里使用自增的计数器来让每一个进程都拥有一个惟一的 pid。对于远程节点的进程,其pid里会加上节点的信息,使得其全局惟一。另外,erlang提供了全局的注册名管理机制,咱们来试验一下:
iex(carl@localhost)10> :global.register_name :god, self() :yes
iex(bob@localhost)17> :global.register_name :god, self() :no
iex(carl@localhost)14> :global.unregister_name :god :ok
iex(bob@localhost)18> :global.register_name :god, self() :yes
内部实现就是用了上面提到的全局锁。在修改注册名信息的时候要先获取到锁。这里作了一些优化,首先尝试在一个节点(The Boss)上获取锁,获取成功后才会尝试在其它所有节点上获取锁。
%% Use the same convention (a boss) as lock_nodes_safely. Optimization. case set_lock_on_nodes(Id, [Boss]) of true -> case lock_on_known_nodes(Id, Known, Nodes) of true -> Nodes; false -> del_lock(Id, [Boss]), random_sleep(Times), set_lock_known(Id, Times+1) end; false -> random_sleep(Times), set_lock_known(Id, Times+1) end.
咱们粗略对比了一下这两样迥然不一样的技术,发现了其中一些有趣的共同点:
若是你对这个话题感兴趣,大力拍打?点赞按钮吧。