Flash----一種VirtualActor模式的分佈式有狀態系統原型

首先, 這個Flash不是咱們在瀏覽器用的Flash這種技術, 而是:python

動做緩慢, 車速極快------閃電(Flash).c++

 

18年的某一個週末, 即興用Python寫了一個Virtual Actor模式的demo, 起了一個名字叫Flash, 是由於速度快如閃電------作framework快, 經過framework寫邏輯快.git

因此大言不慚, 叫Flash, https://github.com/egmkang/flash. 第一個版本是asyncio寫的, 可是編寫的過程當中發現一旦少寫一點東西(async/await), bug會很難找. 這一點和C#是不太同樣的, 因此第一個版本能夠跑以後, 花了一點時間把asyncio的代碼換成了gevent.github

 

這邊主要來講說當時的想法, 之後將來若是要作相似的東西, 該如何選擇. (README裏面的東西可能和實現沒多少關係......懶, 因此也不打算更改README, 錯了就錯了)golang

 

當時爲了實現一個去中心化, 能夠橫向擴容, 能夠故障遷移有狀態framework. (很顯然我對無狀態的東西一點興趣都沒有:-D)promise

 

全部有幾個關鍵點, 這邊簡單介紹一下(由於代碼不必定能跑起來, 可是思想能夠):瀏覽器

1) RPC緩存

 這邊沒有使用第三方RPC庫, 而是選擇本身實現看了一個. 在Python這種語言裏面, 實現一個RPC仍是比較簡單的, 所須要的例如future/promise, 序列化庫, 協程, 仍是就是Python是動態語言, 因此造一個Proxy對象比較簡單(C#裏面是DispatchProxy).安全

   future/promise選擇了gevent.event.AsyncResult.服務器

   序列化庫選擇了pickle, 序列化這邊作法其實是有一點問題的, 第一個就是pickle效率較低, 數據比較大; 第二個就是RpcRequest/RpcResponse協議的設計不對, 由於Python的args是沒有通過序列化直接塞到RpcRequest裏面的, 因此沒看出來有啥問題, 可是若是是其餘語言這樣就行不通了. 因此比較科學的作法仍是brpc那種, 包分紅三部分: 包頭, meta, data. 其中meta用來形容data數據和請求的元數據, 這樣的話, args數據就不會被encode兩次. python裏面能夠這麼搞不表明其餘語言也能夠這麼搞.

   Proxy對象的話, 是本身造的. 在rpc_proxy.py裏面, 經過重寫__call__元方法, 實現比較複雜的功能. C#的DispatchProxy也能實現這種功能, 並且功能更強大, 類型仍是安全的, Python裏面作不到類型安全. 不支持動態代碼生成的語言作這個都不太好作, 例如golang/c++等.

   哦, 還有就是網絡庫裏面必定要注意sendsendall這兩種東西的區別, 對於用戶來說sendall代碼容易編寫, 可是用send實現就須要注意一下返回值, 不然可能發了一半數據, 而後對面收到的流是斷的.

2) 服務發現

   元數據存在etcd裏面. 

   每一個進程拉起來的時候, 經過uuid生成一個惟一的id, 當作ServerID, 而後組成一個MachineInfo, 而後就開啓了一個update_machine_member_info的死循環, 去etcd裏面不停的去更新本身的信息(有一個5s的CD).

   而後再開啓一個get_members_info死循環去不停的刀etcd裏面pull最新的membership信息, 而後再保存到內存中.

   這樣在MemberShipManager裏面就能夠不停的add_machine, remove_machine.

   這樣作的話, 只須要通過幾個TTL, 集羣的全部節點就能感知到成員的變動; 成員和etcd失聯, 那麼就應該本身退出(Flash裏面沒作).

