当前位置: 首页 > news >正文

义乌企业网站建设为网站开发

义乌企业网站建设,为网站开发,重庆公共资源交易中心,如何在手机上建立自己的网站文章目录 前言雪花算法LRU 算法缓存模块数据库测试逻辑 前言 本节实现了 分布式 ID 生成系统#xff0c;采用雪花算法实现唯一 ID#xff1b;实现缓存架构#xff0c;采用 LRU #xff08;最近最少使用#xff09;算法。 雪花算法 分布式 ID 生成算法的有很多种#x… 文章目录 前言雪花算法LRU 算法缓存模块数据库测试逻辑 前言 本节实现了 分布式 ID 生成系统采用雪花算法实现唯一 ID实现缓存架构采用 LRU 最近最少使用算法。 雪花算法 分布式 ID 生成算法的有很多种Twitter 的雪花算法SnowFlake就是其中经典的一种。 SnowFlake算法生成id的结果是一个64bit大小的整数它的结构如下图 1位不用。二进制中最高位为1的都是负数但是我们生成的 id 一般都使用正整数所以这个最高位固定是0 41位用来记录时间戳毫秒。 41位可以表示 2 41 − 1 2^{41}-1 241−1 个数字如果只用来表示正整数计算机中正数包含0可以表示的数值范围是0 至 2 41 − 1 2^{41}-1 241−1。41位可以表示 2 41 − 1 2^{41}-1 241−1个毫秒的值转化成单位年则是 ( 2 41 − 1 ) / ( 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 ) 69 (2^{41}-1) / (1000 * 60 * 60 * 24 * 365) 69 (241−1)/(1000∗60∗60∗24∗365)69年 10位用来记录工作机器id。可以部署在 2 10 1024 2^{10} 1024 2101024 个节点包括5位 datacenterId 和5位 workerId 5位bit可以表示的最大正整数是 2 5 − 1 31 2^{5}-1 31 25−131即可以用0、1、2、3、…31这32个数字来表示不同的 datecenterId 或 workerId 12位序列号用来记录同毫秒内产生的不同 id。12位可以表示的最大正整数是 2 12 − 1 4095 2^{12}-1 4095 212−14095即可以用 0、1、2、3、…4094这4095个数字来表示同一机器同一时间截毫秒内产生的4095个 ID 序号 SnowFlake 算法的优点 生成 ID 时不依赖于数据库完全在内存生成高性能高可用。 容量大每秒可生成几百万ID。 SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢同一毫秒的ID数量 1024 * 4096 4194304 所有生成的id按时间趋势递增后续插入数据库的索引树的时候性能较高。 整个分布式系统内不会产生重复id因为有datacenterId和workerId来做区分 SnowFlake 算法的缺点 依赖于系统时钟的一致性。如果某台机器的系统时钟回拨有可能造成ID冲突或者ID乱序。 还有在启动之前如果这台机器的系统时间回拨过那么有可能出现ID重复的危险。 以上参考cloudyan/snowflake 在本项目中与之有异同之处。采用 39 位表示时间戳12 位表示机器 id12位表示序列号。 实现后的雪花算法 4096 个服务单个服务 10 毫秒内可以生成 4096 个 ID支持时间跨服 174 年 通过自定义雪花算法生成 id 的服务即表示为机器 id则可以实现至多 2 12 4096 2^{12}4096 2124096 个服务。由于 skynet 内部时钟精度是 10ms所以在同一时间戳10ms内生成 id 的序列号依此递增至多 2 12 4096 2^{12}4096 2124096 个。39 位用于表示时间戳 2 39 / ( 100 ∗ 3600 ∗ 24 ∗ 365 ) 174 2^{39}/(100*3600*24*365)174 239/(100∗3600∗24∗365)174 年。 雪花算法服务的配置文件 -- snowflake conf snowflake_begin 1 snowflake_end 2 snowflake_start_date 2003-01-21lualib/snowflake.lua local skynet require skynetlocal _M {} local snowflake_service {} -- service: begin - end local max_service_id local cur_service_id 0-- 获取一个 snowflake 服务 local function get_snowflake_service()cur_service_id cur_service_id 1if cur_service_id max_service_id then cur_service_id 1end return snowflake_service[cur_service_id] end -- 对外接口雪花 id 算法生成 function _M.snowflake()local addr get_snowflake_service()return skynet.call(addr, lua, snowflake) end skynet.init(function()skynet.uniqueservice(snowflake)local snowflake_begin tonumber(skynet.getenv(snowflake_begin)) or 1local snowflake_end tonumber(skynet.getenv(snowflake_end)) or 10assert(snowflake_begin snowflake_end, snowflake_begin or snowflake_end error)local i 0for id snowflake_begin, snowflake_end do i i 1local service_name string.format(.snowflake_%s, id)snowflake_service[i] skynet.localname(service_name) -- 返回同一进程内用 register 注册的具名服务的地址。end max_service_id i end)return _M 可以看到服务采用主从架构通过简单的轮询算法负载均衡。生成的服务数量由 snowflake_begin 、snowflake_end 配置。 我们再来看 snowflake 服务代码 service/snowflake.lua -------- master ---------- 启动主节点服务创建多个从节点服务 skynet.start(function()local snowflake_begin tonumber(skynet.getenv(snowflake_begin)) or 1local snowflake_end tonumber(skynet.getenv(snowflake_end)) or 10assert(snowflake_begin snowflake_end, snowflake_begin or snowflake_end error)for id snowflake_begin, snowflake_end do skynet.newservice(SERVICE_NAME, slave, id)end skynet.register(.snowflake) end)主节点仅负责启动多个从节点服务通过 skynet.newservice(SERVICE_NAME, slave, id)启动并传入参数参数 id 则用于后续标识机器的 id。 从节点用于提供生成 ID 的雪花算法并维护当前这个从服务的时间戳定时每 3s 保存到文件中。 -- 将 2000-01-01 形式日期转为时间戳 local function parse_date(date)local year, month, day date:match((%d)-(%d)-(%d))return os.time({year year, month month, day day}) end local start_date skynet.getenv(snowflake_start_date) or 2000-01-01 local START_TIMESTAMP parse_date(start_date)-- 每一部分占用位数 local TIME_BIT 39 -- 时间占用位数 local SEQUENCE_BIT 12 -- 序列号占用位数 local MACHINE_BIT 12 -- 机器标识占用位数-- 每一部分最大值 local MAX_TIME 1 TIME_BIT -- 时间最大值 ((1 39) / 365 * 24 * 3600 * 100) 174 year local MAX_SEQUENCE 1 SEQUENCE_BIT -- 序列号最大值 (4096) local MAX_MACHINE 1 MACHINE_BIT -- 机器标识最大值 (4096)-- 每一部分向左的偏移 local LEFT_MACHINE SEQUENCE_BIT -- 12 local LEFT_TIME SEQUENCE_BIT MACHINE_BIT -- 24-- snowflake 接口 function CMD.snowflake()local cur get_cur_timestamp()if cur last_timestamp then error(Clock moved backwards. Refusing to generate id)end if cur last_timestamp then -- 相同 10ms 内序列号自增sequence (sequence 1) MAX_SEQUENCEif sequence 0 then cur get_next_timestamp()end else -- 不同 10ms 内序列号置 0sequence 0end last_timestamp curreturn (cur - START_TIMESTAMP) LEFT_TIME | slave_id LEFT_MACHINE | sequence end 从代码中可以看出生成 id 的时间戳是相较于配置文件中 snowflake_start_date 起始的。并且在相同 10ms 内序列号自增如果序列号超出 12 位的最大值那么强制变为下一个 10ms 的时间戳。 雪花算法 snowflake实际返回的 id(cur - START_TIMESTAMP) LEFT_TIME | slave_id LEFT_MACHINE | sequence即分别将时间戳、机器 id、序列号向左偏移到二进制对应的位置返回。 -- 10ms local function get_cur_timestamp()return math.floor(skynet.time() * 100) end local function get_next_timestamp()local cur get_cur_timestamp()while cur last_timestamp do cur get_cur_timestamp()end return cur end skynet.time通过 starttime 和 now 计算出当前 UTC 时间单位是秒, 精度是msget_cur_timestamp 获取当前时间戳函数控制了 10ms 为一个单位。 完整代码service/snowflake LRU 算法 缓存模块使用最经典的 LRU 算法实现淘汰策略是最近最少使用的数据。详细的介绍参考百度百科 LRU 算法在 leetcode 上也有相应试题我们参考实现自己的 LRU 算法。 Go 语言版本 type entry struct {key int value int }type LRUCache struct {ll *list.Listcache map[int]*list.ElementmaxBytes int nBytes int }func Constructor(capacity int) LRUCache {lru : LRUCache{}lru.ll list.New()lru.cache make(map[int]*list.Element)lru.maxBytes capacitylru.nBytes 0return lru }func RemoveOldest(this *LRUCache) {ele : this.ll.Back()if ele ! nil {this.ll.Remove(ele)delete(this.cache, ele.Value.(*entry).key)this.nBytes - 1} }func (this *LRUCache) Get(key int) int {if ele, ok : this.cache[key]; ok {this.ll.MoveToFront(ele)return ele.Value.(*entry).value}return -1 }func (this *LRUCache) Put(key int, value int) {if ele, ok : this.cache[key]; ok {this.ll.MoveToFront(ele)ele.Value entry{key, value}} else {ele : this.ll.PushFront(entry{key, value})this.cache[key] ele this.nBytes 1 }for this.maxBytes this.nBytes this.maxBytes ! 0 {RemoveOldest(this)} } /*** Your LRUCache object will be instantiated and called as such:* obj : Constructor(capacity);* param_1 : obj.Get(key);* obj.Put(key,value);*/根据上述 Go 语言实现的 LRU需要一个双向链表模块还有一个哈希表。哈希表在 lua 中实际就是 table那么下面首先实现双向链表结构。 lualib/list.lua local list {} local mt { __index list }-- entry { key, value, next, prev }function list.New()local self setmetatable({}, mt)self.size 0self.head {}self.tail {} self.head.next self.tail self.tail.prev self.head return self end function list.Back(self)if self.size ~ 0 then return self.tail.prev end return nil end -- insert entry after at; list.size; return entry local function insert(self, entry, at)entry.prev at entry.next at.next entry.prev.next entry entry.next.prev entryself.size self.size 1return entry end function list.PushFront(self, entry)return insert(self, entry, self.head) end -- move entry after at; local function move(self, entry, at)if entry at then return end entry.prev.next entry.next entry.next.prev entry.preventry.prev atentry.next at.nextentry.prev.next entryentry.next.prev entry end function list.MoveToFront(self, entry)if entry self.head or self.size 1 then return end move(self, entry, self.head) end function list.Remove(self, entry)if entry nil then return endentry.prev.next entry.nextentry.next.prev entry.preventry.next, entry.prev, entry.key, entry.value nil, nil, nil, nilentry nil self.size self.size - 1 end return list 设计的对外接口仅和 Go 语言代码一致满足后续的 LRU 算法模块实现这里不再过多赘述双向列表的实现。 下面来看 LRU 模块设计lru.lua 主要实现了 new、set、get 三个方法 function lru.new(size, on_remove)local self setmetatable({}, mt)self.list list.New()self.cache {} self.capacity size self.size 0self.on_remove on_removereturn self end function lru.set(self, key, value, force)local entry self.cache[key]if entry then entry.value valueself.list:MoveToFront(entry)else local entry {key key,value value}self.list:PushFront(entry)self.cache[key] entryself.size self.size 1end while true do if self.size self.capacity and not force thenlru_remove(self)elsebreak end end end function lru.get(self, key)local entry self.cache[key]if entry nil then return end self.list:MoveToFront(entry)return entry.value end lru 模块不仅要有 list 双向链表结构cache 哈希表结构capacity 缓存容量上限size 当前数据量还需要一个 on_remove 回调方法。用于当缓存结构移除数据时执行的该数据回调操作。由使用者进行注册并且一个 lru 模块所有数据共享这一个回调方法即执行的回调操作是相同的。例如后续实现的缓存模块的 lru 结构回调方法在删除改数据时后都会判断一下这个数据是否还有引用还有则继续插入缓存。 还注意到set 方法提供了一个额外参数 force。可以强制无视当前 lru 容量进行插入缓存数据。 完整代码lualib/lru 通过 debug_console 对 lru 模块进行测试 设置 lru 容量是 2插入数据 [1, 1][2, 2]输出 [[2, 2][1, 1]]。 获取数据 [1]输出 [[1, 1][2, 2]]。 插入数据 [3, 3]输出 [[3, 3][1, 1]]。 不做过多演示测试代码参考test/test_lru 缓存模块 一般游戏逻辑都不直接操作数据库而是直接操作内存数据库也称为数据缓存。游戏可以使用 redis 作为内存数据库也可以和本项目一样实现一个缓存服务。 缓存模块库lualib/cache.lua local skynet require skynetlocal _M {}local cached function _M.call_cached(func_name, mod, sub_mod, id, ...)return skynet.call(cached, lua, run, func_name, mod, sub_mod, id, ...) endskynet.init(function()cached skynet.uniqueservice(cached) end)return _M 对外接口方法 call_cached func_name 远程调用的函数名mod 为模块名一个 cached 负责加载多个模块数据sub_mod 子模块名一个模块下面会有多个子模块数据id 数据的唯一 ID例如 user 模块数据id 对应玩家的 uid... 变参为函数的其他参数 缓存服务 service/cached.lua该服务的管理模块 module/cached/mng.lua其余还有不同逻辑模块例如用户模块 module/cached/user.lua 等。 service/cached.lua local skynet require skynet local mng require cached.mng local user require cached.userlocal CMD {}function CMD.run(func_name, mod, sub_mod, id, ...)local func mng.get_func(mod, sub_mod, func_name)local cache mng.load_cache(mod, sub_mod, id)return func(id, cache, ...) end function CMD.SIGHUP()logger.info(SERVICE_NAME, SIGHUP to save db. Doing.)mng.do_save_loop()logger.info(SERVICE_NAME, SIGHUP to save db. Down.) endskynet.start(function()skynet.dispatch(lua, function(_, _, cmd, ...)local f assert(CMD[cmd])skynet.ret(skynet.pack(f(...)))end)mng.init()user.init() end)缓存服务 cached 主要提供两个接口run 用于执行远程函数SIGHUP 用于接受关服信号执行一次脏数据落盘。 还记得在日志服务中 log.lua我们注册了系统消息 PTYPE_SYSTEM -- 捕捉sighup信号kill -l 执行安全关服逻辑 skynet.register_protocol {name SYSTEM, id skynet.PTYPE_SYSTEM, unpack function(...) return ... end,dispatch function()-- 执行必要服务的安全退出操作local cached skynet.localname(.cached)if cached then skynet.call(cached, lua, SIGHUP)end skynet.sleep(100)skynet.abort()end }在外部停止服务器时这里就执行一次关服保存数据操作通知缓存模块进行脏数据落盘。如何更好的更安全的退出 skynet参考https://github.com/cloudwu/skynet/issues/288 服务的另一个接口run 执行远程函数首先通过 get_func 函数接受 mod、sub_mod、func_name 三个参数组成内部的函数名称对应获取要执行的函数。在通过 load_cache加载该函数要操作的对象内部先去查找缓存缓存未命中则会从数据库加载。缓存表中数据字段以 _key 为索引数据对象由 mod 和 id 构成唯一 _key。 下面来看缓存的管理模块这个模块是缓存操作的核心管理了所有的缓存相关处理逻辑。 module/cached/mng.lua local _M {} local CMD {} local cache_list -- 缓存列表 local dirty_list -- 脏数据列表 local load_queue -- 数据加载队列 local mongo_col -- 数据库操作对象 local init_cb_list {} -- 数据加载后的初始化函数列表-- 缓存移除回调函数 local function cache_remove_cb(key, cache)-- 数据脏或仍有引用继续存入缓存if cache._ref 0 or dirty_list[cache] then cache_list:set(key, cache, true)end endfunction _M.init()init_db()local max_cache_cnt tonumber(skynet.getenv(cache_max_cnt)) or 10240local save_interval tonumber(skynet.getenv(cache_save_interval)) or 60cache_list lru.new(max_cache_cnt, cache_remove_cb)dirty_list {}load_queue queue()timer.timeout_repeat(save_interval, _M.do_save_loop) end先来看基础变量和模块的初始化。 cache_list 实际上是 lru用于存储缓存的结构dirty_list 脏数据列表load_cache 加载数据后就会将数据标记脏数据do_save_loop 定时保存脏数据就会取消标记load_queue 数据加载队列使用了 skynet.queue 用于缓存未命中时从数据库中加载数据使用防止加载数据函数重入的。因为操作数据库是一个阻塞 API会挂起当前协程服务会继续响应其他消息可能造成时序问题。可以参考官方 wikiCriticalSectionmongo_col 数据库表对象初始化模块前会先 init_db 初始化数据库 创建 cache_list 对象时指定了当前缓存结构的数据移除回调函数 cache_remove_cb数据还有引用或该数据还是脏数据 cache._ref 0 or dirty_list[cache] 那么重新加入缓存列表中 cache_list:set(key, cache, true)。这里 lru 的 set 方法第三个参数为 true 表示允许缓存列表临时超出上限避免死循环执行 cache_remove_cb 回调函数。 在最后我们启动了一个定时器save_interval 时间间隔执行一次 do_save_loop 进行脏数据落盘。 -- 缓存同步到数据库 local function cache_save_db(key, cache)local data {[$set] cache}local xpcallok, updateok, err, ret xpcall(mongo_col.safe_update, debug.traceback, mongo_col, { _key key }, data, true, false)if not xpcallok or not (updateok and ret and ret.n 1) then end end-- 脏的缓存数据写到数据库 function _M.do_save_loop()for key, _ in pairs(dirty_list) dolocal cache cache_list:get(key)if cache then cache_save_db(key, cache)enddirty_list[key] nil end end实际每轮保存数据就是去遍历当前的 dirty_list 脏数据列表执行 cache_save_db 将缓存 update 到 Mongodb 数据库。 该模块是缓存管理模块具体每个模块逻辑都会新建相应的模块处理并将对外提供的接口按管理模块指定的方式进行注册。如下述代码 -- 注册模块执行函数 -- mod_id 组合数据库索引字段 key local function get_key(mod, id)return string.format(%s_%s, mod, id) end -- mod_sub_mod_func_name 组合执行函数名 local function get_func_name(mod, sub_mod, func_name)return string.format(%s_%s_%s, mod, sub_mod, func_name) end function _M.register_cmd(mod, sub_mod, func_list)for func_name, func in pairs(func_list) do func_name get_func_name(mod, sub_mod, func_name)CMD[func_name] funcend end -- 注册模块数据初始化函数 function _M.register_init_cb(mod, sub_mod, init_cb)if not init_cb_list[mod] then init_cb_list[mod] {}end init_cb_list[mod][sub_mod] init_cb endget_key 是对应缓存数据存储在数据库的 _key 字段由 mod 和 id 拼接而成在 init_db 中有创建索引 mongo_col:createIndex({{_key 1}, unique true})。 get_func_name 是对应管理模块中存储不同模块的对外方法以 mod、sub_mod、func_name 拼接而成保证了唯一性。 同时还提供了两个注册方法用于注册不同模块的 远程调用函数数据初始化回调函数。 我们先来简单看一下 module/cached/user.lua 模块理解一下这里的注册方法。 local mng require cached.mnglocal _M {} local CMD {}function _M.init()mng.register_cmd(user, user, CMD)mng.register_init_cb(user, user, init_cb) endreturn _M cached 服务启动时会执行不同具体逻辑模块的 init 函数。 对于用户 user 模块初始化时调用了两个注册方法将自己的逻辑方法和本模块相关数据初始化回调方法都注册到了管理模块中。 从上述我们了解到之后封装模块进行数据逻辑处理也是同理实现即可。 下面来看管理模块如何获取远程执行函数 -- 释放缓存 function _M.release_cache(mod, id, cache)local key get_key(mod, id)cache._ref cache._ref - 1if cache._ref 0 then logger.error(SERVICE_NAME, cache ref wrong, key: , key, ref: , ref)end end-- 获取执行函数 function _M.get_func(mod, sub_mod, func_name)func_name get_func_name(mod, sub_mod, func_name)logger.debug(SERVICE_NAME, Get func_name: , func_name)local f assert(CMD[func_name])return function(id, cache, ...)local ret table.pack(pcall(f, id, cache, ...))_M.release_cache(mod, id, cache)return select(2, table.unpack(ret))end end 其他服务调用缓存模块lualib/cache.lua时通过对外提供的 call_cached API 调用缓存服务service/cache.lua的 run 方法首先执行的第一步就是 get_func从缓存管理模块module/cached/mng.lua中获取对应可执行的函数也就是这里的 get_func 返回的闭包函数。 通过闭包的形式返回为了保证每次执行完成后相应逻辑后维护当前数据对象的正确引用。获取到的该函数是在加载数据 load_cache 之后执行而 load_cache 中会改变数据对象的引用。下面来看相关代码 -- 从数据库中加载数据 local function load_db(key, mod, sub_mod, id)local ret mongo_col:findOne({ _key key })if not ret then local data {_key key,}local ok, err, ret mongo_col:safe_insert(data)if (ok and ret and ret.n 1) then run_init_cb(mod, sub_mod, id, data)return key, data elsereturn 0, New data error: .. errendelseif not ret._key then return 0, cannot load data. key: .. keyend run_init_cb(mod, sub_mod, id, ret)return ret._key, retend end-- 从缓存中加载数据 function _M.load_cache(mod, sub_mod, id)local key get_key(mod, id)local cache cache_list:get(key)if cache then cache._ref cache._ref 1dirty_list[key] true return cacheend local _key, cache load_queue(load_db, key, mod, sub_mod, id)assert(_key key)cache_list:set(key, cache)cache._ref 1dirty_list[key] truereturn cache end 加载数据实则是先进行缓存加载未命中则进行数据库加载数据库若中没有数据则新建数据插入并返回。 每次从数据库中取出数据后都会执行相关的数据初始化回调函数。如果是全新数据创建插入数据库并对该数据进行初始化。如果数据是已经存在的也会取出进行初始化。所以在不同具体模块实现模块的数据初始化回调时要考虑这点而不是一味的当作新数据的初始化。例如用户模块 local function init_cb(uid, cache)if not cache.username then cache.username New Playerend if not cache.lv thencache.lv 1endif not cache.exp thencache.exp 0end end这样初始化保证了只会对不存在字段的赋值如果数据已经有了并不会影响。 相关的完整代码参考module/cached/mng.lua 数据库 客户端登录由看门狗校验而后登录逻辑在代理服务中执行。代理的逻辑模块中对客户端登录的处理是先去数据库查找是否存在当前用户不存在则进行创建该用户的账号表在数据库中设计为如下 字段描述uid用户唯一IDacc用户账号名 用户的账号信息存在 game 数据库的 account 表下。 取出当前用户信息后还会执行用户游戏信息的加载通过向缓存模块发起 get_userinfo 消息获取用户的历史信息。用户的游戏内信息设计如下 字段描述uid用户唯一IDusername用户昵称lv用户等级exp用户当前经验值 用户的游戏信息存在 cache 数据库的 cached 表下。 在配置文件中mongodb_db_name、cache_db_name 这两个配置字段可以修改上述两张表存在的数据库名。表名则没做配置写死在了对应模块的初始化数据库代码逻辑中。 这里以用户登录注册的例子来看数据库模块的实现 module/ws_agent/mng.lua function _M.login(acc, fd)-- 数据库加载数据local uid db.find_and_create_user(acc)local user {fd fd, acc acc,}online_users[uid] user fd2uid[fd] uid -- 加载玩家信息local userinfo cache.call_cached(get_userinfo, user, user, uid)local res {pid s2c_login,msg Login success,uid userinfo.uid, username userinfo.username, lv userinfo.lv, exp userinfo.exp,}return res end登录逻辑同上述说的这里调用了 db 模块是代理对应的数据库处理模块。完整代码module/ws_agent/mng.lua module/ws_agent/db.lua local _M {}local mongo_col -- account 表操作对象-- game.account function _M.init() end local function call_create_new_user(acc)local uid tostring(snowflake.snowflake())local user_data {uid uid,acc acc, }local ok, err, ret mongo_col:safe_insert(user_data)if (ok and ret and ret.n 1) then return uid, user_dataelsereturn 0, New user error: .. errend end local function call_load_user(acc)local ret mongo_col:findOne({acc acc})if not ret then return call_create_new_user(acc)else if not ret.uid then return 0, Load user error, acc: .. acc end return ret.uid, ret end end local loading_user {} function _M.find_and_create_user(acc)if loading_user[acc] then return 0, already loadingend loading_user[acc] true local ok, uid, data xpcall(call_load_user, debug.traceback, acc)loading_user[acc] nil if not ok then local err uid return 0, err end return uid, data end return _M 本模块通过 loading_user 正在加载的用户数据标识表防止重入。call_load_user 会执行数据库操作是一个阻塞操作同之前缓存管理模块中的 skynet.queue 性质相识。不过在这里我们是保证执行加载数据操作无需在同一相近时间段内多次加载而不是用 skynet.queue 来保证这多次加载操作的时序问题。 完整代码module/ws_agent/db.lua 测试逻辑 设计获取和修改用户名协议 -- client {pid c2s_get_username }-- server {pid s2c_get_username,username 用户昵称 }-- client {pid c2s_set_username,username 用户昵称 }-- server {pid s2c_set_username,msg 是否设置成功消息 }客户端 test/cmds/ws.lua function RPC.s2c_get_username(ws_id, res)logger.debug(SERVICE_NAME, s2c_get_username: , cjson.encode(res)) end function RPC.s2c_set_username(ws_id, res)logger.debug(SERVICE_NAME, s2c_set_username: , cjson.encode(res)) end function CMD.get_username(ws_id)local req {pid c2s_get_username,}websocket.write(ws_id, cjson.encode(req)) end function CMD.set_username(ws_id, username)local req {pid c2s_set_username,username username,}websocket.write(ws_id, cjson.encode(req)) end 服务端 module/ws_agent/mng.lua -- c2s_get_username function RPC.c2s_get_username(req, fd, uid)local userinfo cache.call_cached(get_userinfo, user, user, uid)local res {pid s2c_get_username,username userinfo.username}return res end-- c2s_set_username function RPC.c2s_set_username(req, fd, uid)local ok cache.call_cached(set_username, user, user, uid, req.username)local msg success set username: .. req.usernameif not ok thenmsg failed set usernameend local res {pid s2c_set_username,msg msg,}return res endmodule/cached/user.lua function CMD.get_userinfo(uid, cache)local userinfo {uid uid, username cache.username,lv cache.lv,exp cache.exp,}return userinfo endfunction CMD.set_username(uid, cache, username)if not cache then return false end cache.username usernamereturn true end 以上便是实现一条新协议基本要修改的文件。客户端需要添加协议对应处理方法 CMD添加网络消息接受方法 RPC。 服务端需要在代理模块添加网络上行数据对应的协议处理函数 RPC由于协议要从缓存获取所以在缓存的用户模块中也要添加对应协议的处理方法 CMD。 测试如下 如上述数据成功上行到服务端并做相应逻辑处理成功后返回给了客户端。并且数据库中的数据也同步成功。 以上便是本章节全部内容项目源码同步https://gitee.com/Cauchy_AQ/skynet_practice
http://www.yutouwan.com/news/102157/

