skynet服务的本质与缺陷

来源:互联网 发布:淘宝网客户申诉案例 编辑:程序博客网 时间:2024/06/15 15:14


 4917人阅读 评论(5) 收藏 举报
 分类:

目录(?)[+]

skynet是为多人在线游戏打造的轻量级服务端框架,使用c+lua实现。使用这套框架的一个好处就是,基本只需要lua,很少用到c做开发,一定程度上提高了开发效率。但skynet文档也相对较少,所以这里利用一点时间学习和总结skynet相关内容,文章这里就讲解下skynet服务的本质与缺陷,希望能有所帮助。

skynet服务的本质

或许我们对skynet服务有着太多的疑问:

skynet服务究竟是什么,为什么有人说服务是一个lua虚拟机,服务与服务之间的通讯是怎样的,为什么服务的内存高居不下, 为什么拿skynet服务和erlang进程做比较?等等。。。而这一切的答案都在代码里面,让我们一步一步解开她的面纱。

服务创建API

先从skynet服务创建的接口说起,方式如下:

[plain] view plain copy
  1. skynet.newservice(name, ...)  

看下这个函数的实现:

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.newservice(name, ...)  
  4.     return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)  
  5. end  

实际上是调用另外一个服务(.launcher)完成skynet服务的创建。看下launcher服务的处理:

[plain] view plain copy
  1. -- launcher.lua  
  2.   
  3. -- 处理服务的创建  
  4. local function launch_service(service, ...)  
  5.     local param = table.concat({...}, " ")  
  6.     local inst = skynet.launch(service, param)  
  7.     local response = skynet.response()  
  8.     if inst then  
  9.         services[inst] = service .. " " .. param  
  10.         instance[inst] = response  
  11.     else  
  12.         response(false)  
  13.         return  
  14.     end  
  15.     return inst  
  16. end  
  17.   
  18. -- 处理 LAUNCH 类消息  
  19. function command.LAUNCH(_, service, ...)  
  20.     launch_service(service, ...)  
  21.     return NORET  
  22. end  
  23.   
  24.   
  25. -- 处理launcher服务接收到的消息  
  26. skynet.dispatch("lua", function(session, address, cmd , ...)  
  27.     cmd = string.upper(cmd)  
  28.     local f = command[cmd]  
  29.     if f then  
  30.         local ret = f(address, ...)  
  31.         if ret ~= NORET then  
  32.             skynet.ret(skynet.pack(ret))  
  33.         end  
  34.     else  
  35.         skynet.ret(skynet.pack {"Unknown command"} )  
  36.     end  
  37. end)  

也就是调用 skynet.launch(service, param),实际上 .launcher 服务也是通过这函数实现的。

[plain] view plain copy
  1. -- bootstrap.lua  
  2.   
  3. local launcher = assert(skynet.launch("snlua","launcher"))  
  4. skynet.name(".launcher", launcher)  

为什么要通过另外一个服务创建新的服务?主要目的是为了方便管理所有服务,比如统计,gc,杀掉服务等。


服务创建的实现

再来看下skynet.launch(service, param),服务创建的关键api:

[plain] view plain copy
  1. -- manager.lua  
  2.   
  3. local skynet = require "skynet"  
  4. local c = require "skynet.core"  
  5.   
  6. function skynet.launch(...)  
  7.     local addr = c.command("LAUNCH", table.concat({...}," "))  
  8.     if addr then  
  9.         return tonumber("0x" .. string.sub(addr , 2))  
  10.     end  
  11. end  
skynet.core这个是c实现的,编译成动态库给lua使用,可以在loadfunc时利用luaopen_* 找到这个c函数。实际接口函数如下:
[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. int  
  4. luaopen_skynet_core(lua_State *L) {  
  5.     luaL_checkversion(L);  
  6.   
  7.     luaL_Reg l[] = {  
  8.         { "send" , _send },  
  9.         { "genid", _genid },  
  10.         { "redirect", _redirect },  
  11.         { "command" , _command },  
  12.         { "error", _error },  
  13.         { "tostring", _tostring },  
  14.         { "harbor", _harbor },  
  15.         { "pack", _luaseri_pack },  
  16.         { "unpack", _luaseri_unpack },  
  17.         { "packstring", lpackstring },  
  18.         { "trash" , ltrash },  
  19.         { "callback", _callback },  
  20.         { NULL, NULL },  
  21.     };  
  22.   
  23.     luaL_newlibtable(L, l);  
  24.   
  25.     lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");  
  26.     struct skynet_context *ctx = lua_touserdata(L,-1);  
  27.     if (ctx == NULL) {  
  28.         return luaL_error(L, "Init skynet context first");  
  29.     }  
  30.   
  31.     luaL_setfuncs(L,l,1);  
  32.   
  33.     return 1;  
  34. }  

c.command对应的处理如下:

[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. static int _command(lua_State *L) {  
  4.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  5.     const char * cmd = luaL_checkstring(L,1);  
  6.     const char * result;  
  7.     const char * parm = NULL;  
  8.     if (lua_gettop(L) == 2) {  
  9.         parm = luaL_checkstring(L,2);  
  10.     }  
  11.   
  12.     result = skynet_command(context, cmd, parm);  
  13.     if (result) {  
  14.         lua_pushstring(L, result);  
  15.         return 1;  
  16.     }  
  17.     return 0;  
  18. }  

到了skynet_command的处理:

[cpp] view plain copy
  1. // lua_server.c  
  2.   
  3. static struct command_func cmd_funcs[] = {  
  4.     { "TIMEOUT", cmd_timeout },  
  5.     { "REG", cmd_reg },  
  6.     { "QUERY", cmd_query },  
  7.     { "NAME", cmd_name },  
  8.     { "NOW", cmd_now },  
  9.     { "EXIT", cmd_exit },  
  10.     { "KILL", cmd_kill },  
  11.     { "LAUNCH", cmd_launch },  
  12.     { "GETENV", cmd_getenv },  
  13.     { "SETENV", cmd_setenv },  
  14.     { "STARTTIME", cmd_starttime },  
  15.     { "ENDLESS", cmd_endless },  
  16.     { "ABORT", cmd_abort },  
  17.     { "MONITOR", cmd_monitor },  
  18.     { "MQLEN", cmd_mqlen },  
  19.     { "LOGON", cmd_logon },  
  20.     { "LOGOFF", cmd_logoff },  
  21.     { "SIGNAL", cmd_signal },  
  22.     { NULL, NULL },  
  23. };  
  24.   
  25. const char *   
  26. skynet_command(struct skynet_context * context, const char * cmd , const char * param) {  
  27.     struct command_func * method = &cmd_funcs[0];  
  28.     while(method->name) {  
  29.         if (strcmp(cmd, method->name) == 0) {  
  30.             return method->func(context, param);  
  31.         }  
  32.         ++method;  
  33.     }  
  34.   
  35.     return NULL;  
  36. }  
  37.   
  38. static const char *  
  39. cmd_launch(struct skynet_context * context, const char * param) {  
  40.     size_t sz = strlen(param);  
  41.     char tmp[sz+1];  
  42.     strcpy(tmp,param);  
  43.     char * args = tmp;  
  44.     char * mod = strsep(&args, " \t\r\n");  
  45.     args = strsep(&args, "\r\n");  
  46.     struct skynet_context * inst = skynet_context_new(mod,args);// 实例化上下文  
  47.     if (inst == NULL) {  
  48.         return NULL;  
  49.     } else {  
  50.         id_to_hex(context->result, inst->handle);  
  51.         return context->result;  
  52.     }  
  53. }  
再套上最前面的参数,也就是调用
[cpp] view plain copy
  1. skynet_context_new("snlua", name)  
再看下这个函数的实现。
[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. struct skynet_context *   
  4. skynet_context_new(const char * name, const char *param) {  
  5.     /* 这一步加载name的动态库,这里是snlua.so 
  6.      * snlua模块是 service_snlua.c 然后通过以下接口调用代码 
  7.      * skynet_module_instance_create()  -->  snlua_create() 
  8.      * skynet_module_instance_init()  -->  snlua_init() 
  9.      * skynet_module_instance_release()  -->  snlua_release() 
  10.      * skynet_module_instance_signal()  -->  snlua_signal() 
  11.      */  
  12.     struct skynet_module * mod = skynet_module_query(name);  
  13.   
  14.     if (mod == NULL)  
  15.         return NULL;  
  16.   
  17.     void *inst = skynet_module_instance_create(mod); // 执行snlua_create() 完成服务初始化  
  18.     if (inst == NULL)  
  19.         return NULL;  
  20.     struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));  
  21.     CHECKCALLING_INIT(ctx)  
  22.   
  23.     ctx->mod = mod;  
  24.     ctx->instance = inst;  
  25.     ctx->ref = 2;  
  26.     ctx->cb = NULL;  
  27.     ctx->cb_ud = NULL;  
  28.     ctx->session_id = 0;  
  29.     ctx->logfile = NULL;  
  30.   
  31.     ctx->init = false;  
  32.     ctx->endless = false;  
  33.     // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle  
  34.     ctx->handle = 0;   
  35.     ctx->handle = skynet_handle_register(ctx);  
  36.     struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);  
  37.     // init function maybe use ctx->handle, so it must init at last  
  38.     context_inc();  
  39.   
  40.     CHECKCALLING_BEGIN(ctx)  
  41.     int r = skynet_module_instance_init(mod, inst, ctx, param); // 执行snlua_init() 完成服务的创建  
  42.     CHECKCALLING_END(ctx)  
  43.     if (r == 0) {  
  44.         struct skynet_context * ret = skynet_context_release(ctx);  
  45.         if (ret) {  
  46.             ctx->init = true;  
  47.         }  
  48.         skynet_globalmq_push(queue);  
  49.         if (ret) {  
  50.             skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");  
  51.         }  
  52.         return ret;  
  53.     } else {  
  54.         skynet_error(ctx, "FAILED launch %s", name);  
  55.         uint32_t handle = ctx->handle;  
  56.         skynet_context_release(ctx);  
  57.         skynet_handle_retire(handle);  
  58.         struct drop_t d = { handle };  
  59.         skynet_mq_release(queue, drop_message, &d);  
  60.         return NULL;  
  61.     }  
  62. }  

看下 snlua_init服务实例化的过程:

[cpp] view plain copy
  1. // service_snlua.c  
  2.   
  3. int  
  4. snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {  
  5.     int sz = strlen(args);  
  6.     char * tmp = skynet_malloc(sz);  
  7.     memcpy(tmp, args, sz);  
  8.     skynet_callback(ctx, l , _launch);  // 设置回调函数为 _launch  
  9.     const char * self = skynet_command(ctx, "REG", NULL); // 注册这个服务  
  10.     uint32_t handle_id = strtoul(self+1, NULL, 16);  
  11.   
  12.     /* it must be first message 
  13.      * 把参数当作消息内容发给这个服务,就是 skynet.newservice(name, ...) 后面的 ... 
  14.      * 目的是驱动服务完成初始化,后面会讲到,skynet服务是消息驱动。 
  15.      */  
  16.     skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);   
  17.     return 0;  
  18. }  
  19.   
  20.   
  21. static int  
  22. _launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source ,  
  23.    const void * msg, size_t sz) {  
  24.     assert(type == 0 && session == 0);  
  25.     struct snlua *l = ud;  
  26.     skynet_callback(context, NULL, NULL);  // 设置回调函数为 NULL  
  27.     int err = _init(l, context, msg, sz);  
  28.     if (err) {  
  29.         skynet_command(context, "EXIT", NULL);  
  30.     }  
  31.   
  32.     return 0;  
  33. }  
  34.   
  35. // 完成服务的实例化,执行服务lua代码  
  36. static int  
  37. _init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {  
  38.     lua_State *L = l->L;  
  39.     l->ctx = ctx;  
  40.     lua_gc(L, LUA_GCSTOP, 0);  
  41.     lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */  
  42.     lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");  
  43.     luaL_openlibs(L);  
  44.     lua_pushlightuserdata(L, ctx);  
  45.     lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");  
  46.     luaL_requiref(L, "skynet.codecache", codecache , 0);  
  47.     lua_pop(L,1);  
  48.   
  49.     const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");  
  50.     lua_pushstring(L, path);  
  51.     lua_setglobal(L, "LUA_PATH");  
  52.     const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");  
  53.     lua_pushstring(L, cpath);  
  54.     lua_setglobal(L, "LUA_CPATH");  
  55.     const char *service = optstring(ctx, "luaservice""./service/?.lua");  
  56.     lua_pushstring(L, service);  
  57.     lua_setglobal(L, "LUA_SERVICE");  
  58.     const char *preload = skynet_command(ctx, "GETENV""preload");  
  59.     lua_pushstring(L, preload);  
  60.     lua_setglobal(L, "LUA_PRELOAD");  
  61.   
  62.     lua_pushcfunction(L, traceback);  
  63.     assert(lua_gettop(L) == 1);  
  64.   
  65.     const char * loader = optstring(ctx, "lualoader""./lualib/loader.lua");  
  66.   
  67.     int r = luaL_loadfile(L,loader); // 加载loader模块代码  
  68.     if (r != LUA_OK) {  
  69.         skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));  
  70.         _report_launcher_error(ctx);  
  71.         return 1;  
  72.     }  
  73.     lua_pushlstring(L, args, sz);  
  74.     // 把服务名等参数传入,执行loader模块代码,实际上是通过loader加载和执行服务代码  
  75.     r = lua_pcall(L,1,0,1);   
  76.     if (r != LUA_OK) {  
  77.         skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));  
  78.         _report_launcher_error(ctx);  
  79.         return 1;  
  80.     }  
  81.     lua_settop(L,0);  
  82.   
  83.     lua_gc(L, LUA_GCRESTART, 0);  
  84.   
  85.     return 0;  
  86. }  

