前言 对于复杂的业务逻辑,出现的bug,通过日志手段 或者review代码 都没法查明原因的情况下,这时候我们可以通过服务录像重放 来还原整个事故现场。
实现方案 skynet是多线程的,不太方便对整个进程进行录像,而对于skynet的单个服务,能保证消息是同步处理的,所以可以比较方便实现对于单个服务录像,可以同时录制多个服务。实现思路就是把所有消息记录下来,重放时读取记录,解析重放即可。 其中有一些细节点,比如session分配,socket_id分配,time时间,math.random随机数,hotfix热更,sharedata,sharetable共享配置处理,都需要一些特殊处理才能保证录像重放时确保一致。
session socket_id skynet.time os.time 这些只需要调用时,记录下值,重放时读取即可。
math.random 如何保证一致了 只需要记录math.randomseed(x,y)随机数种子,相同的种子,随机出来的值会是一致的。
hotfix 热更 每一次热更,会把热更过的文件拷贝存在在recordpath对应serverId 关联的目录下,用于重放时能加载到修改后的文件。
sharedata,sharetable 因为录像记录的是共享数据之前由sharedata或者sharetable返回的C指针,重放时地址数据并不存在,这种访问肯定会崩溃。解决方式有2种,一种方法就是启动对应sharedata,sharetable并加载数据,重放时重新去拿数据的指针,第二种方法就是直接使用loadfile加载配置,显然第二种方式更简单直接一些。
如何录制? 可热更服务在load_mods.lua配置中配置is_record_on = 1即可,recordlimit 是录像大小限制,超过即不再写入(针对单个录像文件的限制),recordpath指定录像存放目录。普通skynet服务需要自行接入录像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 return { share_config_m = { launch_seq = 1 , launch_num = 1 , default_arg = { server_cfg = { recordlimit = 1024 * 10 , recordpath = "../records" , }, redis = { test = { host = '127.0.0.1' , port = 6379 , auth = '123456' , db = 0 , } } }, }, A_m = { launch_seq = 2 , launch_num = 1 , }, B_m = { launch_seq = 3 , launch_num = 1 , is_record_on = 1 , } }
如何重放? 在make/script/run.sh和make/script/restart.sh中增加了 recordfile参数,播放录像时加上录像文件路径即可。sh make/script/restart.sh load_mods.lua 0 records/00000010.record
重放注意点
确保代码环境一致,代码不一致可能会导致录像没有按预期回放,这时候通常会代码出错。 比如说 skynet.time(),os.time(),math.randomseed,还有会创建session和socket_id的函数调用,少调或者多调用一次都会导致录像对不上,因为重放录像的时候每次都是去记录中拿对应的值,少或多都会导致顺序错乱。
逻辑代码中pairs调用可能导致重放逻辑乱序 因为pairs每次调用并不保证顺序,如果在pairs循环操作分配了session(call,time_out),socket_id(socket.connect),os.time, skynet.time, math.randomseed,math.random,都会因为pairs遍历无法保证顺序而导致重放乱序出错。目前没有想到非常完美的办法来解决这个问题,强行重写pairs让其变成有序遍历,太过损失其性能。我们只好在需要重放录像的业务逻辑中,通过规避上述情况下的pairs使用。
对hotfix的处理 对于录像的服务,我们可能通过check_hotfix.sh对其进行了多次脚本热更,重放的时候我们怎么保证也能重放热更过程,有2种办法,1是把热更文件写进录像,2是把热更文件CP一份,我选择了第二种方法,因为我们重放过程中,一般需要加一些打印日志,这样热更后的文件,我们也能去增加日志打印。
对于sharedata,sharetable 共享配置热更的处理 同hotfix处理一样,cp被热更的文件。
如何正确的重放 我们应该想办法让代码先回到启动前,通过借助svn,git这些版本控制工具,不过这需要使用者自己去保证了。
2024/12/01 优化改动 命名优化 录像的文件命名改为 room_game_hall_m-1-1-20241205-180640.record
room_game_hall_m 可热更服务名
1 启动版本号
1 启动索引
20241205-180640 启动时间
录像的热更记录路径命名改为 hotfix_room_game_hall_m-1-20241205-194136\patch_20241205-204136\ 共享配置数据的热更记录路径命名改为 sharedata_room_game_hall_m-1-20241205-194136\patch_20241205-204136\
1 启动版本号
20241205-194136 启动时间
20241205-204136 热更时间
录像文件的切割方式 由于录像文件必须从头开始跑,所以我们并不能像切割日志文件一样去切割录像文件。只能另辟蹊径了。得益于框架的reload热更机制,我们可以定时每天reload自身服务,这样就开始记录新的录像,而旧的录像文件我们根据策略去删掉,比如用logrotate去整理历史的录像文件。这样一来,我们可以一直保留近期的录像文件,避免了过多占用系统磁盘资源,录像文件更小,也方便重放。
因此新增了可热更服务 2种机制配置
auto_reload 自动定时热更 skynet-fly.time_extend.time_point.lua 的配置项
record_backup 录像文件自动整理 需要启动logrotate_m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 room_game_hall_m = { launch_seq = 6000 , launch_num = 6 , is_record_on = 1 , auto_reload = { type = 3 , hour = 5 , min = 30 , sec = 30 , }, record_backup = { max_age = 3 , max_backups = 50 , point_type = 3 , hour = 5 , sec = 20 , }, default_arg = { hall_plug = "common.plug.hall_plug" , } },
2024/12/22 优化改动 通过查看pairs的源码实现发现 pairs遍历string key 会依赖于 lua_state 创建时生成的strseed 种子。那么只需要录像时记录到strseed 种子。播放时设置好对应的strseed就能保证pairs 遍历 table string key 的顺序了。这样一来。对于number,string key都遍历顺序都能保证一致。 table, userdata 为 key 的没发保证顺序,因为hash是基于 table, userdata 的指针地址。 录像重复没发保证指针地址也一致。
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 local function test_pairs () local t = {} for i = 1 , 10 do t['i' .. i] = i end for k, v in pairs (t) do log .info("string pairs:" , k, v) end local num_t = {} for i = 1 , 10 do local num = math .random (1 , 10000 ) num_t[num] = i end for k,v in pairs (num_t) do log .info("number pairs:" , k, v) end local table_t = {} for i = 1 , 10 do table_t[{}] = i end for k,v in pairs (table_t) do log .info("table pairs:" , k, v) end end
正常跑服务结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i1" 1 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i2" 2 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i10" 10 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i9" 9 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i3" 3 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i4" 4 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i6" 6 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i5" 5 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i7" 7 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:140]"string pairs:" "i8" 8 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 6165 8 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 7622 4 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 8647 1 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 6339 6 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 5005 5 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 432 10 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 3777 9 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 2564 2 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 5714 7 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:151]"number pairs:" 6690 3 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 1 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 5 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 9 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 2 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 6 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 10 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 3 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 7 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 4 [:00000013][20241221 12:46:43 31][info][B_m][./module/B_m.lua:160]"table pairs:" { } 8
重放结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i1" 1 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i2" 2 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i10" 10 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i9" 9 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i3" 3 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i4" 4 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i6" 6 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i5" 5 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i7" 7 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:140]"string pairs:" "i8" 8 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 6165 8 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 7622 4 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 8647 1 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 6339 6 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 5005 5 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 432 10 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 3777 9 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 2564 2 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 5714 7 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:151]"number pairs:" 6690 3 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 3 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 7 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 4 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 8 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 5 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 9 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 1 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 6 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 10 [:00000013][20241221 12:46:43 20][info][B_m][./module/B_m.lua:160]"table pairs:" { } 2
结论 可以发现number和string重放是一致的。