前段时间在做应用db访问日志客户端的开发,这里记录下日志客户端设计相关问题:
1. 如何拦截请求?
a. 公司的db访问都是统一经过dal框架,所以动态给dal框架底层执行方法增加拦截代码即可。主要思路是在注册tomcat启动监听,在监听代码中,注册jvm修改类定义的tranform(参考jvm的instrument功能),最后使用asm修改dal代码,增加拦截记录日志代码。这样客户端无需做改动。
b. 使用jvm-sandbox框架,jvm-sandbox底层也是使用方法a的原理。
差别:
方法a需要在应用代码里加入记录日志代码依赖。具体是在pom增加dependency,方法b不需要在应用代码里加,但是需要在应用的应用服务器上启动jvm-sandbox进程。
最终选型:
日志记录端使用方法a,原因是使用方法b对开发者透明,不容易管理。我们还有给记录的日志进行流量回放的功能,在流量回放的时候使用了方法b,因为流量回放只会在测试环境做,所以管理问题没有那么严重,而且回放使用方法b还有个好处就是动态插拔,jvm-sandbox可以只在测试环境部署。
下面从方法a来讲解客户端设计需要注意的地方:
1. 如何控制IO?
日志记录是IO密集型,我们公司目前是会通过消息记录到clickhouse,如果日志量比较大,实时同步记录肯定不行。有如下几种解决方法:
a. 加内存缓存,日志先堆到内存中,然后开启多线程(目前我们实现为单线程)异步同步。
b. 加内存缓存会带来内存管理问题,1. 可能导致内存暴涨。 2. 可能导致频繁GC。 3. 日志丢失风险较大。 一种解决方案是设置内存缓存上限,并使用压缩等方法降低日志大小,但是该方法不能彻底解决问题。还有一种方法类似kafka和rocketmq采用的,使用mmp给日志记录到文件。由于mmap性能接近内存,所以性能问题可以解决,而且如果机器重启,操作系统会保证mmap里的数据刷新到磁盘,最后由于mmap不是使用jvm的堆内存,所以不会有GC问题。
c. 压缩存储,采用压缩算法对日志压缩,降低日志大小。
d. 合并日志,这是在日志同步时候要做的,合并日志可以提高吞吐量。但是要注意合并日志要注意分片的大小设置。
目前我们采用的是简单的控制内存缓存大小,因为目前我们的日志量不大,后续在根据情况进行优化。