看下loader的处理:

[plain] view plain copy
  1. -- loader.lua  
  2.   
  3. SERVICE_NAME = args[1]  
  4.   
  5. local main, pattern  
  6.   
  7. local err = {}  
  8. for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do  
  9.     local filename = string.gsub(pat, "?", SERVICE_NAME)  
  10.     local f, msg = loadfile(filename)  -- 加载服务代码  
  11.     if not f then  
  12.         table.insert(err, msg)  
  13.     else  
  14.         pattern = pat  
  15.         main = f  
  16.         break  
  17.     end  
  18. end  
  19.   
  20. if not main then  
  21.     error(table.concat(err, "\n"))  
  22. end  
  23.   
  24. main(select(2, table.unpack(args))) -- 执行服务代码  

顺道看下 skynet_callback 函数,很简单。

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. void   
  4. skynet_callback(struct skynet_context * context, void *ud, skynet_cb cb) {  
  5.     context->cb = cb;  
  6.     context->cb_ud = ud;  
  7. }  
到这里,服务就完成数据结构的初始化,lua层面是 lua state,数据结构是struct skynet_context *ctx

那什么时候会执行到这个回调函数_launch?这个问题要从skynet服务怎么被调度说起。


服务的调度

skynet启动时,会根据thread参数启动一定数量的调度线程,执行这个worker函数,如下:

[cpp] view plain copy
  1. // skynet_start.c  
  2.   
  3. // 调度线程的工作函数  
  4. static void *  
  5. thread_worker(void *p) {  
  6.     struct worker_parm *wp = p;  
  7.     int id = wp->id;  
  8.     int weight = wp->weight;  
  9.     struct monitor *m = wp->m;  
  10.     struct skynet_monitor *sm = m->m[id];  
  11.     skynet_initthread(THREAD_WORKER);  
  12.     struct message_queue * q = NULL;  
  13.     while (!m->quit) {  
  14.         q = skynet_context_message_dispatch(sm, q, weight); // 消息队列的派发和处理  
  15.         if (q == NULL) {  
  16.             if (pthread_mutex_lock(&m->mutex) == 0) {  
  17.                 ++ m->sleep;  
  18.                 // "spurious wakeup" is harmless,  
  19.                 // because skynet_context_message_dispatch() can be call at any time.  
  20.                 if (!m->quit)  
  21.                     pthread_cond_wait(&m->cond, &m->mutex);  
  22.                 -- m->sleep;  
  23.                 if (pthread_mutex_unlock(&m->mutex)) {  
  24.                     fprintf(stderr, "unlock mutex error");  
  25.                     exit(1);  
  26.                 }  
  27.             }  
  28.         }  
  29.     }  
  30.     return NULL;  
  31. }  

而调度线程做的事,就是不断从全局队列取出消息队列,处理消息队列的消息

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. // 处理服务消息  
  4. struct message_queue *   
  5. skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {  
  6.     if (q == NULL) {  
  7.         q = skynet_globalmq_pop();  
  8.         if (q==NULL)  
  9.             return NULL;  
  10.     }  
  11.   
  12.     uint32_t handle = skynet_mq_handle(q);  
  13.   
  14.     struct skynet_context * ctx = skynet_handle_grab(handle);  
  15.     if (ctx == NULL) {  
  16.         struct drop_t d = { handle };  
  17.         skynet_mq_release(q, drop_message, &d);  
  18.         return skynet_globalmq_pop();  
  19.     }  
  20.   
  21.     int i,n=1;  
  22.     struct skynet_message msg;  
  23.   
  24.     for (i=0;i<n;i++) {  
  25.         if (skynet_mq_pop(q,&msg)) {  
  26.             skynet_context_release(ctx);  
  27.             return skynet_globalmq_pop();  
  28.         } else if (i==0 && weight >= 0) {  
  29.             n = skynet_mq_length(q);  
  30.             n >>= weight;  
  31.         }  
  32.         int overload = skynet_mq_overload(q);  
  33.         if (overload) {  
  34.             skynet_error(ctx, "May overload, message queue length = %d", overload);  
  35.         }  
  36.   
  37.         skynet_monitor_trigger(sm, msg.source , handle);  
  38.   
  39.         if (ctx->cb == NULL) {  
  40.             skynet_free(msg.data);  
  41.         } else {  
  42.             dispatch_message(ctx, &msg); // 处理回调函数  
  43.         }  
  44.   
  45.         skynet_monitor_trigger(sm, 0,0);  
  46.     }  
  47.   
  48.     assert(q == ctx->queue);  
  49.     struct message_queue *nq = skynet_globalmq_pop();  
  50.     if (nq) {  
  51.         // If global mq is not empty , push q back, and return next queue (nq)  
  52.         // Else (global mq is empty or block, don't push q back, and return q again  
  53.         // (for next dispatch)  
  54.         skynet_globalmq_push(q); //TODO 为何不判断队列有无消息  
  55.         q = nq;  
  56.     }   
  57.     skynet_context_release(ctx);  
  58.   
  59.     return q;  
  60. }  

在dispatch_message完成消息的回调,看下这个函数:

[cpp] view plain copy
  1. // 处理回调函数  
  2. static void  
  3. dispatch_message(struct skynet_context *ctx, struct skynet_message *msg) {  
  4.     assert(ctx->init);  
  5.     CHECKCALLING_BEGIN(ctx)  
  6.     pthread_setspecific(G_NODE.handle_key, (void *)(uintptr_t)(ctx->handle));  
  7.     int type = msg->sz >> HANDLE_REMOTE_SHIFT;  
  8.     size_t sz = msg->sz & HANDLE_MASK;  
  9.     if (ctx->logfile) {  
  10.         skynet_log_output(ctx->logfile, msg->source, type, msg->session, msg->data, sz);  
  11.     }  
  12.     // 执行回调函数  
  13.     if (!ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz)) {  
  14.         skynet_free(msg->data);  
  15.     }   
  16.     CHECKCALLING_END(ctx)  
  17. }  

可以看出,每个服务都有一个消息队列,如果有新消息就会加到全局队列,等待skynet取出分发,回调处理若干条消息。然后,利用 ctx->cb 处理,完成事件驱动。


服务消息处理

下面以 example/simpledb.lua做说明,这是个典型的skynet服务。

[plain] view plain copy
  1. local skynet = require "skynet"  
  2. require "skynet.manager"    -- import skynet.register  
  3. local db = {}  
  4.   
  5. local command = {}  
  6.   
  7. function command.GET(key)  
  8.     return db[key]  
  9. end  
  10.   
  11. function command.SET(key, value)  
  12.     local last = db[key]  
  13.     db[key] = value  
  14.     return last  
  15. end  
  16.   
  17. skynet.start(function()  
  18.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  19.         local f = command[string.upper(cmd)]  
  20.         if f then  
  21.             skynet.ret(skynet.pack(f(...)))  
  22.         else  
  23.             error(string.format("Unknown command %s", tostring(cmd)))  
  24.         end  
  25.     end)  
  26.     skynet.register "SIMPLEDB"  
  27. end)  

服务的代码被loader加载后就会执行,这里就会执行到 skynet.start(func) ,完成服务的启动。

[plain] view plain copy
  1. -- skynet.lua    
  2.   
  3. function skynet.start(start_func)    
  4.     c.callback(skynet.dispatch_message)  -- 设置回调函数  
  5.     skynet.timeout(0, function()    
  6.         skynet.init_service(start_func)    
  7.     end)    
  8. end    
  9.   
  10. function skynet.dispatch_message(...)  
  11.     local succ, err = pcall(raw_dispatch_message,...)  -- 处理消息  
  12.   
  13.     -- 处理其他 skynet.fork 出来的协程  
  14.     while true do  
  15.         local key,co = next(fork_queue)  
  16.         if co == nil then  
  17.             break  
  18.         end  
  19.         fork_queue[key] = nil  
  20.         local fork_succ, fork_err = pcall(suspend,co,coroutine.resume(co))  
  21.         if not fork_succ then  
  22.             if succ then  
  23.                 succ = false  
  24.                 err = tostring(fork_err)  
  25.             else  
  26.                 err = tostring(err) .. "\n" .. tostring(fork_err)  
  27.             end  
  28.         end  
  29.     end  
  30.     assert(succ, tostring(err))  
  31. end  

前面也讨论了,c.XXX是调c函数实现的,从luaopen_skynet_core可以找到 callback的处理函数。

[cpp] view plain copy
  1. // lua-skynet.c     
  2.   
  3. static int  
  4. _callback(lua_State *L) {  
  5.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  6.     int forward = lua_toboolean(L, 2);  
  7.     luaL_checktype(L,1,LUA_TFUNCTION); // 取到上述c.callback(F)的F  
  8.     lua_settop(L,1);  
  9.     lua_rawsetp(L, LUA_REGISTRYINDEX, _cb); // 记录lua函数F到_cb这个索引位置  
  10.   
  11.     lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);  
  12.     lua_State *gL = lua_tothread(L,-1);  
  13.   
  14.     if (forward) {  
  15.         skynet_callback(context, gL, forward_cb);  
  16.     } else {  
  17.         skynet_callback(context, gL, _cb);  // 设置消息回调处理函数  
  18.     }  
  19.   
  20.     return 0;  
  21. }  
  22.   
  23. static int  
  24. _cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source,  
  25.    const void * msg, size_t sz) {  
  26.     lua_State *L = ud;  
  27.     int trace = 1;  
  28.     int r;  
  29.     int top = lua_gettop(L);  
  30.     if (top == 0) {  
  31.         lua_pushcfunction(L, traceback);  
  32.         lua_rawgetp(L, LUA_REGISTRYINDEX, _cb); // 取出_cb索引位置的lua函数  
  33.     } else {  
  34.         assert(top == 2);  
  35.     }  
  36.     lua_pushvalue(L,2);  
  37.   
  38.     lua_pushinteger(L, type);  
  39.     lua_pushlightuserdata(L, (void *)msg);  
  40.     lua_pushinteger(L,sz);  
  41.     lua_pushinteger(L, session);  
  42.     lua_pushinteger(L, source);  
  43.   
  44.     r = lua_pcall(L, 5, 0 , trace); // 执行lua函数,也就是 skynet.dispatch_message  
  45.   
  46.     if (r == LUA_OK) {  
  47.         return 0;  
  48.     }  
  49.     // 执行出错,就打印一些调试数据  
  50.     const char * self = skynet_command(context, "REG", NULL);  
  51.     switch (r) {  
  52.     case LUA_ERRRUN:  
  53.         skynet_error(context, "lua call [%x to %s : %d msgsz = %d] error : " KRED "%s" KNRM, source , self, session, sz, lua_tostring(L,-1));  
  54.         break;  
  55.     case LUA_ERRMEM:  
  56.         skynet_error(context, "lua memory error : [%x to %s : %d]", source , self, session);  
  57.         break;  
  58.     case LUA_ERRERR:  
  59.         skynet_error(context, "lua error in error : [%x to %s : %d]", source , self, session);  
  60.         break;  
  61.     case LUA_ERRGCMM:  
  62.         skynet_error(context, "lua gc error : [%x to %s : %d]", source , self, session);  
  63.         break;  
  64.     };  
  65.   
  66.     lua_pop(L,1);  
  67.   
  68.     return 0;  
  69. }  

