標籤:
雲風的skynet,定義為一個遊戲伺服器架構,用c + lua基於Actor模型實現。代碼極其精簡,c部分的代碼只有三千行左右。 整個skynet架構要解決的核心問題是:把一個訊息(資料包)從一個服務(Actor)發送給另一個服務(Actor),並接收其返回。也就是在同一進程內(作者也強調並非只限於同一進程,因為可能會有叢集間的通訊)的一個服務通過類似rpc之類的調用同一進程內的另外一個服務,並接收處理結果。而skynet就是處理這些服務間發送資料包的規則和正確性。 skynet的核心層全部是c來實現。 當系統啟動的時候,會得到一個提前分配好的節點id,我們稱之為harbor id,這個id是叢集用的,一個叢集內可以啟動很多個skynet節點,每個節點都會分配到唯一的id。 一個節點(即一個進程)內有很多個服務,服務可以狹義地暫且理解為功能模組。 當初始化一個服務的時候,會產生一個skynet_context來作為服務的執行個體;一個唯一(即使是在叢集裡也是唯一)的服務handle,即服務的唯一id,用來識別服務;一個訊息佇列message_queue;還要向架構註冊一個callback,當服務收到有發送來的訊息時,通過這個方法傳入。 初始化一個服務的代碼如下:
struct skynet_context *skynet_context_new(const char * name, const char *param) { // 裝載模組 struct skynet_module * mod = skynet_module_query(name); if (mod == NULL) return NULL; void *inst = skynet_module_instance_create(mod); if (inst == NULL) return NULL; // 初始化skynet_context執行個體 struct skynet_context * ctx = skynet_malloc(sizeof(*ctx)); CHECKCALLING_INIT(ctx) ctx->mod = mod; ctx->instance = inst; ctx->ref = 2; ctx->cb = NULL; ctx->cb_ud = NULL; ctx->session_id = 0; ctx->logfile = NULL; ctx->init = false; ctx->endless = false; // 初始化服務handle // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle ctx->handle = 0; ctx->handle = skynet_handle_register(ctx); // 初始化訊息佇列 struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle); // init function maybe use ctx->handle, so it must init at last context_inc(); CHECKCALLING_BEGIN(ctx) int r = skynet_module_instance_init(mod, inst, ctx, param); CHECKCALLING_END(ctx) if (r == 0) { struct skynet_context * ret = skynet_context_release(ctx); if (ret) { ctx->init = true; } skynet_globalmq_push(queue); if (ret) { skynet_error(ret, "LAUNCH %s %s", name, param ? param : ""); } return ret; } else { skynet_error(ctx, "FAILED launch %s", name); uint32_t handle = ctx->handle; skynet_context_release(ctx); skynet_handle_retire(handle); struct drop_t d = { handle }; skynet_mq_release(queue, drop_message, &d); return NULL; }}
在skynet_handle_register方法中產生一個服務handle,handle是一個32位的整數,在產生handle的時候,是把該節點的harbor id寫到了handle的高8位裡面,所以一個服務的handle,就可以知道這個服務是哪個節點的。
s->handle_index = handle + 1; rwlock_wunlock(&s->lock); handle |= s->harbor; return handle;
所以說,harbor id最高也只有256個,也就意味著skynet叢集最多隻能有256個節點,而一個節點裡最多也只能有24位個服務,即1.6M個。因為一個handle是32位的整數,高8位用來儲存harbor id,只有低的24位用來分配給本節點的handle。 訊息佇列message_queue是用來儲存發送給該服務的訊息的。所有發送給該服務的訊息,都要先壓到該服務的訊息佇列中。 服務啟動起來了,來看看資料包是如何從一個服務發送給另一個服務的。 來看看 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);
source和destination分別是發送方和接收方的handle。type是發送方和接收方處理資料包的協議session識別本次調用的口令,發送方發送一個訊息後,保留該session,以便收到回應資料包時,能識別出是哪一次調用。msg/sz是資料包的內容和長度,成對使用 skynet 的訊息調度 Skynet 維護了兩級訊息佇列。 每個服務實體有一個私人的訊息佇列,隊列中是一個個發送給它的訊息。訊息由四部分構成: struct skynet_message { uint32_t source; int session; void * data; size_t sz;};向一個服務發送一個訊息,就是把這樣一個訊息體壓入這個服務的私人訊息佇列中。這個結構的值複製進訊息佇列的,但訊息內容本身不做複製。 Skynet 維護了一個全域訊息佇列,裡面放的是諸個不為空白的次級訊息佇列。 在 Skynet 啟動時,建立了若干背景工作執行緒(數量可配置),它們不斷的從主訊息列隊中取出一個次級訊息佇列來,再從次級隊列中取去一條訊息,調用對應的服務的 callback 函數進行出來。為了調用公平,一次僅處理一條訊息,而不是耗淨所有訊息(雖然那樣的局部效率更高,因為減少了查詢服務實體的次數,以及主訊息佇列進出的次數),這樣可以保證沒有服務會被餓死。 這樣,skynet就實現了把一個訊息(資料包)從一個服務發送給另一個服務。
參考:Skynet 設計綜述
風瀟瀟
skynet是什麼