skynet源碼分析:服務


skynet剛開始是單進程多線程的,它是由一個一個的服務組成的。在skynet上做開發,實際上就是在寫服務。服務與服務之間通過消息隊列進行通信。

做為核心功能,Skynet 僅解決一個問題:

把一個符合規范的 C 模塊,從動態庫(so 文件)中啟動起來,綁定一個永不重復(即使模塊退出)的數字 id 做為其 handle 。模塊被稱為服務(Service),服務間可以自由發送消息。每個模塊可以向 Skynet 框架注冊一個 callback 函數,用來接收發給它的消息。每個服務都是被一個個消息包驅動,當沒有包到來的時候,它們就會處於掛起狀態,對 CPU 資源零消耗。如果需要自主邏輯,則可以利用 Skynet 系統提供的 timeout 消息,定期觸發。

一個服務,默認不會執行任何邏輯,需要別人向它發出請求時,才會執行對應的邏輯(定時器也是通過消息隊列,告訴指定服務,要執行定時事件),並在需要時返回結果給請求者。請求者往往也是其他服務。服務間的請求、響應和推送,並不是直接調用對方的api來執行,而是通過一個消息隊列,也就是說,不論是請求、回應還是推送,都需要通過這個消息隊列轉發到另一個服務中。skynet的消息隊列,分為兩級,一個全局消息隊列,他包含一個頭尾指針,分別指向兩個隸屬於指定服務的次級消息隊列。skynet中的每一個服務,都有一個唯一的、專屬的次級消息隊列。

 

skynet服務的本質

每個skynet服務都是一個lua state,也就是一個lua虛擬機實例。而且,每個服務都是隔離的,各自使用自己獨立的內存空間,服務之間通過發消息來完成數據交換。

lua state本身沒有多線程支持的,為了實現cpu的攤分,skynet實現上在一個線程運行多個lua state實例。而同一時間下,調度線程只運行一個服務實例。為了提高系統的並發性,skynet會啟動一定數量的調度線程。同時,為了提高服務的並發性,就利用lua協程並發處理。

所以,skynet的並發性有3點:

1、多個調度線程並發
2、lua協程並發處理
3、服務調度的切換

 

skynet服務的設計基於Actor模型。有兩個特點:

1. 每個Actor依次處理收到的消息
2. 不同的Actor可同時處理各自的消息

實現上,cpu會按照一定規則分攤給每個Actor,每個Actor不會獨占cpu,在處理一定數量消息后主動讓出cpu,給其他進程處理消息。

 

skynet的例子是怎么調用的
服務器: 
simpledb.lua: skynet.register “SIMPLEDB” 向skynet里注冊一個服務 
agent.lua: skynet.call(“SIMPLEDB”, “text”, text) 調用相應的服務 
main.lua: skynet.newservice(“simpledb”) 啟動一個服務
以上函數都在\lualib\skynet.lua 文件內
 

以下是幾個寫服務時經常要用到的函數。

 

newservice(name, ...) 啟動一個名為 name 的新服務。
uniqueservice(name, ...) 啟動一個唯一服務,如果服務該服務已經啟動,則返回已啟動的服務地址。
queryservice(name) 查詢一個由 uniqueservice 啟動的唯一服務的地址,若該服務尚未啟動則等待。
localname(name) 返回同一進程內,用 register 注冊的具名服務的地址。

newservice可以在一個進程里啟動多個服務,這適用於無狀態的服務。
uniqueservice則是類似於設計模式中的單件(singleton),這適用於需要唯一性的服務。舉個例子,比如寫日志,只想寫一份。或者是全局共享的數據。
 
消息機制
SKYNET設計綜述講到模塊被稱為服務。“服務間可以自由發送消息。每個模塊可以向 Skynet 框架注冊一個 callback 函數,用來接收發給它的消息。”還提到“把一個符合規范的 C 模塊,從動態庫(so 文件)中啟動起來,綁定一個永不重復(即使模塊退出)的數字 id 做為其 handle 。 Skynet 提供了名字服務,還可以給特定的服務起一個易讀的名字,而不是用 id 來指代它。id 和運行時態相關,無法保證每次啟動服務,都有一致的 id ,但名字可以。”今天要分析的兩個文件skynet_handle.c和skynet_handle.h就是實現名字服務的。

skynet_handle.c實際上就做了兩個核心的事情,一是給服務分配一個handle,二是把handle和name關聯起來。

把handle和name關聯起來比較容易懂,實際上使用一個數組,關聯的時候使用二分查找到數組里查名字,如果名字不存在,就插入一個元素,然后把名字和handle關聯起來。插入元素的時候,如果數組空間不足了,就擴容為原來的2倍。

