skynet学习笔记之服务管理 skynet_handle

前言

本文总结对于 skynet 服务管理器,skynet_handle.c 源文件的学习。

总览

在这里插入图片描述

设计思路

为每一个服务绑定一个永不重复(即使模块退出)的数字 id 作为其 handle

服务管理器完成的核心工作

存放所有服务对象,skynet 用服务对象的指针数组作为容器,限定了单进程内最大容纳服务数为 2^24 个,之所以是不是 2^32 次方,是因为高 8 位的 2 个字节用于存放用于远程服务的节点 id。初始容器大小为 4,然后当服务数超过容器当前大小时,按当前大小的 2 倍来扩容。每个服务有一个 id 来唯一标识,并且通过这个 id 能够找到该服务在容器中的位置,而 id 是自增的一个整数,容器位置是有限的,这就需要一个 hash 函数可以通过传入一个服务 id,返回服务在容器中的位置,且不能冲突。这个 hash 函数很简单,就是用 id 跟容器的当前容量来取余数。服务分为匿名服务和有名服务,区别是有名服务可以通过也可以通过名字获取服务 id。


#define DEFAULT_SLOT_SIZE 4        // 初始大小                                                                                                                                                              
#define MAX_SLOT_SIZE 0x40000000   // 名字数组最大容量,这个值远大于服务最大数量限制,有点奇怪                                                                                                                                                          
#define HASH_HANDLE(s, handle) (handle & (s->slot_size-1)) // hash 函数

struct handle_name {                                                                                                                                                                         
    char * name;        // 服务名字 调用单独接口为服务自定义名字,默认是没有名字的,即匿名服务                                                                                                                                                                     
    uint32_t handle;                                                                                                                                                                         
};

struct handle_storage {                                                                                                                                                                      
    struct rwlock lock;                                                                                                                                                                      
                                                                                                                                                                                             
    uint32_t harbor;                /* toby@2022-03-02): 每个进程一个8bit的标志 用作集群节点 */                                                                                              
    uint32_t handle_index;          /* toby@2022-03-02): 每个服务对应一个24位范围内的id 0 被保留*/                                                                                           
    int slot_size;                  /* toby@2022-03-02): 可以容纳的服务数 */                                                                                                                 
    struct skynet_context ** slot;  /* toby@2022-03-02): 服务的指针数组 */                                                                                                                   
                                                                                                                                                                                             
    int name_cap;                                                                                                                                                                            
    int name_count;                                                                                                                                                                          
    struct handle_name *name;       /* toby@2022-03-04): 名字数组,按字符串大小排序 */                                                                                                       
};

static struct handle_storage *H = NULL; // 管理器单例对象,启动时会初始化

