orm在用户数据日志上的应用

前言

游戏服务器免不了需要记录大量玩家数据变化玩家操作行为对局战斗数据等等数据的记录以及后台提供数据的查询入口。我遇到过两种做法。

  • 方案一
  1. 手动创建数据表结构。
  2. 服务器代码拼接sql调用插入。
  3. 后台再根据表结构定义写查询页面。
  • 方案二
  1. 服务器先写本地json文件。
  2. 再选用某种日志采集方案采集日志。
  3. 采集那边也需要定义表结构,然后插入数据库。
  4. 后台再根据表结构定义写查询页面。

总的来说,这两种方式效率低下,重复劳动多,并且还需要关心怎么清理过期的历史记录数据(比如一个月前的,可能每个日志记录保留的时间不一样)。我在考虑实现日志系统的时候,就想做一个架构直接能写日志采集日志定义日志表插入日志查询日志面板过期数据清理过期日志文件清理。实现以后,业务上我们只需要去定义日志orm结构,写日志就行,其他工作架构都统一处理了。

实现方案

写日志

利用use_log的写文件功能。use_log创建时只需要定义好保留天数(maxage),以及刷磁盘间隔时间(flush_inval),其中保留天数可以解决过期日志文件清理的问题,刷磁盘间隔时间可以缓解磁盘性能压力(不立即flush file:write会利用到内存缓存待写入数据)。实现ormadapter_uselog对接上ormtable只需要实现创建单条数据接口(create_one_entry),创建数据时转成json写入文件。这样有了orm的加持,我们可以方便的定义字段类型以及字段索引。再利用watch_serverorm的相关信息发布同步。这样其他服务想了解日志信息订阅即可。实现好之后我们就可以这样写日志了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local adapter = ormadapter_uselog:new(error_log_path, error_log_name, 0, g_default_maxage)
adapter:set_sub_syn(SYN_CHANNEL_NAME.log_desc_info .. tab_name)
g_errobj = ormtable:new(tab_name)
:string32("_guid")
:string64("_log_name")
:uint8('_svr_type')
:uint16('_svr_id')
:uint32('_time')
:text('_err_str')
:set_keys('_guid')
:set_index('time_index', '_time')
:set_index("svr_index", '_svr_type', '_svr_id')
:builder(adapter)

local info = {
_guid = guid_util.fly_guid(),
_time = time_util.time(),
_log_name = tab_name,
_svr_type = g_svr_type,
_svr_id = g_svr_id,
_err_str = err_str,
}

g_errobj:create_one_entry(info)

日志的写入路径和文件名,通常都会写成一样的,还有日志的_guid,_svr_type,_svr_id_log_name,_time字段,这些都是通用的,每个日志都需要写入这些,所以还需要进一步封装,最后在demo中实现了log_helper,有了log_helper之后我们写日志是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
g_logic_info.item_change_log = log_helper:new_user_log('item_change_log')
:int64("player_id") --玩家ID
:int64("item_id") --道具ID
:int64("change_num") --改变值
:int64("cur_num") --目前值
:uint32("source") --变更来源
:set_index("item_index", "item_id", "change_num")
:set_index("player_index", "player_id")
:set_index("source_index", "source")
:builder()

g_logic_info.item_change_log:write_log({
player_id = player_id,
item_id = id,
change_num = num,
cur_num = count,
source = source or 0,
})

其中item_change_log就是日志在数据库中的表名。最终实现后,我们在业务中,后续只需要做这两步,定义结构和写入日志。

日志采集

在logserver中利用watch_syn_clientpwatch就可以订阅同步到其他服务发布的日志orm信息,利用同步到的信息,我们能知道对应日志的路径和文件名,然后启用间隔轮询的方式调用use_logread方法,一次采集多行数据。logserver建立一个记录采集信息的orm表,记录采集的相关信息。

定义日志表

通过watch_syn_clientpwatch同步的信息,我们能知道每个日志表的表字段相关结构信息,利用这些信息,可以很方便的建立对接数据库的orm对象。

插入日志

采集到的数据,通过解析出log_name得到对应的orm,再调用对应orm创建方法就行。
异常处理,为了避免出现网络波动问题,或者数据库挂逼,出现日志写入失败的情况,增加了一个重试池,写入失败的日志数据会加入重试池,重试池每间隔1分钟尝试写入20条数据,当重试池达到1万+数据时,日志采集暂定,小于1万日志采集继续,这样避免采集过多的数据又无法插入。

查询日志面板

后台服务通过订阅同步也能知道orm日志表结构,建立适配数据库的orm,这样后台新增日志或者修改日志就无需修改后台代码了。

过期数据清理

通过同步过来的**保留天数(maxage)**信息,再利用orm:idx_delete_entry,利用_time索引做过期数据删除处理。

总结

大大简化了加数据日志的流程,整个日志系统兼顾了稳定性和吞吐量。


orm在用户数据日志上的应用
https://huahua132.github.io/2025/04/09/skynet_fly_ss/orm_log_use/
作者
huahua132
发布于
2025年4月9日
许可协议