3) 對象的定位

   上面說了, 集羣內的節點對其餘節點的感知其實是靠定時pull etcd信息來得到的, 那麼新加入的節點, 就不能立馬提供服務, 不然集羣元數據是不一致的. 例如5s間隔去pull, 那麼3個interval以後, 其餘節點大機率是能感知到節點的變動. 因此等一段時間再路由新的請求到新增服務器, 能夠作到更好的一致性.

   而後, MachineInfo內有服務器的負載信息, 那麼:

   0> 先到進程內緩存區尋找對象的位置, 看看最近是否有人請求過, 若是目標服務器健康(保持心跳), 那麼直接返回

   1> 先去到etcd裏面查詢對象的位置是否存在, 若是存在, 而且machine健康, 那麼直接返回(並緩存)

   2> 對對象上分佈式鎖(經過etcd), 而後再作步驟1>, 還未找到對象的位置, 那麼獲取到能夠提供相應服務的machine列表, 經過負載權重, 隨機出來一個新的服務器, 而後保存etcd, 保存進程緩存, 返回

   很明顯, 對象的定位是經過客戶端側+帶權重的Random來作的. 這只是一種選擇, 完成一個功能有不少選擇.

4) 故障遷移

   對象的定位有一個檢測目標服務器是否健康的過程, 實際上就是目標服務器是否最近向etcd更新過本身的心跳, 若是更新過那麼認爲健康, 不然就是不健康.

   那麼, 當目標服務器不健康的時候, 就會觸發對象的再定位, 從而實現故障的遷移.

5) 可重入性

   互聯網的服務不存在這個問題, 是由於互聯網的無狀態服務, 不存在排隊等候處理請求的過程.

   可是在有狀態服務裏面, 每每會對同一個用戶(或者其餘單位)的請求進行排隊. 那麼試想一下, 排隊處理A的請求, A又調用了B, B又調用了A. A的請求沒有返回以前是不能處理其餘的請求的, 因此這時候就死鎖了. 因此有狀態的Actor服務必需要處理這種狀況.

   這時候須要引入一點點代碼, 來看看RpcRequest的數據結構, 裏面嗎包含了一個request_id, 可是在request_id以前有一個host. 實際上就是這倆數據, 決定了rpc請求的可重入性.

class RpcRequest:
    def __init__(self):
        self.clear()

    def clear(self):
        self.host = ""
        if _global_id_generator is not None:
            self.request_id = _global_id_generator.NextId()
        else:
            self.request_id = 0
        self.entity_type = 0
        self.entity_id = 0
        self.method = ""
        self.args = ()
        self.kwargs = dict()

   思考一下, Actor請求的一個請求是誰發出的? 確定是外界系統產生的第一個請求, 那麼這個請求沒有完成以前, 是不能處理其餘請求的. 而中間的請求實際上都不是源頭. 因此咱們只須要在源頭上面標記惟一ID, 中間傳染的路徑上面都用源頭的惟一ID, 因此係統裏面有一個ActorContext的概念, 就是在保存這個信息. Dispatch的過程也就變得比較簡單:

    if entity.context().running is False:
        gevent.spawn(lambda: _dispatch_entity_method_loop(entity))

    if entity.context().host == request.host and entity.context().request_id <= request.request_id:
        gevent.spawn(lambda: _dispatch_entity_method_anyway(entity, conn, request, response, method))
        return
    entity.context().send_message((conn, request, response, method))

   若是對象的loop不在運行就拉起來, 若是如今正在處理的請求和當前須要被Dispatch的請求源自一個請求, 那麼直接開啓一個協程去處理, 不然就塞進MailBox等候處理.

   從而實現了可重入性.

 

   Flash, 麻雀雖小五臟俱全, 實現不是很精良, 可是做爲一個原型, 其目的已經達到. 能夠對其實現進行反思, 組合出來更合理的分佈式有狀態服務系統.

   世人都說Python的性能差, 可是這個原型系統一秒能夠跨進程進行1.5~2.2Wqps, 已經很是優秀了. 有沒有算過本身的系統到底要承載多少請求, Python真的就是系統的瓶頸?

   

參考:

0) Flash (https://github.com/egmkang/flash)

1) Orleans (https://dotnet.github.io/orleans/)

2) gevent (http://www.gevent.org/)

相關文章
相關標籤/搜索