而給服務分配handle稍復雜一些,實際上也是使用一個slot數組,數組下標使用的是一個hash,數組元素指向服務的上下文。這個hash的算法是比較簡單粗暴的,就是看從handle_indx開始累計到slot_size,看中間有沒有空閑的下標(也就是下標指向為null的),如果遍歷完了還是沒有,就把slot擴大一倍,還是沒有就再擴大一倍,直到找到空位為止,或者是slot長度超出限制為止。

取到了handle以后呢,還要將harbor id附到handle的高8位。

 

每個服務分三個運行階段:

首先是服務加載階段,當服務的源文件被加載時,就會按 lua 的運行規則被執行到。這個階段不可以調用任何有可能阻塞住該服務的 skynet api 。因為,在這個階段中,和服務配套的 skynet 設置並沒有初始化完畢。

然后是服務初始化階段,由 skynet.start 這個 api 注冊的初始化函數執行。這個初始化函數理論上可以調用任何 skynet api 了,但啟動該服務的 skynet.newservice 這個 api 會一直等待到初始化函數結束才會返回。

最后是服務工作階段,當你在初始化階段注冊了消息處理函數的話,只要有消息輸入,就會觸發注冊的消息處理函數。這些消息都是 skynet 內部消息,外部的網絡數據,定時器也會通過內部消息的形式表達出來。

 

從 skynet 底層框架來看,每個服務就是一個消息處理器。但在應用層看來並非如此。它是利用 lua 的 coroutine 工作的。當你的服務向另一個服務發送一個請求(即一個帶 session 的消息)后,可以認為當前的消息已經處理完畢,服務會被 skynet 掛起。待對應服務收到請求並做出回應(發送一個回應類型的消息)后,服務會找到掛起的 coroutine ,把回應信息傳入,延續之前未完的業務流程。從使用者角度看,更像是一個獨立線程在處理這個業務流程,每個業務流程有自己獨立的上下文,而不像 nodejs 等其它框架中使用的 callback 模式。

 

但框架已經提供了一個叫做 snlua 的用 C 開發的服務模塊,它可以用來解析一段 Lua 腳本來實現業務邏輯。也就是說,你可以在 skynet 啟動任意份 snlua 服務,只是它們承載的 Lua 腳本不同。這樣,我們只使用 Lua 來進行開發就足夠了。

 

ShareData


當你把業務拆分到多個服務中去后,數據如何共享,可能是最易面臨的問題。

最簡單粗暴的方法是通過消息傳遞數據。如果 A 服務需要 B 服務中的數據,可以由 B 服務發送一個消息,將數據打包攜帶過去。如果是一份數據,很多地方都需要獲得它,那么用一個服務裝下這組數據,提供一組查詢接口即可。DataCenter 模塊對此做了簡單的封裝。

datacenter 可用來在整個 skynet 網絡做跨節點的數據共享。

如果你僅僅需要一組只讀的結構信息分享給很多服務(比如一些配置數據),你可以把數據寫到一個 lua 文件中,讓不同的服務加載它。Cluster 的配置文件就是這樣做的。注意:默認 skynet 使用自帶的修改版 lua ,會緩存 lua 源文件。當一個 lua 文件通過 loadfile 加載后,磁盤上的修改不會影響下一次加載。所以你需要直接用 io.open 打開文件,再用 load 加載內存中的 string 。

另一個更好的方法是使用 sharedata 模塊。

當大量的服務可能需要共享一大塊並不太需要更新的結構化數據,每個服務卻只使用其中一小部分。你可以設想成,這些數據在開發時就放在一個數據倉庫中,各個服務按需要檢索出需要的部分。

整個工程需要的數據倉庫可能規模龐大,每個服務卻只需要使用其中一小部分數據,如果每個服務都把所有數據加載進內存,服務數量很多時,就因為重復加載了大量不會觸碰的數據而浪費了大量內存。在開發期,卻很難把數據切分成更小的粒度,因為很難時刻根據需求的變化重新切分。

如果使用 DataCenter 這種中心式管理方案,卻無法避免每次在檢索數據時都要進行一次 RPC 調用,性能或許無法承受。

sharedata 模塊正是為了解決這種需求而設計出來的。sharedata 只支持在同一節點內(同一進程下)共享數據,如果需要跨節點,需要自行同步處理。

 


skynet call的實現--服務與服務的交互

https://blog.csdn.net/zxm342698145/article/details/79786802

如果一個服務生產了大量數據,想傳給您一個服務消費,在同一進程下,是不必經過序列化過程,而只需要通過消息傳遞內存地址指針即可。這個優化存在 O(1) 和 O(n) 的性能差別,不可以無視。

 

架構圖

 

TimeOutCall


 https://github.com/cloudwu/skynet/wiki/TimeOutCall

 

TinyService


https://github.com/cloudwu/skynet/wiki/TinyService

 


參考:

skynet教程(3)--服務的別名

skynet服務的本質與缺陷

GettingStarted


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM