项目背景:我所在的厂子里广为使用的某某通用log库,对多进程多线程支持不佳,要求自己在新线程中调用某函数初始化线程数据,而且多进程切分文件还有bug,对使用者极不友好;还有,那神一样的配置很多人只是会照着葫芦画瓢改吧改吧用,真正完全明白的不多,可读性不太好;然而,我们开发的一个大项目中,有不少模块,既有多进程的,又有多线程的,需要多进程打同一个log文件。我们后来从一个退役产品自己的代码里找到一个他们做的log库,满足了我们的需求,对多进程、多线程完美支持,而且使用接口非常简单,只需初始化一次,剩下的不用管了,只管用它就行了。切分文件也很给力,人工rm或者mv走文件,都能自己检测到然后生成新文件,这非常方便,就算是有人误操作把当前日志删了或挪了,都没问题。像nginx就不行,你得给他发信号让它rotate log才行。所以我们一直在用它,感觉不错。 直到有一天,我开发了一个性能苛刻的模块,发现开启log能达到3w的qps,关闭log就能上升到8w qps,一下子明白了这个日志还是很耗性能的。于是,我打算优化日志库,这个东西大家都在用,让大家都能享受一次免费的性能提升,何乐而不为? 在此过程中,我参考了网上很多资料,列出:
1、http://www.slideshare.net/chenshuo/efficient-logging-in-multithreaded-c-server/ 感谢陈硕,陈硕的观点我一直都是赞同和跟随的。从这里我们知道我们要做一个诊断日志库,诊断程序问题用的,我们绝不打算把日志发送到网络上去这种功能集成进来。性能和使用的便捷性是我们主要的追求。 他这里提到的logstream方式能够提升一些性能,但是我没有采用,因为实际中大家还是习惯于printf这种格式的写日志方式。用stream太前卫了,而且会限死了只能被c++程序使用。我们有用c开发的模块。 异步日志是提升性能的另一个终极手段,写日志完全不会被io卡住。我同时提供了较为常见的同步日志和异步日志。但是目前异步日志只是一个naive的实现,还需要很多优化。 因为异步日志数据可能来不及写到磁盘、不方便做throttle(都有对应的解决办法),可能我们在生产环境中用的比较少,除非性能真的非常苛刻。我现在还没有精力对它优化。估计优化也会抄袭一下陈硕的实现。
2、Pantheios 号称是c++界最快的诊断日志库,但是这个实现的代码看得让人云里雾里的,资料也比较少。不敢恭维。
3、log4cxx,log4cpp,冗余的设计,估计是喜欢玩设计模式的人搞的,想弄个和log4j对等的c++版本日志库。但是这恰恰是缺点。使用起来真的不简单不可依赖,而且大致一看就知道性能低下。
4、tb-common-utils中的tblog。来自淘宝的多隆(看过《淘宝技术这十年》的少年们,你应该知道多隆)。特别感谢他的一段代码,能让我们在多线程打到同一个log instance的时候也完全不用加锁。
总结起来,要做的是一个兼容了我们在用的库的使用方式和接口的,简单易用,性能卓越的,稳定可靠的日志库。 目前FileAppender已经完了,经过我多次仔细的测试,我已经分析出来性能消耗点所在,优化空间已经非常小了。我会另外记录优化过程中使用的技术,以及如果真的再优化,下一个优化点何在。 目前1000w条日志,每条200字节payload,日志的level,pid,file,func,line,tid,time信息大约有100字节,加起来有300字节左右,现在每秒钟能写35w条日志,比现在用的这个库整整提高了一倍。 而且在多线程下,性能没有明显下降(其实是有的,那是因为write调用毕竟在内核是有锁的缘故,用户态的线程几乎无锁,哎,还是有一个很简单的锁的,可以用原子操作替换掉,但是厂子里gcc版本过低,没有__sync_val_compare_and_set这种高级货色,我又不想写汇编,先暂且如此,锁本身性能消耗很低,只要锁里边很快,几个指令周期的事)。