紧接着回头看下skynet.timeout(0, function() skynet.init_service(start_func) end) 的处理

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.timeout(ti, func)  
  4.     local session = c.command("TIMEOUT",tostring(ti)) -- 超时处理  
  5.     assert(session)  
  6.     session = tonumber(session)  
  7.     local co = co_create(func) --  从协程池找到空闲的协程来执行这个函数  
  8.     assert(session_id_coroutine[session] == nil)  
  9.     session_id_coroutine[session] = co  
  10. end  

前面也提到 c.command 的处理,对于“TIMEOUT”的处理过程如下:

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. static const char *  
  4. cmd_timeout(struct skynet_context * context, const char * param) {  
  5.     char * session_ptr = NULL;  
  6.     int ti = strtol(param, &session_ptr, 10); // 超时时间  
  7.     int session = skynet_context_newsession(context);  
  8.     skynet_timeout(context->handle, ti, session); // 处理超时功能  
  9.     sprintf(context->result, "%d", session);  
  10.     return context->result;  
  11. }  
看下skynet_timeout的处理

[cpp] view plain copy
  1. // skynet_timer.c  
  2.   
  3. int  
  4. skynet_timeout(uint32_t handle, int time, int session) {  
  5.     if (time == 0) {  
  6.         struct skynet_message message;  
  7.         message.source = 0;  
  8.         message.session = session;  
  9.         message.data = NULL;  
  10.         message.sz = PTYPE_RESPONSE << HANDLE_REMOTE_SHIFT;  
  11.   
  12.         // 如果time为0,把超时事件压到服务的消息队列,等待调度处理  
  13.         if (skynet_context_push(handle, &message)) {  
  14.             return -1;  
  15.         }  
  16.     } else {  
  17.         struct timer_event event;  
  18.         event.handle = handle;  
  19.         event.session = session;  
  20.         // time不为0,超时事件挂到时间轮上,等待超时处理  
  21.         timer_add(TI, &event, sizeof(event), time);   
  22.     }  
  23.   
  24.     return session;  
  25. }  
co_create 是从协程池找到空闲的协程来执行这个函数,没有空闲的协程则创建。
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function co_create(f)  
  4.     local co = table.remove(coroutine_pool)  
  5.     if co == nil then  
  6.         co = coroutine.create(function(...)  
  7.             f(...)  
  8.             while true do  
  9.                 f = nil  
  10.                 coroutine_pool[#coroutine_pool+1] = co  
  11.                 f = coroutine_yield "EXIT" -- a. yield 第一次获取函数  
  12.                 f(coroutine_yield()) -- b. yield 第二次获取函数参数,然后执行函数f  
  13.             end  
  14.         end)  
  15.     else  
  16.         -- resume 第一次让协程取到函数,就是 a点  
  17.         -- 之后再 resume 第二次传入参数,并执行函数,就是b点  
  18.         coroutine.resume(co, f)  
  19.     end  
  20.     return co  
  21. end  
顺道说下 skynet.dispatch的处理(还记得吧,在前面 skynet.start时调用的):
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.dispatch(typename, func)  
  4.     local p = proto[typename]  
  5.     if func then  
  6.         local ret = p.dispatch  
  7.         p.dispatch = func  -- 设置协议的处理函数  
  8.         return ret  
  9.     else  
  10.         return p and p.dispatch  
  11.     end  
  12. end  
这一步是设置proto[typename].dispatch,skynet.call/skynet.send的消息都会找到这个回调函数处理,如下:

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function raw_dispatch_message(prototype, msg, sz, session, source, ...)  
  4.     -- skynet.PTYPE_RESPONSE = 1, read skynet.h  
  5.     if prototype == 1 then  
  6.         local co = session_id_coroutine[session]  
  7.         if co == "BREAK" then  
  8.             session_id_coroutine[session] = nil  
  9.         elseif co == nil then  
  10.             unknown_response(session, source, msg, sz)  
  11.         else  
  12.             session_id_coroutine[session] = nil  
  13.             suspend(co, coroutine.resume(co, true, msg, sz))  
  14.         end  
  15.     else  
  16.         local p = proto[prototype]  
  17.         if p == nil then  
  18.             if session ~= 0 then  
  19.                 c.send(source, skynet.PTYPE_ERROR, session, "")  
  20.             else  
  21.                 unknown_request(session, source, msg, sz, prototype)  
  22.             end  
  23.             return  
  24.         end  
  25.         local f = p.dispatch -- 找到dispatch函数  
  26.         if f then  
  27.             local ref = watching_service[source]  
  28.             if ref then  
  29.                 watching_service[source] = ref + 1  
  30.             else  
  31.                 watching_service[source] = 1  
  32.             end  
  33.             local co = co_create(f)  
  34.             session_coroutine_id[co] = session  
  35.             session_coroutine_address[co] = source  
  36.             suspend(co, coroutine.resume(co, session,source, p.unpack(msg,sz, ...)))  
  37.         else  
  38.             unknown_request(session, source, msg, sz, proto[prototype].name)  
  39.         end  
  40.     end  
  41. end  


关于proto[typename],也作下简要的说明。可以看作是对数据的封装,方便不同服务间、不同节点间,以及前后端的数据通讯,不需要手动封包解包。默认支持lua/response/error这3个协议,还有log和debug协议,除了这几个,其他要自己调用skynet.register_protocol 注册

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.register_protocol(class)  
  4.     local name = class.name  
  5.     local id = class.id  
  6.     assert(proto[name] == nil)  
  7.     assert(type(name) == "string" and type(id) == "number" and id >=0 and id <=255)  
  8.     proto[name] = class  
  9.     proto[id] = class  
  10. end  
  11.   
  12. do  
  13.     local REG = skynet.register_protocol  
  14.   
  15.     REG {  
  16.         name = "lua",  
  17.         id = skynet.PTYPE_LUA,  
  18.         pack = skynet.pack,  
  19.         unpack = skynet.unpack,  
  20.     }  
  21.   
  22.     REG {  
  23.         name = "response",  
  24.         id = skynet.PTYPE_RESPONSE,  
  25.     }  
  26.   
  27.     REG {  
  28.         name = "error",  
  29.         id = skynet.PTYPE_ERROR,  
  30.         unpack = function(...) return ... end,  
  31.         dispatch = _error_dispatch,  
  32.     }  
  33. end  



服务之间的通讯

服务与服务之间互通消息,主要是这两个接口来通讯的:

1、skynet.send 消息发送

2、skynet.call 消息发送并返回

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.send(addr, typename, ...)  
  4.     local p = proto[typename]  
  5.     return c.send(addr, p.id, 0 , p.pack(...))  
  6. end  
  7.   
  8. function skynet.call(addr, typename, ...)  
  9.     local p = proto[typename]  
  10.     local session = c.send(addr, p.id , nil , p.pack(...))  
  11.     if session == nil then  
  12.         error("call to invalid address " .. skynet.address(addr))  
  13.     end  
  14.     return p.unpack(yield_call(addr, session))  
  15. end  
可以看出,skynet.call 对比多了返回值的处理,所以就以 skynet.call 做说明。

和前面的c.XXX一样,c.send的实现很快找到,如下:

[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. static int  
  4. _send(lua_State *L) {  
  5.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  6.     uint32_t dest = (uint32_t)lua_tointeger(L, 1);  
  7.     const char * dest_string = NULL; // 节点名字  
  8.     if (dest == 0) {  
  9.         if (lua_type(L,1) == LUA_TNUMBER) {  
  10.             return luaL_error(L, "Invalid service address 0");  
  11.         }  
  12.         dest_string = get_dest_string(L, 1);  
  13.     }  
  14.   
  15.     int type = luaL_checkinteger(L, 2);  
  16.     int session = 0;  
  17.     if (lua_isnil(L,3)) {  
  18.         type |= PTYPE_TAG_ALLOCSESSION;  
  19.     } else {  
  20.         session = luaL_checkinteger(L,3);  
  21.     }  
  22.   
  23.     int mtype = lua_type(L,4);  
  24.     switch (mtype) {  
  25.     case LUA_TSTRING: {  
  26.         size_t len = 0;  
  27.         void * msg = (void *)lua_tolstring(L,4,&len);  
  28.         if (len == 0) {  
  29.             msg = NULL;  
  30.         }  
  31.         if (dest_string) { // 以节点名字发消息  
  32.             session = skynet_sendname(context, 0, dest_string, type, session , msg, len);  
  33.         } else {   
  34.             session = skynet_send(context, 0, dest, type, session , msg, len);  
  35.         }  
  36.         break;  
  37.     }  
  38.     case LUA_TLIGHTUSERDATA: {  
  39.         void * msg = lua_touserdata(L,4);  
  40.         int size = luaL_checkinteger(L,5);  
  41.         if (dest_string) { // 以节点名字发消息  
  42.             session = skynet_sendname(context, 0, dest_string, type | PTYPE_TAG_DONTCOPY, session, msg, size);  
  43.         } else {  
  44.             session = skynet_send(context, 0, dest, type | PTYPE_TAG_DONTCOPY, session, msg, size);  
  45.         }  
  46.         break;  
  47.     }  
  48.     default:  
  49.         luaL_error(L, "skynet.send invalid param %s", lua_typename(L, lua_type(L,4)));  
  50.     }  
  51.     if (session < 0) {  
  52.         // send to invalid address  
  53.         // todo: maybe throw an error would be better  
  54.         return 0;  
  55.     }  
  56.     lua_pushinteger(L,session);  
  57.     return 1;  
  58. }  
这里看下 skynet_send 的实现吧:
[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. int  
  4. skynet_send(struct skynet_context * context, uint32_t source, uint32_t destination , int type,   
  5.    int session, void * data, size_t sz) {  
  6.     if ((sz & MESSAGE_TYPE_MASK) != sz) {  
  7.         skynet_error(context, "The message to %x is too large", destination);  
  8.         if (type & PTYPE_TAG_DONTCOPY) {  
  9.             skynet_free(data);  
  10.         }  
  11.         return -1;  
  12.     }  
  13.     _filter_args(context, type, &session, (void **)&data, &sz); // 复制消息数据,获取session  
  14.   
  15.     if (source == 0) {  
  16.         source = context->handle;  
  17.     }  
  18.   
  19.     if (destination == 0) {  
  20.         return session;  
  21.     }  
  22.     if (skynet_harbor_message_isremote(destination)) { // 是否跨节点消息  
  23.         struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg));  
  24.         rmsg->destination.handle = destination;  
  25.         rmsg->message = data;  
  26.         rmsg->sz = sz;  
  27.         skynet_harbor_send(rmsg, source, session); // 发给其他节点,这里不讨论  
  28.     } else {  
  29.         struct skynet_message smsg;  
  30.         smsg.source = source;  
  31.         smsg.session = session;  
  32.         smsg.data = data;  
  33.         smsg.sz = sz;  
  34.   
  35.         // 发给其他服务,实际就是复制消息到队列,然后将消息队列加到全局队列  
  36.         if (skynet_context_push(destination, &smsg)) {  
  37.             skynet_free(data);  
  38.             return -1;  
  39.         }  
  40.     }  
  41.     return session;  
  42. }  
到这里,消息算是“发送”出去了,skynet.call 可能拿到返回的结果,也就是这一步:
[plain] view plain copy
  1. p.unpack(yield_call(addr, session))  
看下 yield_call 的实现,其实就是挂起协程,并把 “CALL”, session 返回给 coroutine.resume 者
[plain] view plain copy
  1. local coroutine_yield = coroutine.yield  
  2.   
  3. local function yield_call(service, session)  
  4.     watching_session[session] = service  
  5.     local succ, msg, sz = coroutine_yield("CALL", session)  
  6.     watching_session[session] = nil  
  7.     if not succ then  
  8.         error "call failed"  
  9.     end  
  10.     return msg,sz  
  11. end  

另外补充下,skynet还对coroutine.yield进行了改写,但这个函数的功能不变,还是挂起协程,等待 coroutine.resume

继续看前面的代码,这里有个问题,skynet.call 返回数据哪里来的?

实际上,还是走了服务间通讯的路子,看回 simpledb 的代码。

[plain] view plain copy
  1. -- simpledb.lua  
  2.   
  3. skynet.start(function()  
  4.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  5.         local f = command[string.upper(cmd)]  
  6.         if f then  
  7.             skynet.ret(skynet.pack(f(...))) -- 这里 skynet.ret 返回数据  
  8.         else  
  9.             error(string.format("Unknown command %s", tostring(cmd)))  
  10.         end  
  11.     end)  
  12.     skynet.register "SIMPLEDB"  
  13. end)  
看下skynet.ret 的处理,挂起返回数据。
[plain] view plain copy
  1. function skynet.ret(msg, sz)  
  2.     msg = msg or ""  
  3.     return coroutine_yield("RETURN", msg, sz)  
  4. end  
而skynet.dispatch 设置的回调函数,当回调触发时就会走到 raw_dispatch_message (前面有说明),继续执行就到了下面这步(函数较大,做了删节):
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function suspend(co, result, command, param, size)  
  4.     if not result then  
  5.         local session = session_coroutine_id[co]  
  6.         if session then -- coroutine may fork by others (session is nil)  
  7.             local addr = session_coroutine_address[co]  
  8.             if session ~= 0 then  
  9.                 -- only call response error  
  10.                 c.send(addr, skynet.PTYPE_ERROR, session, "")  
  11.             end  
  12.             session_coroutine_id[co] = nil  
  13.             session_coroutine_address[co] = nil  
  14.         end  
  15.         error(debug.traceback(co,tostring(command)))  
  16.     end  
  17.     if command == "CALL" then  -- skynet.call 挂起返回时操作  
  18.         session_id_coroutine[param] = co  
  19.     elseif command == "RETURN" then  -- skynet.ret 挂起返回时操作  
  20.         local co_session = session_coroutine_id[co]  
  21.         local co_address = session_coroutine_address[co]  
  22.         if param == nil or session_response[co] then  
  23.             error(debug.traceback(co))  
  24.         end  
  25.         session_response[co] = true  
  26.         local ret  
  27.         if not dead_service[co_address] then  
  28.             -- 把处理结果当作消息发给请求的服务  
  29.             ret = c.send(co_address, skynet.PTYPE_RESPONSE, co_session, param, size) ~= nil  
  30.             if not ret then  
  31.                 -- If the package is too large, returns nil. so we should report error back  
  32.                 c.send(co_address, skynet.PTYPE_ERROR, co_session, "")  
  33.             end  
  34.         elseif size ~= nil then  
  35.             c.trash(param, size)  
  36.             ret = false  
  37.         end  
  38.         return suspend(co, coroutine.resume(co, ret))  
  39.     else  
  40.         error("Unknown command : " .. command .. "\n" .. debug.traceback(co))  
  41.     end  
  42.     dispatch_wakeup() -- 处理所有需要恢复的协程  
  43.     dispatch_error_queue()    
  44. end  
到这里,服务间的通讯就讲完了。可以看出,服务间通讯基于消息,而消息数据也通过复制以避免数据读写加锁。


skynet服务的设计

统观整篇文章,不难发现:

每个skynet服务都是一个lua state,也就是一个lua虚拟机实例。而且,每个服务都是隔离的,各自使用自己独立的内存空间,服务之间通过发消息来完成数据交换。

架构图如下:


图片取自spartan1的skynet任务调度分析

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服务的缺陷


并发问题

这要从skynet一个服务霸占调度器的极端例子说起。

下面给出两个lua代码 main.lua 和 simpledb.lua,和一个配置文件 config 

[plain] view plain copy
  1. -- main.lua  
  2.   
  3. local skynet = require "skynet"  
  4.   
  5. skynet.start(function()  
  6.     print("Server start")  
  7.     skynet.newservice("simpledb")  
  8.   
  9.     -- 发消息给simpledb服务  
  10.     skynet.send("SIMPLEDB", "lua", "TEST")  
  11.     -- 死循环占据cpu  
  12.     local i = 0  
  13.     while true do  
  14.         i = i>100000000 and 0 or i+1  
  15.         if i==0 then  
  16.             print("I'm working")   
  17.         end  
  18.     end  
  19.     skynet.exit()  
  20. end)  
[plain] view plain copy
  1. -- simpledb.lua  
  2.   
  3. local skynet = require "skynet"  
  4. require "skynet.manager"    -- import skynet.register  
  5. local db = {}  
  6.   
  7. local command = {}  
  8.   
  9. function command.TEST()  
  10.     print("Simpledb test")  
  11.     return true  
  12. end  
  13.   
  14. skynet.start(function()  
  15.     print("Simpledb start")  
  16.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  17.         local f = command[string.upper(cmd)]  
  18.         if f then  
  19.             skynet.ret(skynet.pack(f(...)))  
  20.         else  
  21.             error(string.format("Unknown command %s", tostring(cmd)))  
  22.         end  
  23.     end)  
  24.     skynet.register "SIMPLEDB"  
  25. end)  
配置文件 config
[plain] view plain copy
  1. root = "./"  
  2. thread = 1  
  3. logger = nil  
  4. logpath = "."  
  5. harbor = 1  
  6. address = "127.0.0.1:2526"  
  7. master = "127.0.0.1:2013"  
  8. start = "main"  
  9. bootstrap = "snlua bootstrap"  
  10. standalone = "0.0.0.0:2013"  
  11. luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"  
  12. lualoader = "lualib/loader.lua"  
  13. snax = root.."examples/?.lua;"..root.."test/?.lua"  
  14. cpath = root.."cservice/?.so"  

注意了,这里特地把 thread 设置为1,表示只启动一个调度线程。

现在,启动skynet执行我们的例子,结果如下:
[root@local skynet]# ./skynet config
[:01000001] LAUNCH logger 
[:01000002] LAUNCH snlua bootstrap
[:01000003] LAUNCH snlua launcher
[:01000004] LAUNCH snlua cmaster
[:01000004] master listen socket 0.0.0.0:2013
[:01000005] LAUNCH snlua cslave
[:01000005] slave connect to master 127.0.0.1:2013
[:01000004] connect from 127.0.0.1:41589 4
[:01000006] LAUNCH harbor 1 16777221
[:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526
[:01000005] Waiting for 0 harbors
[:01000005] Shakehand ready
[:01000007] LAUNCH snlua datacenterd
[:01000008] LAUNCH snlua service_mgr
[:01000009] LAUNCH snlua main
Server start
[:0100000a] LAUNCH snlua simpledb
Simpledb start
I'm working
I'm working
I'm working
可以看出,simpledb 没有机会处理TEST消息,一直是main模块占据着cpu。


为什么会出现这个情况?

这和skynet的调度机制有关。skynet使用全局队列保存了要调度的服务,调度算法是先来先服务。如果某个服务有新消息,就把这个服务加到调度队列中,然后等待调度线程调度。而skynet服务的调度切换依赖于协程的挂起,如果当前调度的服务没有主动挂起或退出,就会一直执行,不调度其他服务了。

这种机制的好处就是实现简单,有利于长作业,上下文切换较少,缺点就是并发效率低,而且像这种长作业的服务超过调度线程数量,就可能导致其他服务饿死。


内存隐患

细心的同学会发现,在服务处理新消息时,是通过创建新协程来处理的(见co_create),虽然协程会被重复利用,但在当前版本下,这种不断创建协程来消息的方式本身存在不稳定因素:
1、协程只增加不减少,意味过了某个并发高峰后内存不会降下来。
2、创建协程也有一定开销,容易触发GC,也占用内存,协程的数量规模不容易控制
3、如果解决第1点,最槽糕的情况是,不断要创建协程,不断要销毁协程,频繁触发gc

这里有一个极端的例子:

如果服务a不断给服务b发消息,但服务b的处理过程存在长时间挂起,这样,对于服务a发来的消息,服务b会不断创建协程去处理,就导致内存被大量占用的情况出现。

但是skynet也提供办法解决这个问题,方法是主动调用GC:

[plain] view plain copy
  1. skynet.send(service,"debug","GC")  

另外,有兴趣的同学,不妨看下实现代码:

[plain] view plain copy
  1. -- debug.lua  
  2.   
  3. return function (skynet, export)  
  4.   
  5. -- 处理 GC 消息  
  6. function dbgcmd.GC()    
  7.     export.clear()  -- export 是 skynet.debug 的传入参数  
  8.     collectgarbage "collect"  -- 执行GC  
  9. end  
  10.   
  11. local function _debug_dispatch(session, address, cmd, ...)  
  12.     local f = (dbgcmd or init_dbgcmd())[cmd]    -- lazy init dbgcmd  
  13.     f(...)  
  14. end  
  15.   
  16. skynet.register_protocol {  
  17.     name = "debug",  -- 注册处理服务收到的 "debug" 消息  
  18.     id = assert(skynet.PTYPE_DEBUG),  
  19.     pack = assert(skynet.pack),  
  20.     unpack = assert(skynet.unpack),  
  21.     dispatch = _debug_dispatch,  
  22. }  
  23. end  

而什么时候调用了 skynet.debug 呢?看这里

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function clear_pool()  
  4.     coroutine_pool = {} -- 清空协程的引用  
  5. end  
  6.   
  7. -- debug设置回调处理函数  
  8. local debug = require "skynet.debug"  
  9. debug(skynet, {  
  10.     dispatch = skynet.dispatch_message,  
  11.     clear = clear_pool,  
  12.     suspend = suspend,  
  13. })  

就是说,但给服务发送GC消息时,就会清空协程池,随后执行底层GC接口。这样,不再有内容引用到这个协程,所以,协程会在GC时被清理。

至于协程只是挂起没有结束,为什么会被清理?

因为从协程池移走后,那些协程就变成了不可达的协程了,没有方法能 coroutine.resume 激活他们了,所以就会被gc掉。


同步问题

同步也是skynet存在的问题,当一个服务call其他服务时,当前协程会挂起,但是这个服务还可以接受并处理其他消息。如果多个协程改到同一个数据,你不做同步处

skynet服务的本质与缺陷

 4917人阅读 评论(5) 收藏 举报
 分类:

目录(?)[+]

skynet是为多人在线游戏打造的轻量级服务端框架,使用c+lua实现。使用这套框架的一个好处就是,基本只需要lua,很少用到c做开发,一定程度上提高了开发效率。但skynet文档也相对较少,所以这里利用一点时间学习和总结skynet相关内容,文章这里就讲解下skynet服务的本质与缺陷,希望能有所帮助。

skynet服务的本质

或许我们对skynet服务有着太多的疑问:

skynet服务究竟是什么,为什么有人说服务是一个lua虚拟机,服务与服务之间的通讯是怎样的,为什么服务的内存高居不下, 为什么拿skynet服务和erlang进程做比较?等等。。。而这一切的答案都在代码里面,让我们一步一步解开她的面纱。

服务创建API

先从skynet服务创建的接口说起,方式如下:

[plain] view plain copy
  1. skynet.newservice(name, ...)  

看下这个函数的实现:

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.newservice(name, ...)  
  4.     return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)  
  5. end  

实际上是调用另外一个服务(.launcher)完成skynet服务的创建。看下launcher服务的处理:

[plain] view plain copy
  1. -- launcher.lua  
  2.   
  3. -- 处理服务的创建  
  4. local function launch_service(service, ...)  
  5.     local param = table.concat({...}, " ")  
  6.     local inst = skynet.launch(service, param)  
  7.     local response = skynet.response()  
  8.     if inst then  
  9.         services[inst] = service .. " " .. param  
  10.         instance[inst] = response  
  11.     else  
  12.         response(false)  
  13.         return  
  14.     end  
  15.     return inst  
  16. end  
  17.   
  18. -- 处理 LAUNCH 类消息  
  19. function command.LAUNCH(_, service, ...)  
  20.     launch_service(service, ...)  
  21.     return NORET  
  22. end  
  23.   
  24.   
  25. -- 处理launcher服务接收到的消息  
  26. skynet.dispatch("lua", function(session, address, cmd , ...)  
  27.     cmd = string.upper(cmd)  
  28.     local f = command[cmd]  
  29.     if f then  
  30.         local ret = f(address, ...)  
  31.         if ret ~= NORET then  
  32.             skynet.ret(skynet.pack(ret))  
  33.         end  
  34.     else  
  35.         skynet.ret(skynet.pack {"Unknown command"} )  
  36.     end  
  37. end)  

也就是调用 skynet.launch(service, param),实际上 .launcher 服务也是通过这函数实现的。

[plain] view plain copy
  1. -- bootstrap.lua  
  2.   
  3. local launcher = assert(skynet.launch("snlua","launcher"))  
  4. skynet.name(".launcher", launcher)  

为什么要通过另外一个服务创建新的服务?主要目的是为了方便管理所有服务,比如统计,gc,杀掉服务等。


服务创建的实现

再来看下skynet.launch(service, param),服务创建的关键api:

[plain] view plain copy
  1. -- manager.lua  
  2.   
  3. local skynet = require "skynet"  
  4. local c = require "skynet.core"  
  5.   
  6. function skynet.launch(...)  
  7.     local addr = c.command("LAUNCH", table.concat({...}," "))  
  8.     if addr then  
  9.         return tonumber("0x" .. string.sub(addr , 2))  
  10.     end  
  11. end  
skynet.core这个是c实现的,编译成动态库给lua使用,可以在loadfunc时利用luaopen_* 找到这个c函数。实际接口函数如下:
[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. int  
  4. luaopen_skynet_core(lua_State *L) {  
  5.     luaL_checkversion(L);  
  6.   
  7.     luaL_Reg l[] = {  
  8.         { "send" , _send },  
  9.         { "genid", _genid },  
  10.         { "redirect", _redirect },  
  11.         { "command" , _command },  
  12.         { "error", _error },  
  13.         { "tostring", _tostring },  
  14.         { "harbor", _harbor },  
  15.         { "pack", _luaseri_pack },  
  16.         { "unpack", _luaseri_unpack },  
  17.         { "packstring", lpackstring },  
  18.         { "trash" , ltrash },  
  19.         { "callback", _callback },  
  20.         { NULL, NULL },  
  21.     };  
  22.   
  23.     luaL_newlibtable(L, l);  
  24.   
  25.     lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");  
  26.     struct skynet_context *ctx = lua_touserdata(L,-1);  
  27.     if (ctx == NULL) {  
  28.         return luaL_error(L, "Init skynet context first");  
  29.     }  
  30.   
  31.     luaL_setfuncs(L,l,1);  
  32.   
  33.     return 1;  
  34. }  

c.command对应的处理如下:

[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. static int _command(lua_State *L) {  
  4.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  5.     const char * cmd = luaL_checkstring(L,1);  
  6.     const char * result;  
  7.     const char * parm = NULL;  
  8.     if (lua_gettop(L) == 2) {  
  9.         parm = luaL_checkstring(L,2);  
  10.     }  
  11.   
  12.     result = skynet_command(context, cmd, parm);  
  13.     if (result) {  
  14.         lua_pushstring(L, result);  
  15.         return 1;  
  16.     }  
  17.     return 0;  
  18. }  

到了skynet_command的处理:

[cpp] view plain copy
  1. // lua_server.c  
  2.   
  3. static struct command_func cmd_funcs[] = {  
  4.     { "TIMEOUT", cmd_timeout },  
  5.     { "REG", cmd_reg },  
  6.     { "QUERY", cmd_query },  
  7.     { "NAME", cmd_name },  
  8.     { "NOW", cmd_now },  
  9.     { "EXIT", cmd_exit },  
  10.     { "KILL", cmd_kill },  
  11.     { "LAUNCH", cmd_launch },  
  12.     { "GETENV", cmd_getenv },  
  13.     { "SETENV", cmd_setenv },  
  14.     { "STARTTIME", cmd_starttime },  
  15.     { "ENDLESS", cmd_endless },  
  16.     { "ABORT", cmd_abort },  
  17.     { "MONITOR", cmd_monitor },  
  18.     { "MQLEN", cmd_mqlen },  
  19.     { "LOGON", cmd_logon },  
  20.     { "LOGOFF", cmd_logoff },  
  21.     { "SIGNAL", cmd_signal },  
  22.     { NULL, NULL },  
  23. };  
  24.   
  25. const char *   
  26. skynet_command(struct skynet_context * context, const char * cmd , const char * param) {  
  27.     struct command_func * method = &cmd_funcs[0];  
  28.     while(method->name) {  
  29.         if (strcmp(cmd, method->name) == 0) {  
  30.             return method->func(context, param);  
  31.         }  
  32.         ++method;  
  33.     }  
  34.   
  35.     return NULL;  
  36. }  
  37.   
  38. static const char *  
  39. cmd_launch(struct skynet_context * context, const char * param) {  
  40.     size_t sz = strlen(param);  
  41.     char tmp[sz+1];  
  42.     strcpy(tmp,param);  
  43.     char * args = tmp;  
  44.     char * mod = strsep(&args, " \t\r\n");  
  45.     args = strsep(&args, "\r\n");  
  46.     struct skynet_context * inst = skynet_context_new(mod,args);// 实例化上下文  
  47.     if (inst == NULL) {  
  48.         return NULL;  
  49.     } else {  
  50.         id_to_hex(context->result, inst->handle);  
  51.         return context->result;  
  52.     }  
  53. }  
再套上最前面的参数,也就是调用
[cpp] view plain copy
  1. skynet_context_new("snlua", name)  
再看下这个函数的实现。
[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. struct skynet_context *   
  4. skynet_context_new(const char * name, const char *param) {  
  5.     /* 这一步加载name的动态库,这里是snlua.so 
  6.      * snlua模块是 service_snlua.c 然后通过以下接口调用代码 
  7.      * skynet_module_instance_create()  -->  snlua_create() 
  8.      * skynet_module_instance_init()  -->  snlua_init() 
  9.      * skynet_module_instance_release()  -->  snlua_release() 
  10.      * skynet_module_instance_signal()  -->  snlua_signal() 
  11.      */  
  12.     struct skynet_module * mod = skynet_module_query(name);  
  13.   
  14.     if (mod == NULL)  
  15.         return NULL;  
  16.   
  17.     void *inst = skynet_module_instance_create(mod); // 执行snlua_create() 完成服务初始化  
  18.     if (inst == NULL)  
  19.         return NULL;  
  20.     struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));  
  21.     CHECKCALLING_INIT(ctx)  
  22.   
  23.     ctx->mod = mod;  
  24.     ctx->instance = inst;  
  25.     ctx->ref = 2;  
  26.     ctx->cb = NULL;  
  27.     ctx->cb_ud = NULL;  
  28.     ctx->session_id = 0;  
  29.     ctx->logfile = NULL;  
  30.   
  31.     ctx->init = false;  
  32.     ctx->endless = false;  
  33.     // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle  
  34.     ctx->handle = 0;   
  35.     ctx->handle = skynet_handle_register(ctx);  
  36.     struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);  
  37.     // init function maybe use ctx->handle, so it must init at last  
  38.     context_inc();  
  39.   
  40.     CHECKCALLING_BEGIN(ctx)  
  41.     int r = skynet_module_instance_init(mod, inst, ctx, param); // 执行snlua_init() 完成服务的创建  
  42.     CHECKCALLING_END(ctx)  
  43.     if (r == 0) {  
  44.         struct skynet_context * ret = skynet_context_release(ctx);  
  45.         if (ret) {  
  46.             ctx->init = true;  
  47.         }  
  48.         skynet_globalmq_push(queue);  
  49.         if (ret) {  
  50.             skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");  
  51.         }  
  52.         return ret;  
  53.     } else {  
  54.         skynet_error(ctx, "FAILED launch %s", name);  
  55.         uint32_t handle = ctx->handle;  
  56.         skynet_context_release(ctx);  
  57.         skynet_handle_retire(handle);  
  58.         struct drop_t d = { handle };  
  59.         skynet_mq_release(queue, drop_message, &d);  
  60.         return NULL;  
  61.     }  
  62. }  

看下 snlua_init服务实例化的过程:

[cpp] view plain copy
  1. // service_snlua.c  
  2.   
  3. int  
  4. snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {  
  5.     int sz = strlen(args);  
  6.     char * tmp = skynet_malloc(sz);  
  7.     memcpy(tmp, args, sz);  
  8.     skynet_callback(ctx, l , _launch);  // 设置回调函数为 _launch  
  9.     const char * self = skynet_command(ctx, "REG", NULL); // 注册这个服务  
  10.     uint32_t handle_id = strtoul(self+1, NULL, 16);  
  11.   
  12.     /* it must be first message 
  13.      * 把参数当作消息内容发给这个服务,就是 skynet.newservice(name, ...) 后面的 ... 
  14.      * 目的是驱动服务完成初始化,后面会讲到,skynet服务是消息驱动。 
  15.      */  
  16.     skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);   
  17.     return 0;  
  18. }  
  19.   
  20.   
  21. static int  
  22. _launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source ,  
  23.    const void * msg, size_t sz) {  
  24.     assert(type == 0 && session == 0);  
  25.     struct snlua *l = ud;  
  26.     skynet_callback(context, NULL, NULL);  // 设置回调函数为 NULL  
  27.     int err = _init(l, context, msg, sz);  
  28.     if (err) {  
  29.         skynet_command(context, "EXIT", NULL);  
  30.     }  
  31.   
  32.     return 0;  
  33. }  
  34.   
  35. // 完成服务的实例化,执行服务lua代码  
  36. static int  
  37. _init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {  
  38.     lua_State *L = l->L;  
  39.     l->ctx = ctx;  
  40.     lua_gc(L, LUA_GCSTOP, 0);  
  41.     lua_pushboolean(L, 1);  /* signal for libraries to ignore env. vars. */  
  42.     lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");  
  43.     luaL_openlibs(L);  
  44.     lua_pushlightuserdata(L, ctx);  
  45.     lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");  
  46.     luaL_requiref(L, "skynet.codecache", codecache , 0);  
  47.     lua_pop(L,1);  
  48.   
  49.     const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");  
  50.     lua_pushstring(L, path);  
  51.     lua_setglobal(L, "LUA_PATH");  
  52.     const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");  
  53.     lua_pushstring(L, cpath);  
  54.     lua_setglobal(L, "LUA_CPATH");  
  55.     const char *service = optstring(ctx, "luaservice""./service/?.lua");  
  56.     lua_pushstring(L, service);  
  57.     lua_setglobal(L, "LUA_SERVICE");  
  58.     const char *preload = skynet_command(ctx, "GETENV""preload");  
  59.     lua_pushstring(L, preload);  
  60.     lua_setglobal(L, "LUA_PRELOAD");  
  61.   
  62.     lua_pushcfunction(L, traceback);  
  63.     assert(lua_gettop(L) == 1);  
  64.   
  65.     const char * loader = optstring(ctx, "lualoader""./lualib/loader.lua");  
  66.   
  67.     int r = luaL_loadfile(L,loader); // 加载loader模块代码  
  68.     if (r != LUA_OK) {  
  69.         skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));  
  70.         _report_launcher_error(ctx);  
  71.         return 1;  
  72.     }  
  73.     lua_pushlstring(L, args, sz);  
  74.     // 把服务名等参数传入,执行loader模块代码,实际上是通过loader加载和执行服务代码  
  75.     r = lua_pcall(L,1,0,1);   
  76.     if (r != LUA_OK) {  
  77.         skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));  
  78.         _report_launcher_error(ctx);  
  79.         return 1;  
  80.     }  
  81.     lua_settop(L,0);  
  82.   
  83.     lua_gc(L, LUA_GCRESTART, 0);  
  84.   
  85.     return 0;  
  86. }  

