Skynet 設計綜述

  1. 多線程模式,可使得狀態共享、數據交換更加高效。而多線程模型的諸多弊端,好比複雜的線程鎖、線程調度問題等,均可以經過減少底層的規模,精簡設計,最終把危害限制在很小的範圍內。
  2. 作爲核心功能,Skynet 僅解決一個問題:把一個符合規範的 C 模塊,從動態庫(so 文件)中啓動起來,綁定一個永不重複(即便模塊退出)的數字 id 作爲其 handle 。模塊被稱爲服務(Service),服務間能夠自由發送消息。每一個模塊能夠向 Skynet 框架註冊一個 callback 函數,用來接收發給它的消息。每一個服務都是被一個個消息包驅動,當沒有包到來的時候,它們就會處於掛起狀態,對 CPU 資源零消耗。若是須要自主邏輯,則能夠利用 Skynet 系統提供的 timeout 消息,按期觸發。Skynet 提供了名字服務,還能夠給特定的服務起一個易讀的名字,而不是用 id 來指代它。id 和運行時態相關,沒法保證每次啓動服務,都有一致的 id ,但名字能夠。
  3. Skynet 原則上主張全部的服務都在同一個 OS 進程中協做完成。因此在覈心層內,不考慮跨機通信的機制,也不爲單獨一個服務的崩潰,重啓等提供相應的支持。
  4. Skynet 只負責把一個數據包從一個服務內發送出去,讓同一進程內的另外一個服務收到,調用對應的 callback 函數處理。
  5. 數據包一般是在一個服務內打包生成的,Skynet 並不關心數據包是怎樣被打包的,它甚至不要求這個數據包內的數據是連續的(雖然這樣很危險,在後面會談及的跨機通信中會出錯,除非你保證你的數據包絕對不被傳遞出當前所在的進程)。它僅僅是把數據包的指針,以及你聲稱的數據包長度(並不必定是真實長度)傳遞出去。因爲服務都是在同一個進程內,接收方取得這個指針後,就能夠直接處理其引用的數據了。api

    這個機制能夠在必要時,保證絕對的零拷貝,幾乎等價於在同一線程內作一次函數調用的開銷。session

    但,這只是 Skynet 提供的性能上的可能性。它推薦的是一種更可靠,性能略低的方案:它約定,每一個服務發送出去的包都是複製到用 malloc 分配出來的連續內存。接收方在處理完這個數據塊(在處理的 callback 函數調用完畢)後,會默認調用 free 函數釋放掉所佔的內存。即,發送方申請內存,接收方釋放。多線程

  6. 咱們來看看 skynet_send 和 callback 函數的定義:框架

    int skynet_send(
      struct skynet_context * context, 
      uint32_t source, 
      uint32_t destination,
      int type,
      int session,
      void * msg, 
      size_t sz
    );
    
    typedef int (*skynet_cb)(
      struct skynet_context * context,
      void *ud, 
      int type, 
      int session, 
      uint32_t source ,
      const void * msg,
      size_t sz
    );
    
    //發送一個數據包,就是發送 msg/sz 對。
    //咱們能夠在 type 裏打上 dontcopy 的 tag (PTYPE_TAG_DONTCOPY) ,讓框架不要複製 msg/sz 指代的數據包。不然 skynet 會用 malloc 分配一塊內存,把數據複製進去。callback 函數在處理完這塊數據後,會調用 free 釋放內存。你能夠經過讓 callback 返回 1 ,阻止框架釋放內存。這一般和在 send 時標記 dontcopy 標記配對使用。
  7. skynet 核心並不解決進程間通信的問題。數據交換方式並不是相似 TCP 的數據流,因此,沒有必要把服務間的通信形式強行統一爲單個數據塊。最合適作進程內通信的方式就是 C 結構。消息發送方和接收方都處於同一個進程內時,它們必定能夠識別同一個 C 結構映射的內存塊,沒必要考慮內存佈局,字節序等問題。在這個層面上使用這種更高效的數據交換方式,能夠極大的提高性能。tcp

  8. session 是什麼?因爲每一個服務僅有一個 callback 函數,比如在 ip 協議中去掉了端口的設定,全部發送到一個 ip 地址上的 ip 包就沒法被分發到不一樣的進程了。這時,咱們就須要有另外一個東西來區分這個包。這就是 session 的做用。使用 skynet_send 發送一個包的時候,你能夠在 type 裏設上 alloc session 的 tag (PTYPE_TAG_ALLOCSESSION)。send api 就會忽略掉傳入的 session 參數,而會分配出一個當前服務歷來沒有使用過的 session 號,發送出去。同時約定,接收方在處理完這個消息後,把這個 session 原樣發送回來。這樣,編寫服務的人只須要在 callback 函數裏記錄下全部待返回的 session 表,就能夠在收到每一個消息後,正確的調用對應的處理函數。函數

  9. type 的做用?type 表示的是當前消息包的協議組別,而不是傳統意義上的消息類別編號。協議組別類型並不會不少,因此,我限制了 type 的範圍是 0 到 255 ,由一個字節標識。在實現時,我把 type 編碼到了 size 參數的高 8 位。由於單個消息包限制長度在 16 M (24 bit)內,是個合理的限制。這樣,爲每一個消息增長了 type 字段,並無額外增長內存上的開銷。佈局

  10. 爲何整個系統不統一使用一種消息編碼協議?這樣,全部服務間都不會有溝通障礙。這樣,甚至 session 參數也能夠編碼在數據包中。那僅僅是一丁點效率問題。可是,在真正的項目開發中,這其實作起來很難。尤爲在使用第三方庫的時候,你須要作不少低效的封裝工做,才能夠把交互協議都統一塊兒來。並且,這個一致的編碼協議也就成了系統底層約定的一部分,成爲全部開發人員都須要瞭解的知識性能

  11. handle 也就是每一個服務的地址,在接口上看用的是一個 32 位整數。但實際上單個服務中 handle 的最終限制在 24bit 內,也就是 16M 個。高 8 位是保留給集羣間通信用的。咱們最終容許 255 個 skynet 節點部署在不一樣的機器上協做。每一個 skynet 節點有不一樣的 id 。這裏被稱爲 harbor id 。這個是獨立指定,人爲管理分配的(也能夠寫一箇中央服務協調分配)。每一個消息包產生的時候,skynet 框架會把本身的 harbor id 編碼到源地址的高 8 位。這樣,系統內全部的服務模塊(用handle來標識,高8位是 harbor id ,低24爲本節點中的服務標識符),都有不一樣的地址了。從數字地址,能夠輕易識別出,這個消息是遠程消息,仍是本地消息。ui

  12. 集羣間的通信,是由一個獨立的 harbor 服務來完成的。全部的消息包在發送時,skynet 識別出這是一個遠程消息包時,都會把它轉發到 harbor 服務內。harbor 服務會創建 tcp 鏈接到全部它認識的其它 skynet 節點內的 harbor 服務上。編碼

  13. 全局名字服務:

    skynet 目前支持一個全局名字服務,能夠把一個消息包發送到特定名字的服務上。這個服務沒必要存在於當前 skynet 節點中。這樣,咱們就須要一個機構可以同步這些全局名字。爲此,我實現了一個叫作 master 的服務。它的做用就是廣播同步全部的全局名字,以及加入進來的 skynet 節點的地址。本質上,這些地址也是一種名字。一樣能夠用 key-value 的形式儲存。即,每一個 skynet 節點號對應一個字符串的地址。

  14. 組播

相關文章
相關標籤/搜索