分佈式開發雜談

分佈式雜談

分佈式模型的理念自計算機誕生之日就已經出現。然而計算機發展初期,由於設備數量稀少且價格昂貴,再加上摩爾定律的影響,與其實現一套複雜的分佈式架構系統,直接提升單臺機器的硬件性能要更加簡單可行得多。由此導致的現象就是上世紀40到80年代之間,計算機行業一直被大型機所主導。

經過多年的演變,計算機硬件發展遭遇瓶頸,通過分佈式架構來水平提升計算能力成爲主流。今天,藉助已有的開源技術,你可以非常容易搭建起屬於自己的分佈式服務,大大降低了分佈式應用開發的門檻;另一方面,近幾年微服務理念的興起掀起了新一輪的技術熱潮。

根據平臺和技術框架的不同,分佈式開發的具體實現會存在差異,不過各自的架構理念以及技術原理仍是大同小異,連集成度極高的大型主機平臺,也早在1990年就提出了sysplex集羣技術。

以Web系統爲例,一套完整的分佈式系統通常包含以下幾個組件:

  1. 負載均衡服務器
  2. 應用服務器組
  3. 緩存集羣
  4. 消息隊列
  5. 數據庫集羣
  6. 文件服務器集羣
    在這裏插入圖片描述

與單機應用開發相比,分佈式系統的邏輯結構幾乎沒有變化,依然是分應用服務、緩存、數據庫、文件系統等各個模塊,不同的是各個邏輯模塊內部以多節點形式構成,模塊間交互依然認爲彼此是一個完整的個體,遵循高內聚低耦合的理念。
對於分佈式開發人員來說,大部分情況下編寫代碼並不需要關注分佈式架構的具體細節,模塊已經屏蔽了底層細節只保留接口,而接口通常是架構無關的。因此,如果把開發單機應用的邏輯套用在分佈式開發過程中,編譯器並不會報錯,單機調試也大概率無異常,但最後把代碼部署到分佈式環境中執行時,往往會出現意料之外的問題。架構差異帶來的影響,編譯器還不能幫我們發現並排除,更多需要依靠的程序設計規劃來解決。

分佈式開發常見的問題主要有以下幾種:

  1. 鎖問題
    分佈式環境下,除線程同步、進程同步外,還額外增加了服務器同步的考慮。常見的臨界區、信號量等技術手段只能限制本機程序,當程序跑在不同的機器上時,彼此便失去了關聯。
  2. 事務問題
    寫數據庫時,我們知道爲了保證一致性,可以打開一個事務進行提交,確保事務內的修改動作要麼全執行,要麼全回退。但如果A機器的事務執行過程中調用了B機器的服務,在A機器回滾事務時,如何讓B機器也跟着一起撤銷修改。
  3. 全局序列問題
    A機器在12:00:01時刻寫了一條日誌到本地,假定同時刻B機器也寫了一條本地日誌。日誌歸併階段,應該怎麼判斷A的日誌順序在前還是B的日誌?需要有一個全局的序列號發生器確保順序性以及唯一性,類似的還有時鐘同步問題。
  4. session同步問題
    系統將用戶甲的請求分配給了A機器,一番操作後留下了一批session信息。不巧A機器故障了,用戶甲請求改分配到了B機器,B機器怎麼知道用戶剛剛操作了什麼內容?
  5. 負載均衡問題
    通常我們希望用戶甲的請求被一旦分派給了機器A,後續用戶甲的請求都交給機器A來處理。但假定各個用戶請求都成功分派給了已有機器,這時候再在集羣新加入一臺機器,會出現沒有請求進入新節點的情況。
  6. CAP取捨:
    CAP理論說明一致性、可用性、分區容錯性三者不能同時兼備,而C、A、P三者孰優孰劣沒有統一標準,分佈式開發過程必須有所取捨,如何權衡則需要自行考慮。

分佈式鎖的一些思考

對公共資源加鎖會限制系統併發,如同將寬敞的多車道高速路合併成單車道,容易引發擁堵。因此能夠不加鎖解決的的問題,我們都儘量避免加鎖。

以緩存操作爲例,我們通常通過以下方法避免加鎖:

場景一:查詢時緩存命中
緩存命中,直接返回緩存數據
在這裏插入圖片描述
場景二:查詢時緩存擊穿
緩存發生擊穿,此時需要訪問數據庫取得請求結果,寫入緩存後返回
在這裏插入圖片描述
場景三:更新操作
數據發生更新時,先更新數據庫值,其次直接刪除舊緩存,返回操作結果
在這裏插入圖片描述
更新場景下不直接修改緩存,而是將緩存刪除,讓後續查詢請求發生緩存擊穿,並進而更新緩存,這樣做法的好處是保證了緩存內容和數據庫內容的最終一致性。如果將刪除緩存動作調整爲更新緩存,當數據更新和緩存擊穿同時發生時,有可能緩存擊穿查詢到的是修改前的髒數據,而回寫存時緩存擊穿寫入在後,導致最後緩存內容和數據庫值不一致。

對於複雜的業務場景,緩存加鎖不可避免。正如上面提到的,普通的進程同步機制無法控制跨服務器進程,這時需要引入分佈式鎖的概念。分佈式鎖同樣是實現公共資源的互斥訪問,主要的區別是它將控制範圍從單機擴展到了集羣。

以常見的“秒殺”活動爲例,看看分佈式鎖如何發揮作用。
場景四:商品秒殺
在這裏插入圖片描述

  1. 接收商品請求後,首先檢查當前庫存量;
  2. 若庫存量滿足,首先嚐試獲取分佈式鎖,表明要對庫存進行調整;
  3. 鎖定成功,則更新數據庫以及緩存庫存,最後釋放鎖;
  4. 鎖定失敗,則說明秒殺失敗;

加入分佈式鎖後,秒殺活動帶來的流量壓力轉移到了緩存讀取以及分佈式鎖的排隊請求中,避免了海量更新請求直接落到數據庫層。

在目前主流的分佈式框架中並沒有直接提供分佈式鎖的接口,相比之下分佈式鎖更接近一種邏輯概念,由開發人員自行設計實現。常見的分佈式鎖方案基於redis或Zookeeper實現,也有基於數據庫的設計。

結尾

這幾年來微服務的興起和容器技術的發展,讓分佈式理念愈加滲透到各個領域。不過不管技術如何演變,任何時候拋開量級談設計都是不靠譜的行爲,單機設計不一定就比分佈式要差,選擇合適的纔是最優解。

作者:阮偉聰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章