看下loader的处理:

[plain] view plain copy
  1. -- loader.lua  
  2.   
  3. SERVICE_NAME = args[1]  
  4.   
  5. local main, pattern  
  6.   
  7. local err = {}  
  8. for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do  
  9.     local filename = string.gsub(pat, "?", SERVICE_NAME)  
  10.     local f, msg = loadfile(filename)  -- 加载服务代码  
  11.     if not f then  
  12.         table.insert(err, msg)  
  13.     else  
  14.         pattern = pat  
  15.         main = f  
  16.         break  
  17.     end  
  18. end  
  19.   
  20. if not main then  
  21.     error(table.concat(err, "\n"))  
  22. end  
  23.   
  24. main(select(2, table.unpack(args))) -- 执行服务代码  

顺道看下 skynet_callback 函数,很简单。

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. void   
  4. skynet_callback(struct skynet_context * context, void *ud, skynet_cb cb) {  
  5.     context->cb = cb;  
  6.     context->cb_ud = ud;  
  7. }  
到这里,服务就完成数据结构的初始化,lua层面是 lua state,数据结构是struct skynet_context *ctx

那什么时候会执行到这个回调函数_launch?这个问题要从skynet服务怎么被调度说起。


服务的调度

skynet启动时,会根据thread参数启动一定数量的调度线程,执行这个worker函数,如下:

[cpp] view plain copy
  1. // skynet_start.c  
  2.   
  3. // 调度线程的工作函数  
  4. static void *  
  5. thread_worker(void *p) {  
  6.     struct worker_parm *wp = p;  
  7.     int id = wp->id;  
  8.     int weight = wp->weight;  
  9.     struct monitor *m = wp->m;  
  10.     struct skynet_monitor *sm = m->m[id];  
  11.     skynet_initthread(THREAD_WORKER);  
  12.     struct message_queue * q = NULL;  
  13.     while (!m->quit) {  
  14.         q = skynet_context_message_dispatch(sm, q, weight); // 消息队列的派发和处理  
  15.         if (q == NULL) {  
  16.             if (pthread_mutex_lock(&m->mutex) == 0) {  
  17.                 ++ m->sleep;  
  18.                 // "spurious wakeup" is harmless,  
  19.                 // because skynet_context_message_dispatch() can be call at any time.  
  20.                 if (!m->quit)  
  21.                     pthread_cond_wait(&m->cond, &m->mutex);  
  22.                 -- m->sleep;  
  23.                 if (pthread_mutex_unlock(&m->mutex)) {  
  24.                     fprintf(stderr, "unlock mutex error");  
  25.                     exit(1);  
  26.                 }  
  27.             }  
  28.         }  
  29.     }  
  30.     return NULL;  
  31. }  

而调度线程做的事,就是不断从全局队列取出消息队列,处理消息队列的消息

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. // 处理服务消息  
  4. struct message_queue *   
  5. skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {  
  6.     if (q == NULL) {  
  7.         q = skynet_globalmq_pop();  
  8.         if (q==NULL)  
  9.             return NULL;  
  10.     }  
  11.   
  12.     uint32_t handle = skynet_mq_handle(q);  
  13.   
  14.     struct skynet_context * ctx = skynet_handle_grab(handle);  
  15.     if (ctx == NULL) {  
  16.         struct drop_t d = { handle };  
  17.         skynet_mq_release(q, drop_message, &d);  
  18.         return skynet_globalmq_pop();  
  19.     }  
  20.   
  21.     int i,n=1;  
  22.     struct skynet_message msg;  
  23.   
  24.     for (i=0;i<n;i++) {  
  25.         if (skynet_mq_pop(q,&msg)) {  
  26.             skynet_context_release(ctx);  
  27.             return skynet_globalmq_pop();  
  28.         } else if (i==0 && weight >= 0) {  
  29.             n = skynet_mq_length(q);  
  30.             n >>= weight;  
  31.         }  
  32.         int overload = skynet_mq_overload(q);  
  33.         if (overload) {  
  34.             skynet_error(ctx, "May overload, message queue length = %d", overload);  
  35.         }  
  36.   
  37.         skynet_monitor_trigger(sm, msg.source , handle);  
  38.   
  39.         if (ctx->cb == NULL) {  
  40.             skynet_free(msg.data);  
  41.         } else {  
  42.             dispatch_message(ctx, &msg); // 处理回调函数  
  43.         }  
  44.   
  45.         skynet_monitor_trigger(sm, 0,0);  
  46.     }  
  47.   
  48.     assert(q == ctx->queue);  
  49.     struct message_queue *nq = skynet_globalmq_pop();  
  50.     if (nq) {  
  51.         // If global mq is not empty , push q back, and return next queue (nq)  
  52.         // Else (global mq is empty or block, don't push q back, and return q again  
  53.         // (for next dispatch)  
  54.         skynet_globalmq_push(q); //TODO 为何不判断队列有无消息  
  55.         q = nq;  
  56.     }   
  57.     skynet_context_release(ctx);  
  58.   
  59.     return q;  
  60. }  

在dispatch_message完成消息的回调,看下这个函数:

[cpp] view plain copy
  1. // 处理回调函数  
  2. static void  
  3. dispatch_message(struct skynet_context *ctx, struct skynet_message *msg) {  
  4.     assert(ctx->init);  
  5.     CHECKCALLING_BEGIN(ctx)  
  6.     pthread_setspecific(G_NODE.handle_key, (void *)(uintptr_t)(ctx->handle));  
  7.     int type = msg->sz >> HANDLE_REMOTE_SHIFT;  
  8.     size_t sz = msg->sz & HANDLE_MASK;  
  9.     if (ctx->logfile) {  
  10.         skynet_log_output(ctx->logfile, msg->source, type, msg->session, msg->data, sz);  
  11.     }  
  12.     // 执行回调函数  
  13.     if (!ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz)) {  
  14.         skynet_free(msg->data);  
  15.     }   
  16.     CHECKCALLING_END(ctx)  
  17. }  

可以看出,每个服务都有一个消息队列,如果有新消息就会加到全局队列,等待skynet取出分发,回调处理若干条消息。然后,利用 ctx->cb 处理,完成事件驱动。


服务消息处理

下面以 example/simpledb.lua做说明,这是个典型的skynet服务。

[plain] view plain copy
  1. local skynet = require "skynet"  
  2. require "skynet.manager"    -- import skynet.register  
  3. local db = {}  
  4.   
  5. local command = {}  
  6.   
  7. function command.GET(key)  
  8.     return db[key]  
  9. end  
  10.   
  11. function command.SET(key, value)  
  12.     local last = db[key]  
  13.     db[key] = value  
  14.     return last  
  15. end  
  16.   
  17. skynet.start(function()  
  18.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  19.         local f = command[string.upper(cmd)]  
  20.         if f then  
  21.             skynet.ret(skynet.pack(f(...)))  
  22.         else  
  23.             error(string.format("Unknown command %s", tostring(cmd)))  
  24.         end  
  25.     end)  
  26.     skynet.register "SIMPLEDB"  
  27. end)  

服务的代码被loader加载后就会执行,这里就会执行到 skynet.start(func) ,完成服务的启动。

[plain] view plain copy
  1. -- skynet.lua    
  2.   
  3. function skynet.start(start_func)    
  4.     c.callback(skynet.dispatch_message)  -- 设置回调函数  
  5.     skynet.timeout(0, function()    
  6.         skynet.init_service(start_func)    
  7.     end)    
  8. end    
  9.   
  10. function skynet.dispatch_message(...)  
  11.     local succ, err = pcall(raw_dispatch_message,...)  -- 处理消息  
  12.   
  13.     -- 处理其他 skynet.fork 出来的协程  
  14.     while true do  
  15.         local key,co = next(fork_queue)  
  16.         if co == nil then  
  17.             break  
  18.         end  
  19.         fork_queue[key] = nil  
  20.         local fork_succ, fork_err = pcall(suspend,co,coroutine.resume(co))  
  21.         if not fork_succ then  
  22.             if succ then  
  23.                 succ = false  
  24.                 err = tostring(fork_err)  
  25.             else  
  26.                 err = tostring(err) .. "\n" .. tostring(fork_err)  
  27.             end  
  28.         end  
  29.     end  
  30.     assert(succ, tostring(err))  
  31. end  

前面也讨论了,c.XXX是调c函数实现的,从luaopen_skynet_core可以找到 callback的处理函数。

[cpp] view plain copy
  1. // lua-skynet.c     
  2.   
  3. static int  
  4. _callback(lua_State *L) {  
  5.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  6.     int forward = lua_toboolean(L, 2);  
  7.     luaL_checktype(L,1,LUA_TFUNCTION); // 取到上述c.callback(F)的F  
  8.     lua_settop(L,1);  
  9.     lua_rawsetp(L, LUA_REGISTRYINDEX, _cb); // 记录lua函数F到_cb这个索引位置  
  10.   
  11.     lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);  
  12.     lua_State *gL = lua_tothread(L,-1);  
  13.   
  14.     if (forward) {  
  15.         skynet_callback(context, gL, forward_cb);  
  16.     } else {  
  17.         skynet_callback(context, gL, _cb);  // 设置消息回调处理函数  
  18.     }  
  19.   
  20.     return 0;  
  21. }  
  22.   
  23. static int  
  24. _cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source,  
  25.    const void * msg, size_t sz) {  
  26.     lua_State *L = ud;  
  27.     int trace = 1;  
  28.     int r;  
  29.     int top = lua_gettop(L);  
  30.     if (top == 0) {  
  31.         lua_pushcfunction(L, traceback);  
  32.         lua_rawgetp(L, LUA_REGISTRYINDEX, _cb); // 取出_cb索引位置的lua函数  
  33.     } else {  
  34.         assert(top == 2);  
  35.     }  
  36.     lua_pushvalue(L,2);  
  37.   
  38.     lua_pushinteger(L, type);  
  39.     lua_pushlightuserdata(L, (void *)msg);  
  40.     lua_pushinteger(L,sz);  
  41.     lua_pushinteger(L, session);  
  42.     lua_pushinteger(L, source);  
  43.   
  44.     r = lua_pcall(L, 5, 0 , trace); // 执行lua函数,也就是 skynet.dispatch_message  
  45.   
  46.     if (r == LUA_OK) {  
  47.         return 0;  
  48.     }  
  49.     // 执行出错,就打印一些调试数据  
  50.     const char * self = skynet_command(context, "REG", NULL);  
  51.     switch (r) {  
  52.     case LUA_ERRRUN:  
  53.         skynet_error(context, "lua call [%x to %s : %d msgsz = %d] error : " KRED "%s" KNRM, source , self, session, sz, lua_tostring(L,-1));  
  54.         break;  
  55.     case LUA_ERRMEM:  
  56.         skynet_error(context, "lua memory error : [%x to %s : %d]", source , self, session);  
  57.         break;  
  58.     case LUA_ERRERR:  
  59.         skynet_error(context, "lua error in error : [%x to %s : %d]", source , self, session);  
  60.         break;  
  61.     case LUA_ERRGCMM:  
  62.         skynet_error(context, "lua gc error : [%x to %s : %d]", source , self, session);  
  63.         break;  
  64.     };  
  65.   
  66.     lua_pop(L,1);  
  67.   
  68.     return 0;  
  69. }  