相关文章:

  • 什么是网站的栏目和板块哪个网站做美食自媒体更好
  • 怎么利用QQ空间给网站做排名怎么开无货源网店赚钱
  • 宁波企业做网站网站和微信公众号建设
  • 建设银行网站首页打三亚门户
  • 西部数码网站助手教程商品推广软文写作500字
  • 网站建设与维护岗位职责wordpress 论坛app
  • 网站审核文件绍兴注册公司
  • 网站svg使用北约网络防御中心
  • 网站后台更新 前台不显示wordpress注册不发送邮件
  • 如何重启网站服务器网页游戏排行2019
  • 免费建立企业网站广西建设厅官方网站文件通知
  • 上海营销型网站建设费用wordpress 主题 恢复
  • 网站备案的幕布是什么来的网站推广优化建设
  • 做网站读什么专业外贸公司名称大全简单大气
  • 东莞行业网站建设教程章丘做网站的公司
  • 工程建设比选公告固价方式网站jquery网页设计作业
  • 成都网站建设公司地址笛东景观设计公司官网
  • 九江网站建设哪家公司好wordpress 深度优化
  • 东莞企业网站费用小程序模板下载
  • 做一个商城网站多少钱wordpress标签怎么做静态化
  • 广东华电建设股份有限公司网站明月浩空WordPress
  • 怎么做网站栏目百度上做网站推广
  • 建立网站需要分几部进行网站建设合同性质
  • 网站被人做跳转了wordpress oss静态
  • 大连网站制作的wordpress空间大小
  • 西安网站建设流程建电影网站的程序
  • 企业网站建设的基本原则技术支持 骏域网站建设专家佛山
  • 网站怎么上传模板东莞营销型网站建站
  • 怎样优化网站自然排名博物馆网站建设的目标
  • 怎么建立免费个人网站微信公众号小程序搭建