您的位置:首页 > 理论基础 > 计算机网络

网络游戏服务器之 日志系统

2016-11-01 16:04 253 查看
一、For Beginner

日志系统主要是一组记录引擎底层及应用逻辑层系统状态变化过程的接口。几乎所有的游戏服务器框架都会有一组操作日志的接口,当然最简单的就是大家所熟悉的printf格式化到标准输出,日志系统的复杂度有高有低,具体目的主要是为调试及运营数据查询需要。可以说日志系统实现是否易用、简洁,将直接影响到开发及运营效率。

二、文件VS数据库

是写文件还是写数据库,这是一个问题。数据库的优势是查询/统计/排序很方便,一旦建立好所有的日志表及其操作以后,后期运营会比较舒服,可以轻松应对策划无休止的BT日志统计需求。但为每个日志建立数据库的表项及其索引是需要有很强前瞻性的,对程序员的经验要求较高,大数据量的数据库数据库表项及其索引的变更所需要的时间比较长,
而日志本身的内容变更应随需而变,这是一个矛盾。写文件的最大好处是简单,方便人工阅读,文件更容易做备份,备份的拆分与合并也更容易,即使要查找若干年以前的数据也不需要很复杂的恢复过程。但文件的数据搜集与统计的成本是很高的,往往不能适应策划的快速响应需求,后期在运营上面支撑不足。 现在很多项目也逐步采用文件与数据库相结合的方式,日志首先写到文件中,然后根据日常统计收集需要将指定的日志定时增量load到数据库中,然后对数据库进行查询,这无疑结合了两种载体的优势,值得尝试。

三、日志文件格式建议

日志文件应该采用文本格式,这是unix工具集的标准格式,任何尝试自定义日志格式的行为都将付出代价--你将不得不为自己写一堆的配套工具,服务器磁盘容量及IO消耗并没紧要到如此这般的程度。

通常每一条日志可以概括为: [datetime]log desc:key1=value1,key2=value2,key3=value3...,无论如何选定日志的格式,有一个基本的前提就是,项目内必须保持严格一致,可以在debug模式下对日志的输入信息进行正则匹配,如果不满足需要就报错。日志格式的统一,为项目后期运营工具的单一化带来了可能,如果每个系统都是按照自己的格式任意书写日志,那么所有的日志扫描工具都差不多要自己重写,模块的可重用性大大降低,这实在是很令人恼怒的事情,我们不应该把时间浪费到这样的事情上面。

建议设置单条日志长度上限,这主要是为方便人工阅读,设置为80的可能性很小,废话稍微多一点就超过了,一般设置为256,大概主流显示器在终端上面字体合理的情况下可以显示完一行。

单行日志记录完整事件,切忌换行,以交易系统为例,应该在一行里面把交易双方的个人信息/物品/金钱信息一起记录下来,如果换行的话,后期扫描日志的工具会相当难写。

四、日志分级

多数日志都是废话,特别是当系统一切正常的时候,你可能会关闭掉许多输出速度很快又没啥价值的日志...但关键时候你可能又需要启用这些日志。如果在日志系统设计之初就提供日志的分级策略,将为这种需求提供便利。
日志分级可以做两种设计,一种是静态的即编译期决定的,比如设置一个LOG_LEVEL的宏,所有小于等于此级别的日志都将被输出。 另外也可以作为服务器的启动参数, 比如-D 3,这跟apache配置文件的LogLevel比较类似,一般地,当系统收到SIGHUP信号的时候,可以重设一次LogLevel,这样就可以实时的在生产环境中改变日志分级。常见的日志分级层次有

LOG_EMERG 7

system is unusable

LOG_ALERT  6

action must be taken immediately

LOG_CRIT  5

critical conditions

LOG_ERR 4

error conditions

LOG_WARNING 3

warning conditions

LOG_NOTICE 2

normal, but significant, condition

LOG_INFO 1

informational message

LOG_DEBUG  0

debug-level message

当然实际游戏环境中可能并不需要这么多的级别,一般情况下缩减一半都足够了。实际项目中,写游戏逻辑的程序员对日志分级是非常不感冒,反正只要IO还支撑得了,谁又愿意花那么一点点时间定义一下自己的日志Level呢?

五、基于文件日志系统实现细节

基于本地文件的日志系统几乎都要面对一个问题,写日志是用buffered io(fopen/fwrite/fclose) 还是系统调用unbuffered io(open/write/close),如果你的日志系统会缓存文件操作的handler,并且测试表明日志系统占用了不小的开销,那么你可以选用buffered
io,否则还是使用unbuffered io会比较靠谱。在系统异常退出的情况下,丢失日志的可能性小非常多。

在日志大量重复写同一组文件的情况下,我们很容易想到把日志文件handler缓存起来,通常的做法是做一个日志的class。这个方法看似完美,节省了大量的open/close操作,经过笔者的反复测试,在写小数据量(24-48字节)的情况下,cpu消耗几乎减少一半左右。但考虑到实际服务器生产环境会定期move/tar日志文件,而日志class得不到任何通知,这会导致将日志写到并不期望的地方,甚至丢失日志。当然我们可以建立一种通知机制,让系统管理员的shell运行的时候顺带通过signal通知一下服务器进程,然后进程内显式的reload所有的日志对象。甚至,我们还可以用一种更优雅一点的做法,使用系统kernel提供的inotify机制去监控这些日志文件,当他们发生改变时候reload对应的日志文件。
但wait...这真的有此必要么? 过早的优化是魔鬼,还是让你的系统统计数据来说话吧!

日志系统是同步写还是异步写? 相信经历过游戏运营的同学都不会陌生以下场景,某日深夜,运维同学急电:“服务器卡机啦,卡得不得了啊,走三步退一步,赶紧上线看看吧”,结果上去一看,每日文件全备份,IO卡到死,游戏中所有的IO操作都阻塞了,不卡才怪呢...这就是同步io读写的悲剧场景。基本上应该说,游戏服务器的所有操作都不应该是同步阻塞的(关于sync,block,unblock,async的话题另外再说吧),除了内存以外我们不能假想任何设备的速度是足够的快的,这是一个基本前提。否则,游戏服务器莫名的卡机那是早迟的事情。对于异步IO读写来说,有两个可选方案,一个是叫做非阻塞读写/带阻塞通知的异步io模型比如,epoll/kq/select/poll等等,但似乎悲剧的是在linux下面epoll并不支持文件的异步读写,另外一个就是真正的正宗的async
io - AIO,关于AIO的资料网络上面已经相当丰富了,使用起来也非常简单。重点是AIO跟libevent的结合还不是特别好,需要做一定工作,并且忍受一下在多个AIO并发时的性能损失,但无论如何都不要尝试直接操纵AIO的signal或者thread callback func,除非你觉得你以及你的团队可以写出漂亮的“信号可重入函数”以及“线程安全的函数”。 

日志系统写本地还是写远程? 为了实现简洁及运营方便,对于单服结构的游戏服务器都应该写本地日志,但对于多服结构的则最好是固定写到一个物理服务器上面,否则日志的收集/合并/排序是个抓狂的工作。

*unix syslog

相信有很多人考虑过syslog作为日志系统的底层框架,我目前对这部分了解有限。有时间了再补课

六、其他

呃,部分新手有时候会犯傻,当日志系统出错以后想要log下来,结果却习惯性的使用了自己定义的日志函数,于是...悲剧了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息