紧接着回头看下skynet.timeout(0, function() skynet.init_service(start_func) end) 的处理

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.timeout(ti, func)  
  4.     local session = c.command("TIMEOUT",tostring(ti)) -- 超时处理  
  5.     assert(session)  
  6.     session = tonumber(session)  
  7.     local co = co_create(func) --  从协程池找到空闲的协程来执行这个函数  
  8.     assert(session_id_coroutine[session] == nil)  
  9.     session_id_coroutine[session] = co  
  10. end  

前面也提到 c.command 的处理,对于“TIMEOUT”的处理过程如下:

[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. static const char *  
  4. cmd_timeout(struct skynet_context * context, const char * param) {  
  5.     char * session_ptr = NULL;  
  6.     int ti = strtol(param, &session_ptr, 10); // 超时时间  
  7.     int session = skynet_context_newsession(context);  
  8.     skynet_timeout(context->handle, ti, session); // 处理超时功能  
  9.     sprintf(context->result, "%d", session);  
  10.     return context->result;  
  11. }  
看下skynet_timeout的处理

[cpp] view plain copy
  1. // skynet_timer.c  
  2.   
  3. int  
  4. skynet_timeout(uint32_t handle, int time, int session) {  
  5.     if (time == 0) {  
  6.         struct skynet_message message;  
  7.         message.source = 0;  
  8.         message.session = session;  
  9.         message.data = NULL;  
  10.         message.sz = PTYPE_RESPONSE << HANDLE_REMOTE_SHIFT;  
  11.   
  12.         // 如果time为0,把超时事件压到服务的消息队列,等待调度处理  
  13.         if (skynet_context_push(handle, &message)) {  
  14.             return -1;  
  15.         }  
  16.     } else {  
  17.         struct timer_event event;  
  18.         event.handle = handle;  
  19.         event.session = session;  
  20.         // time不为0,超时事件挂到时间轮上,等待超时处理  
  21.         timer_add(TI, &event, sizeof(event), time);   
  22.     }  
  23.   
  24.     return session;  
  25. }  
co_create 是从协程池找到空闲的协程来执行这个函数,没有空闲的协程则创建。
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function co_create(f)  
  4.     local co = table.remove(coroutine_pool)  
  5.     if co == nil then  
  6.         co = coroutine.create(function(...)  
  7.             f(...)  
  8.             while true do  
  9.                 f = nil  
  10.                 coroutine_pool[#coroutine_pool+1] = co  
  11.                 f = coroutine_yield "EXIT" -- a. yield 第一次获取函数  
  12.                 f(coroutine_yield()) -- b. yield 第二次获取函数参数,然后执行函数f  
  13.             end  
  14.         end)  
  15.     else  
  16.         -- resume 第一次让协程取到函数,就是 a点  
  17.         -- 之后再 resume 第二次传入参数,并执行函数,就是b点  
  18.         coroutine.resume(co, f)  
  19.     end  
  20.     return co  
  21. end  
顺道说下 skynet.dispatch的处理(还记得吧,在前面 skynet.start时调用的):
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.dispatch(typename, func)  
  4.     local p = proto[typename]  
  5.     if func then  
  6.         local ret = p.dispatch  
  7.         p.dispatch = func  -- 设置协议的处理函数  
  8.         return ret  
  9.     else  
  10.         return p and p.dispatch  
  11.     end  
  12. end  
这一步是设置proto[typename].dispatch,skynet.call/skynet.send的消息都会找到这个回调函数处理,如下:

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function raw_dispatch_message(prototype, msg, sz, session, source, ...)  
  4.     -- skynet.PTYPE_RESPONSE = 1, read skynet.h  
  5.     if prototype == 1 then  
  6.         local co = session_id_coroutine[session]  
  7.         if co == "BREAK" then  
  8.             session_id_coroutine[session] = nil  
  9.         elseif co == nil then  
  10.             unknown_response(session, source, msg, sz)  
  11.         else  
  12.             session_id_coroutine[session] = nil  
  13.             suspend(co, coroutine.resume(co, true, msg, sz))  
  14.         end  
  15.     else  
  16.         local p = proto[prototype]  
  17.         if p == nil then  
  18.             if session ~= 0 then  
  19.                 c.send(source, skynet.PTYPE_ERROR, session, "")  
  20.             else  
  21.                 unknown_request(session, source, msg, sz, prototype)  
  22.             end  
  23.             return  
  24.         end  
  25.         local f = p.dispatch -- 找到dispatch函数  
  26.         if f then  
  27.             local ref = watching_service[source]  
  28.             if ref then  
  29.                 watching_service[source] = ref + 1  
  30.             else  
  31.                 watching_service[source] = 1  
  32.             end  
  33.             local co = co_create(f)  
  34.             session_coroutine_id[co] = session  
  35.             session_coroutine_address[co] = source  
  36.             suspend(co, coroutine.resume(co, session,source, p.unpack(msg,sz, ...)))  
  37.         else  
  38.             unknown_request(session, source, msg, sz, proto[prototype].name)  
  39.         end  
  40.     end  
  41. end  


关于proto[typename],也作下简要的说明。可以看作是对数据的封装,方便不同服务间、不同节点间,以及前后端的数据通讯,不需要手动封包解包。默认支持lua/response/error这3个协议,还有log和debug协议,除了这几个,其他要自己调用skynet.register_protocol 注册

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.register_protocol(class)  
  4.     local name = class.name  
  5.     local id = class.id  
  6.     assert(proto[name] == nil)  
  7.     assert(type(name) == "string" and type(id) == "number" and id >=0 and id <=255)  
  8.     proto[name] = class  
  9.     proto[id] = class  
  10. end  
  11.   
  12. do  
  13.     local REG = skynet.register_protocol  
  14.   
  15.     REG {  
  16.         name = "lua",  
  17.         id = skynet.PTYPE_LUA,  
  18.         pack = skynet.pack,  
  19.         unpack = skynet.unpack,  
  20.     }  
  21.   
  22.     REG {  
  23.         name = "response",  
  24.         id = skynet.PTYPE_RESPONSE,  
  25.     }  
  26.   
  27.     REG {  
  28.         name = "error",  
  29.         id = skynet.PTYPE_ERROR,  
  30.         unpack = function(...) return ... end,  
  31.         dispatch = _error_dispatch,  
  32.     }  
  33. end  



服务之间的通讯

服务与服务之间互通消息,主要是这两个接口来通讯的:

1、skynet.send 消息发送

2、skynet.call 消息发送并返回

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function skynet.send(addr, typename, ...)  
  4.     local p = proto[typename]  
  5.     return c.send(addr, p.id, 0 , p.pack(...))  
  6. end  
  7.   
  8. function skynet.call(addr, typename, ...)  
  9.     local p = proto[typename]  
  10.     local session = c.send(addr, p.id , nil , p.pack(...))  
  11.     if session == nil then  
  12.         error("call to invalid address " .. skynet.address(addr))  
  13.     end  
  14.     return p.unpack(yield_call(addr, session))  
  15. end  
可以看出,skynet.call 对比多了返回值的处理,所以就以 skynet.call 做说明。

和前面的c.XXX一样,c.send的实现很快找到,如下:

[cpp] view plain copy
  1. // lua-skynet.c  
  2.   
  3. static int  
  4. _send(lua_State *L) {  
  5.     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));  
  6.     uint32_t dest = (uint32_t)lua_tointeger(L, 1);  
  7.     const char * dest_string = NULL; // 节点名字  
  8.     if (dest == 0) {  
  9.         if (lua_type(L,1) == LUA_TNUMBER) {  
  10.             return luaL_error(L, "Invalid service address 0");  
  11.         }  
  12.         dest_string = get_dest_string(L, 1);  
  13.     }  
  14.   
  15.     int type = luaL_checkinteger(L, 2);  
  16.     int session = 0;  
  17.     if (lua_isnil(L,3)) {  
  18.         type |= PTYPE_TAG_ALLOCSESSION;  
  19.     } else {  
  20.         session = luaL_checkinteger(L,3);  
  21.     }  
  22.   
  23.     int mtype = lua_type(L,4);  
  24.     switch (mtype) {  
  25.     case LUA_TSTRING: {  
  26.         size_t len = 0;  
  27.         void * msg = (void *)lua_tolstring(L,4,&len);  
  28.         if (len == 0) {  
  29.             msg = NULL;  
  30.         }  
  31.         if (dest_string) { // 以节点名字发消息  
  32.             session = skynet_sendname(context, 0, dest_string, type, session , msg, len);  
  33.         } else {   
  34.             session = skynet_send(context, 0, dest, type, session , msg, len);  
  35.         }  
  36.         break;  
  37.     }  
  38.     case LUA_TLIGHTUSERDATA: {  
  39.         void * msg = lua_touserdata(L,4);  
  40.         int size = luaL_checkinteger(L,5);  
  41.         if (dest_string) { // 以节点名字发消息  
  42.             session = skynet_sendname(context, 0, dest_string, type | PTYPE_TAG_DONTCOPY, session, msg, size);  
  43.         } else {  
  44.             session = skynet_send(context, 0, dest, type | PTYPE_TAG_DONTCOPY, session, msg, size);  
  45.         }  
  46.         break;  
  47.     }  
  48.     default:  
  49.         luaL_error(L, "skynet.send invalid param %s", lua_typename(L, lua_type(L,4)));  
  50.     }  
  51.     if (session < 0) {  
  52.         // send to invalid address  
  53.         // todo: maybe throw an error would be better  
  54.         return 0;  
  55.     }  
  56.     lua_pushinteger(L,session);  
  57.     return 1;  
  58. }  
这里看下 skynet_send 的实现吧:
[cpp] view plain copy
  1. // skynet_server.c  
  2.   
  3. int  
  4. skynet_send(struct skynet_context * context, uint32_t source, uint32_t destination , int type,   
  5.    int session, void * data, size_t sz) {  
  6.     if ((sz & MESSAGE_TYPE_MASK) != sz) {  
  7.         skynet_error(context, "The message to %x is too large", destination);  
  8.         if (type & PTYPE_TAG_DONTCOPY) {  
  9.             skynet_free(data);  
  10.         }  
  11.         return -1;  
  12.     }  
  13.     _filter_args(context, type, &session, (void **)&data, &sz); // 复制消息数据,获取session  
  14.   
  15.     if (source == 0) {  
  16.         source = context->handle;  
  17.     }  
  18.   
  19.     if (destination == 0) {  
  20.         return session;  
  21.     }  
  22.     if (skynet_harbor_message_isremote(destination)) { // 是否跨节点消息  
  23.         struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg));  
  24.         rmsg->destination.handle = destination;  
  25.         rmsg->message = data;  
  26.         rmsg->sz = sz;  
  27.         skynet_harbor_send(rmsg, source, session); // 发给其他节点,这里不讨论  
  28.     } else {  
  29.         struct skynet_message smsg;  
  30.         smsg.source = source;  
  31.         smsg.session = session;  
  32.         smsg.data = data;  
  33.         smsg.sz = sz;  
  34.   
  35.         // 发给其他服务,实际就是复制消息到队列,然后将消息队列加到全局队列  
  36.         if (skynet_context_push(destination, &smsg)) {  
  37.             skynet_free(data);  
  38.             return -1;  
  39.         }  
  40.     }  
  41.     return session;  
  42. }  
到这里,消息算是“发送”出去了,skynet.call 可能拿到返回的结果,也就是这一步:
[plain] view plain copy
  1. p.unpack(yield_call(addr, session))  
看下 yield_call 的实现,其实就是挂起协程,并把 “CALL”, session 返回给 coroutine.resume 者
[plain] view plain copy
  1. local coroutine_yield = coroutine.yield  
  2.   
  3. local function yield_call(service, session)  
  4.     watching_session[session] = service  
  5.     local succ, msg, sz = coroutine_yield("CALL", session)  
  6.     watching_session[session] = nil  
  7.     if not succ then  
  8.         error "call failed"  
  9.     end  
  10.     return msg,sz  
  11. end  

另外补充下,skynet还对coroutine.yield进行了改写,但这个函数的功能不变,还是挂起协程,等待 coroutine.resume