接口

  1. 服务注册接口,在创建好一个服务后,将服务对象注册(存放)到管理器中,返回管理器分配的服务 id。

    uint32_t skynet_handle_register(struct skynet_context *);

    uint32_t                                                                                                                                                                                     
    skynet_handle_register(struct skynet_context *ctx) {                                                                                                                                         
        struct handle_storage *s = H;   // 拿到管理器单例对象                                                                                                                                                       
                                                                                                                                                                                                 
        rwlock_wlock(&s->lock);         // 写锁保护,因为将要对容器数据进行改动                                                                                                                                                         
                                                                                                                                                                                                 
        for (;;) {                      // 死循环来分配一个空闲的数组位置,拿到了会主动退出                                                                                                                                                         
            int i;  // 查找次数,超过当前容器大小则说明容器已满,需要扩容                                                                                                                                                         
            uint32_t handle = s->handle_index; // 当前自增 id 的值                                                                                                                                                   
            for (i=0;i<s->slot_size;i++,handle++) { // 最多循环遍历完当前容器中所有位置                                                                                                                                              
                if (handle > HANDLE_MASK) {    // 自增 id 超过最大值则重置为 1 
                                               // 可以重置是因为服务退出工作后,分配给他的 id 被回收了                                                                                                              
                    // 0 is reserved           // 0 这个 id 是保留给系统使用的                                                                                                                                                  
                    handle = 1;                                                                                                                                                                  
                }                                                                                                                                                                                
                int hash = HASH_HANDLE(s, handle);  // 取到 id 对应的数组位置                                                                                                                                             
                if (s->slot[hash] == NULL) {        // 如果该位置没有存放服务即是找到了可用的 id                                                                                                                                             
                    s->slot[hash] = ctx;            // 将服务对象的地址存放到管理器中                                                                                                                                                  
                    s->handle_index = handle + 1;   // id 自增                                                                                                                                             
                                                                                                                                                                                                 
                    rwlock_wunlock(&s->lock);       // 数据改变操作结束就可以解锁了                                                                                                                                             
                                                                                                                                                                                                 
                    handle |= s->harbor;            // 为 id 添加远程用的节点编号头,高 8 位                                                                                                                                            
                    return handle;                                                                                                                                                               
                }                                                                                                                                                                                
            }                                                                                                                                                                                    
            //(toby@2022-03-01): 位置不够用了 按 2 倍扩展                                                                                                                                            
            assert((s->slot_size*2 - 1) <= HANDLE_MASK);  // 不能超过最大限制                                                                                                                                        
            struct skynet_context ** new_slot = skynet_malloc(s->slot_size * 2 * sizeof(struct skynet_context *));     // 分配一个新的内存空间                                                                          
            memset(new_slot, 0, s->slot_size * 2 * sizeof(struct skynet_context *)); // 初始化该内存空间,相当于所有位置存放 NULL                                                                                                             
            for (i=0;i<s->slot_size;i++) { // 将所有服务对象的地址复制到新的数组中                                                                                                                                                       
                int hash = skynet_context_handle(s->slot[i]) & (s->slot_size * 2 - 1); // 因为容器的大小变了,所以需要为所有 id 重新 hash 计算一下新的位置                                                                                                           
                assert(new_slot[hash] == NULL);                                                                                                                                                  
                new_slot[hash] = s->slot[i];                                                                                                                                                     
            }                                                                                                                                                                                    
            skynet_free(s->slot);  // 释放旧的数组内存                                                                                                                                                              
            s->slot = new_slot;    // 绑定新的数组                                                                                                                                                              
            s->slot_size *= 2;                                                                                                                                                                   
        }                                                                                                                                                                                        
    }
    
  2. 通过服务 id 获取服务对象

    struct skynet_context * skynet_handle_grab(uint32_t handle);

    struct skynet_context *                                                                                                                                                                      
    skynet_handle_grab(uint32_t handle) {                                                                                                                                                        
        struct handle_storage *s = H;                                                                                                                                                            
        struct skynet_context * result = NULL;                                                                                                                                                   
                                                                                                                                                                                                 
        rwlock_rlock(&s->lock);  // 读锁保护,因为这里只是获取服务对象,并不会修改管理器本身数据                                                                                                                                                                
                                                                                                                                                                                                 
        uint32_t hash = HASH_HANDLE(s, handle);                                                                                                                                                  
        struct skynet_context * ctx = s->slot[hash];                                                                                                                                             
        if (ctx && skynet_context_handle(ctx) == handle) {  // 检查 handle 是否一致,避免通过过期 id 访问                                                                                                                                     
            result = ctx;                                                                                                                                                                        
            skynet_context_grab(result); // 服务本身具有引用计数,这里计数 +1,使用完之后会调用减少计数的接口,如果计数归零,则服务将被释放                                                                                                                                                         
        }                                                                                                                                                                                        
                                                                                                                                                                                                 
        rwlock_runlock(&s->lock);                                                                                                                                                                
                                                                                                                                                                                                 
        return result;                                                                                                                                                                           
    }
    
  3. 回收句柄(服务id),销毁服务

    int skynet_handle_retire(uint32_t handle); // 回收单个服务
    void skynet_handle_retireall(); // 回收所有服务

    int                                                                                                                                                                                          
    skynet_handle_retire(uint32_t handle) {                                                                                                                                                      
        int ret = 0;                                                                                                                                                                             
        struct handle_storage *s = H;                                                                                                                                                            
                                                                                                                                                                                                 
        rwlock_wlock(&s->lock);      // 写锁保护,要回收服务,自然会改动管理器数据                                                                                                                                                           
                                                                                                                                                                                                 
        uint32_t hash = HASH_HANDLE(s, handle);                                                                                                                                                  
        struct skynet_context * ctx = s->slot[hash];   // 先拿到指定服务                                                                                                                                          
                                                                                                                                                                                                 
        if (ctx != NULL && skynet_context_handle(ctx) == handle) {  // 检查一致性                                                                                                                             
            s->slot[hash] = NULL;   // 从容器中移除服务对象                                                                                                                                                             
            ret = 1; 
            /*如果该服务是有名服务,还需要释放对象的名字结构,这里所有服务释放都会遍历名字数组,可以考虑优化,权衡设置标志的内存占用和遍历数组的耗时,且需结合具体场景是否有频繁释放服务的需求*/                                                                                                                                                                            
            int i;                                                                                                                                                                               
            int j=0, n=s->name_count;                                                                                                                                                            
            for (i=0; i<n; ++i) {                                                                                                                                                                
                if (s->name[i].handle == handle) {                                                                                                                                               
                    skynet_free(s->name[i].name);  // 找到该服务,释放名字结构占用的内存,直接进入下一轮循环,下一轮就会存在 j < i 的情况了,达到前移数据的目的                                                                                                                                              
                    continue;                                                                                                                                                                    
                } else if (i!=j) {                                                                                                                                                               
                    /* toby@2022-03-02): 后面的值都向前移动 */                                                                                                                                   
                    s->name[j] = s->name[i];                                                                                                                                                     
                }                                                                                                                                                                                
                ++j;                                                                                                                                                                             
            }                                                                                                                                                                                    
            s->name_count = j;                                                                                                                                                                   
        } else {                                                                                                                                                                                 
            ctx = NULL;                                                                                                                                                                          
        }                                                                                                                                                                                        
                                                                                                                                                                                                 
        rwlock_wunlock(&s->lock);                                                                                                                                                                
                                                                                                                                                                                                 
        if (ctx) {                                                                                                                                                                               
            // release ctx may call skynet_handle_* , so wunlock first.   
            /*这里注释的意思是,服务自定义的 release 过程中可能会调用到管理器的某些接口,而管理器的接口基本上都会先请求锁保护,如果不先解锁,这里可能出现死锁。 但是,正常写法就应该是在做完对管理器数据修改的逻辑之后就立即解锁,如果在函数末尾才解锁,本就于理不通。 所以这个注释可有可无,可能是改 bug 留下的*/                                                                                                                       
            skynet_context_release(ctx);   // 减少服务的引用计数                                                                                                                                                      
        }                                                                                                                                                                                        
                                                                                                                                                                                                 
        return ret;                                                                                                                                                                              
    }
    
  4. 给服务命名,有名服务通常作为工具服务,只有一个对象,能直接通过名字来查询服务 id。例如日志服务 “logger” 。

    const char * skynet_handle_namehandle(uint32_t handle, const char *name);

    static const char *                                                                                                                                                                          
    _insert_name(struct handle_storage *s, const char * name, uint32_t handle) {                                                                                                                 
        int begin = 0;                                                                                                                                                                           
        int end = s->name_count - 1;                                                                                                                                                             
        while (begin<=end) {                                                                                                                                                                     
            int mid = (begin+end)/2;                                                                                                                                                             
            struct handle_name *n = &s->name[mid];                                                                                                                                               
            int c = strcmp(n->name, name);                                                                                                                                                       
            if (c==0) {  // 相等 发现名字已存在                                                                                                                                                                        
                return NULL;                                                                                                                                                                     
            }                                                                                                                                                                                    
            if (c<0) {   // 中间位置的名字小于当前名字,从后半段继续找位置                                                                                                                                                                        
                begin = mid + 1;                                                                                                                                                                 
            } else {     // 中间位置的名字大于当前名字,从前半段继续找位置                                                                                                                                                                        
                end = mid - 1;                                                                                                                                                                   
            }                                                                                                                                                                                    
        }                                                                                                                                                                                        
        char * result = skynet_strdup(name);                                                                                                                                                     
                                                                                                                                                                                                 
        _insert_name_before(s, result, handle, begin);  // 插入 begin 所在位置                                                                                                                                         
                                                                                                                                                                                                 
        return result;                                                                                                                                                                           
    }                                                                                                                                                                                            
                                                                                                                                                                                                 
    const char *                                                                                                                                                                                 
    skynet_handle_namehandle(uint32_t handle, const char *name) {                                                                                                                                
        rwlock_wlock(&H->lock);                                                                                                                                                                  
                                                                                                                                                                                                 
        const char * ret = _insert_name(H, name, handle);  // 用二分法插入新创建的名字                                                                                                                                      
                                                                                                                                                                                                 
        rwlock_wunlock(&H->lock);                                                                                                                                                                
                                                                                                                                                                                                 
        return ret;                                                                                                                                                                              
    }
    
  5. 通过名字查找服务 id

    uint32_t skynet_handle_findname(const char * name);

    uint32_t                                                                                                                                                                                     
    skynet_handle_findname(const char * name) {  // 二分查找                                                                                                                                           
        struct handle_storage *s = H;                                                                                                                                                            
                                                                                                                                                                                                 
        rwlock_rlock(&s->lock);                                                                                                                                                                  
                                                                                                                                                                                                 
        uint32_t handle = 0;                                                                                                                                                                     
                                                                                                                                                                                                 
        int begin = 0;                                                                                                                                                                           
        int end = s->name_count - 1;                                                                                                                                                             
        while (begin<=end) {                                                                                                                                                                     
            int mid = (begin+end)/2;                                                                                                                                                             
            struct handle_name *n = &s->name[mid];                                                                                                                                               
            int c = strcmp(n->name, name);                                                                                                                                                       
            if (c==0) {                                                                                                                                                                          
                handle = n->handle;                                                                                                                                                              
                break;                                                                                                                                                                           
            }                                                                                                                                                                                    
            if (c<0) {                                                                                                                                                                           
                begin = mid + 1;                                                                                                                                                                 
            } else {                                                                                                                                                                             
                end = mid - 1;                                                                                                                                                                   
            }                                                                                                                                                                                    
        }                                                                                                                                                                                        
                                                                                                                                                                                                 
        rwlock_runlock(&s->lock);                                                                                                                                                                
                                                                                                                                                                                                 
        return handle;                                                                                                                                                                           
    }
    

结语

用读写锁的原因是,读操作(获取服务)的频率通常是远高于写操作(注册服务、回收服务)的。该读写锁的实现是基于自旋锁的。skynet 内的锁基本都是在自旋锁的基础上来实现的,猜想这样做是为了让工作线程尽量不让出 cpu,减少 cpu 切换带来的消耗,充分利用多核优势。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tobybo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值