上周做了一些测试还有改进,这里简单的总结下。
需求
需求是这样的:网络存储Gluserfs每天的日志文件大概有400多G的样子,全是类似nginx的默认格式的web日志,文件的大小分布不一定,几kb也有,几个G的也有,目录深度3~4层的样子。 现在把网络存储挂载到ServerA上,需要做的就是扫描昨天的所有日志文件,然后解析日志,存储到另外的ServerB上,B上是一个单机的Clickhouse。
设计与实现
ServerA,B均是Centos7
- B为8核心,8G内存
- A为32核心,64G内存
这种程序也称为批处理程序,其实就是离线的数据导入,使用Python3.6来实现,熟悉度最多的语言,测试集大概有460多个文件,3.1 GB的日志。
单进程
扫描目录 --> 循环读取文件 --> 解析煤航日志,每1000条插入一次clickhouse
这种是最原始的最简单的思路,测试集跑20分钟
完成数据解析倒入,ServerA上可以看到Python程序的CPU一直是100%
这里用CProfile在本地开发环境做了profiling,发现直接采用 datetime.strptime
做日期的解析 , urlparse.parse
做url的解析都比较慢,自己重新实现了,15分钟不到
完成测试。
多进程
扫描目录 --> 文件1 ,进程1,每个文件建立一次连接
--> 文件2 ,进程2
从上面的观察来看,这个是CPU型的任务,于是采用 multiprocess.Pool
来实现多进程,每个文件进行读取,建立数据库连接,1000条批量插入,20个进程大概能缩短到 2分半
左右
开始的时候是怀疑日志解析的正则表达式拖慢了速度,后来经过对测试环境的再次profiling发现60%的时间都花在clickhouse的交互上,就是插入数据的时间话费很长。为了证实这个结果,我把插入数据库这一步skip掉,发现果然快了很多,之前的统计解析+插入,单进程比较高的也就是 12000条/s,现在能到40000+/s。
优化的思考是更多的合并插入,从1000条一次插入,改成10000条一次插入,然后之前是每个文件10000条合并插入,对于比较小的文件,如果就几千条几百条也要插入,还是会增加时间消耗,于是就做了一个进程级别的合并,一个进程内积累到10000条再插入。
这样改进之后20个进程 1分36秒
不到就能完成3G日志的处理,当前的插入速入10w+/s.
设想
后来继续观察发现10个进程的情况下,由于文件的大小不一,1分钟的时候其实只剩下3个进程在工作了,其他进程已经没有负载了,然后还要快1分钟才能完全结束处理。也就是说文件大小的不均衡,导致每个进程的负载不均衡,导致复杂最大的进程拖慢了整个处理流程,所以后面还可以把做文件的切割处理,把大文件进程分块处理,例如30M一个task,分配给进程去解析插入。
大文件chunk 已经实现,插入时间已经缩短到 1分15秒
,整体的插入效率 16w/s左右
还有就是把网络文件系统挂在到新的server,多个机器分配不同的目录进行处理插入到clickhouse,当然这也要看clickhouse自身的最大吞吐。
这里为啥没有考虑网络存储和数据库的瓶颈呢,因为这边内网之间是万兆网卡,clickhouse之前的测试单进程10w插入应该没问题,当前的程序还没到这些瓶颈,所以没有重点优化
总结
文件处理的优化手段
- profliling
- 多进程
- 文本解析
- 合并入库
- 大文件分块
- 使用的类库要熟悉
后记
后来发现 clickhouse python client有压缩的选项,结果开启压缩之后,整体时间一下子缩短了40%。。。