继续看前面的代码,这里有个问题,skynet.call 返回数据哪里来的?

实际上,还是走了服务间通讯的路子,看回 simpledb 的代码。

[plain] view plain copy
  1. -- simpledb.lua  
  2.   
  3. skynet.start(function()  
  4.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  5.         local f = command[string.upper(cmd)]  
  6.         if f then  
  7.             skynet.ret(skynet.pack(f(...))) -- 这里 skynet.ret 返回数据  
  8.         else  
  9.             error(string.format("Unknown command %s", tostring(cmd)))  
  10.         end  
  11.     end)  
  12.     skynet.register "SIMPLEDB"  
  13. end)  
看下skynet.ret 的处理,挂起返回数据。
[plain] view plain copy
  1. function skynet.ret(msg, sz)  
  2.     msg = msg or ""  
  3.     return coroutine_yield("RETURN", msg, sz)  
  4. end  
而skynet.dispatch 设置的回调函数,当回调触发时就会走到 raw_dispatch_message (前面有说明),继续执行就到了下面这步(函数较大,做了删节):
[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. function suspend(co, result, command, param, size)  
  4.     if not result then  
  5.         local session = session_coroutine_id[co]  
  6.         if session then -- coroutine may fork by others (session is nil)  
  7.             local addr = session_coroutine_address[co]  
  8.             if session ~= 0 then  
  9.                 -- only call response error  
  10.                 c.send(addr, skynet.PTYPE_ERROR, session, "")  
  11.             end  
  12.             session_coroutine_id[co] = nil  
  13.             session_coroutine_address[co] = nil  
  14.         end  
  15.         error(debug.traceback(co,tostring(command)))  
  16.     end  
  17.     if command == "CALL" then  -- skynet.call 挂起返回时操作  
  18.         session_id_coroutine[param] = co  
  19.     elseif command == "RETURN" then  -- skynet.ret 挂起返回时操作  
  20.         local co_session = session_coroutine_id[co]  
  21.         local co_address = session_coroutine_address[co]  
  22.         if param == nil or session_response[co] then  
  23.             error(debug.traceback(co))  
  24.         end  
  25.         session_response[co] = true  
  26.         local ret  
  27.         if not dead_service[co_address] then  
  28.             -- 把处理结果当作消息发给请求的服务  
  29.             ret = c.send(co_address, skynet.PTYPE_RESPONSE, co_session, param, size) ~= nil  
  30.             if not ret then  
  31.                 -- If the package is too large, returns nil. so we should report error back  
  32.                 c.send(co_address, skynet.PTYPE_ERROR, co_session, "")  
  33.             end  
  34.         elseif size ~= nil then  
  35.             c.trash(param, size)  
  36.             ret = false  
  37.         end  
  38.         return suspend(co, coroutine.resume(co, ret))  
  39.     else  
  40.         error("Unknown command : " .. command .. "\n" .. debug.traceback(co))  
  41.     end  
  42.     dispatch_wakeup() -- 处理所有需要恢复的协程  
  43.     dispatch_error_queue()    
  44. end  
到这里,服务间的通讯就讲完了。可以看出,服务间通讯基于消息,而消息数据也通过复制以避免数据读写加锁。


skynet服务的设计

统观整篇文章,不难发现:

每个skynet服务都是一个lua state,也就是一个lua虚拟机实例。而且,每个服务都是隔离的,各自使用自己独立的内存空间,服务之间通过发消息来完成数据交换。

架构图如下:


图片取自spartan1的skynet任务调度分析

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服务的缺陷


并发问题

这要从skynet一个服务霸占调度器的极端例子说起。

下面给出两个lua代码 main.lua 和 simpledb.lua,和一个配置文件 config 

[plain] view plain copy
  1. -- main.lua  
  2.   
  3. local skynet = require "skynet"  
  4.   
  5. skynet.start(function()  
  6.     print("Server start")  
  7.     skynet.newservice("simpledb")  
  8.   
  9.     -- 发消息给simpledb服务  
  10.     skynet.send("SIMPLEDB", "lua", "TEST")  
  11.     -- 死循环占据cpu  
  12.     local i = 0  
  13.     while true do  
  14.         i = i>100000000 and 0 or i+1  
  15.         if i==0 then  
  16.             print("I'm working")   
  17.         end  
  18.     end  
  19.     skynet.exit()  
  20. end)  
[plain] view plain copy
  1. -- simpledb.lua  
  2.   
  3. local skynet = require "skynet"  
  4. require "skynet.manager"    -- import skynet.register  
  5. local db = {}  
  6.   
  7. local command = {}  
  8.   
  9. function command.TEST()  
  10.     print("Simpledb test")  
  11.     return true  
  12. end  
  13.   
  14. skynet.start(function()  
  15.     print("Simpledb start")  
  16.     skynet.dispatch("lua", function(session, address, cmd, ...)  
  17.         local f = command[string.upper(cmd)]  
  18.         if f then  
  19.             skynet.ret(skynet.pack(f(...)))  
  20.         else  
  21.             error(string.format("Unknown command %s", tostring(cmd)))  
  22.         end  
  23.     end)  
  24.     skynet.register "SIMPLEDB"  
  25. end)  
配置文件 config
[plain] view plain copy
  1. root = "./"  
  2. thread = 1  
  3. logger = nil  
  4. logpath = "."  
  5. harbor = 1  
  6. address = "127.0.0.1:2526"  
  7. master = "127.0.0.1:2013"  
  8. start = "main"  
  9. bootstrap = "snlua bootstrap"  
  10. standalone = "0.0.0.0:2013"  
  11. luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"  
  12. lualoader = "lualib/loader.lua"  
  13. snax = root.."examples/?.lua;"..root.."test/?.lua"  
  14. cpath = root.."cservice/?.so"  

注意了,这里特地把 thread 设置为1,表示只启动一个调度线程。

现在,启动skynet执行我们的例子,结果如下:
[root@local skynet]# ./skynet config
[:01000001] LAUNCH logger 
[:01000002] LAUNCH snlua bootstrap
[:01000003] LAUNCH snlua launcher
[:01000004] LAUNCH snlua cmaster
[:01000004] master listen socket 0.0.0.0:2013
[:01000005] LAUNCH snlua cslave
[:01000005] slave connect to master 127.0.0.1:2013
[:01000004] connect from 127.0.0.1:41589 4
[:01000006] LAUNCH harbor 1 16777221
[:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526
[:01000005] Waiting for 0 harbors
[:01000005] Shakehand ready
[:01000007] LAUNCH snlua datacenterd
[:01000008] LAUNCH snlua service_mgr
[:01000009] LAUNCH snlua main
Server start
[:0100000a] LAUNCH snlua simpledb
Simpledb start
I'm working
I'm working
I'm working
可以看出,simpledb 没有机会处理TEST消息,一直是main模块占据着cpu。


为什么会出现这个情况?

这和skynet的调度机制有关。skynet使用全局队列保存了要调度的服务,调度算法是先来先服务。如果某个服务有新消息,就把这个服务加到调度队列中,然后等待调度线程调度。而skynet服务的调度切换依赖于协程的挂起,如果当前调度的服务没有主动挂起或退出,就会一直执行,不调度其他服务了。

这种机制的好处就是实现简单,有利于长作业,上下文切换较少,缺点就是并发效率低,而且像这种长作业的服务超过调度线程数量,就可能导致其他服务饿死。


内存隐患

细心的同学会发现,在服务处理新消息时,是通过创建新协程来处理的(见co_create),虽然协程会被重复利用,但在当前版本下,这种不断创建协程来消息的方式本身存在不稳定因素:
1、协程只增加不减少,意味过了某个并发高峰后内存不会降下来。
2、创建协程也有一定开销,容易触发GC,也占用内存,协程的数量规模不容易控制
3、如果解决第1点,最槽糕的情况是,不断要创建协程,不断要销毁协程,频繁触发gc

这里有一个极端的例子:

如果服务a不断给服务b发消息,但服务b的处理过程存在长时间挂起,这样,对于服务a发来的消息,服务b会不断创建协程去处理,就导致内存被大量占用的情况出现。

但是skynet也提供办法解决这个问题,方法是主动调用GC:

[plain] view plain copy
  1. skynet.send(service,"debug","GC")  

另外,有兴趣的同学,不妨看下实现代码:

[plain] view plain copy
  1. -- debug.lua  
  2.   
  3. return function (skynet, export)  
  4.   
  5. -- 处理 GC 消息  
  6. function dbgcmd.GC()    
  7.     export.clear()  -- export 是 skynet.debug 的传入参数  
  8.     collectgarbage "collect"  -- 执行GC  
  9. end  
  10.   
  11. local function _debug_dispatch(session, address, cmd, ...)  
  12.     local f = (dbgcmd or init_dbgcmd())[cmd]    -- lazy init dbgcmd  
  13.     f(...)  
  14. end  
  15.   
  16. skynet.register_protocol {  
  17.     name = "debug",  -- 注册处理服务收到的 "debug" 消息  
  18.     id = assert(skynet.PTYPE_DEBUG),  
  19.     pack = assert(skynet.pack),  
  20.     unpack = assert(skynet.unpack),  
  21.     dispatch = _debug_dispatch,  
  22. }  
  23. end  

而什么时候调用了 skynet.debug 呢?看这里

[plain] view plain copy
  1. -- skynet.lua  
  2.   
  3. local function clear_pool()  
  4.     coroutine_pool = {} -- 清空协程的引用  
  5. end  
  6.   
  7. -- debug设置回调处理函数  
  8. local debug = require "skynet.debug"  
  9. debug(skynet, {  
  10.     dispatch = skynet.dispatch_message,  
  11.     clear = clear_pool,  
  12.     suspend = suspend,  
  13. })  

就是说,但给服务发送GC消息时,就会清空协程池,随后执行底层GC接口。这样,不再有内容引用到这个协程,所以,协程会在GC时被清理。

至于协程只是挂起没有结束,为什么会被清理?

因为从协程池移走后,那些协程就变成了不可达的协程了,没有方法能 coroutine.resume 激活他们了,所以就会被gc掉。


同步问题

同步也是skynet存在的问题,当一个服务call其他服务时,当前协程会挂起,但是这个服务还可以接受并处理其他消息。如果多个协程改到同一个数据,你不做同步处理就无法确定这个数据会是多少。

这样的例子特别常见,比如,服务正当处理玩家login请求,刚好遇到call挂起,这时候又有新的请求到来,比如logout,服务就会转去处理logout消息。那玩家究竟是login,还是logout?

当然,同步问题也容易解决,加多一个state的标识和一个协程列表,操作执行时,将state置doing,其他协程判断state=doing时就将自己加到协程列表,然后 skynet.wait。在操作执行完后,重置state,然后遍历协程列表依次 skynet.wakeup(co) ,最后将协程列表置空。

最后语

天啊,我又写了一篇超长的文章,篇幅过长,通篇阅读的话可以很好锻炼耐心:)。  如果有什么建议和意见都可以评论,我看到就回复。另外补充下,skynet也在不断进步,以后很可能会解决上面提到的一些问题,希望skynet 越来越好。


参考:http://blog.csdn.net/mycwq/article/details/47379277
理就无法确定这个数据会是多少。

这样的例子特别常见,比如,服务正当处理玩家login请求,刚好遇到call挂起,这时候又有新的请求到来,比如logout,服务就会转去处理logout消息。那玩家究竟是login,还是logout?

当然,同步问题也容易解决,加多一个state的标识和一个协程列表,操作执行时,将state置doing,其他协程判断state=doing时就将自己加到协程列表,然后 skynet.wait。在操作执行完后,重置state,然后遍历协程列表依次 skynet.wakeup(co) ,最后将协程列表置空。

最后语

天啊,我又写了一篇超长的文章,篇幅过长,通篇阅读的话可以很好锻炼耐心:)。  如果有什么建议和意见都可以评论,我看到就回复。另外补充下,skynet也在不断进步,以后很可能会解决上面提到的一些问题,希望skynet 越来越好。


参考:http://blog.csdn.net/mycwq/article/details/47379277
0 0
原创粉丝点击