常常閱讀 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.
咱們粗略對比了一下這兩樣迥然不一樣的技術,發現了其中一些有趣的共同點:
若是你對這個話題感興趣,大力拍打?點贊按鈕吧。