iOS支持多线程的日志系统设计与实现
Posted 58无线技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS支持多线程的日志系统设计与实现相关的知识,希望对你有一定的参考价值。
谨以此文记录一下过去一段时间整理ios日志系统的思路和解决方案,希望能给关注这部分的朋友们一些思考或启发,也欢迎分享你们的解决方案,让大家一起学习一起进步。毕竟现在不是流行一个词么:共享~
本次分享主要分为以下六个部分
1. 背景,阐述旧版日志系统出现问题。
2. 解决问题,既然产生问题,应该如何合理的解决。
3. 测试,为了保证日志生成和上传的稳定性,如何测试。
4. 实施,将项目中的旧的API替换成新的API。
5. 容错,一种日志的降级方案,当新的日志系统出现问题,怎样回滚到老的方式。
6. 总结与回顾。
一、背景(老的埋点方式遇到的问题):
因为老的日志系统是在项目的初期完成的,但是随着58业务的不断发展,业务场景不断增多,老的日志系统暴露出以下一些问题:
1. 埋点日志不支持多线程,如果发生多个线程并发埋点,容易造成日志拼写错乱甚至App发生Crash。
2. 如果埋点是在主线程发起的,那么文件的读写、生成、删除等操作就在主线程处理。我们知道,文件的IO操作相对耗时,如果使用主线程做日志的各种处理,显然不是一个好的方式。
3. 埋点Api设计不合理,一个埋点API由两句甚至多句语句组成,调用不方便。
4. Server端统计到上传的日志包偶尔会很大,甚至有可能出现几兆的情况。这样就增大了用户瞬时流量的消耗,并且日志上传过程中也容易失败。
针对上面的一系列问题,我们决定设计一个新的日志系统。
二、解决问题
1. 针对埋点不支持多线程怎么办?答案是没有就创建一个~,创建一个日志线程用于日志相关的操作,如读写、删除和创建。关于怎么创建线程,这里我不多说了,ASI、AFN已经有很好的例子了(顺手还可以给线程起个风骚点的名字~),如有需要自行查阅即可。
2. 有了日志线程后,外部API接口可以运行在不同的线程环境中,在此先画个流程图便于大家理解:
在此流程图中,我们外部有3个线程在同时进行埋点,如何让这些线程在合适的时机回到日志线程Work呢?实现上我们利用performSelector:onThread:withObject:waitUntilDone:这个苹果原生的API。想要生成日志时,将onThread:这个参数设置成图中的埋点线程,就能做到无论外面是什么线程环境,都会调度到日志线程工作。从而解决了多线程造成的数据不同步甚至Crash的问题。
然而这样虽然简化了调用场景,但是日志生成变成了异步调用的过程,这样外部传进来用于装配日志的参数就需要捕获,并保存这些参数直至日志生成完成。为了解决这个问题,我们自然想到OC里的Block,利用Block,我们不仅能在Block块内提供装配日志的方法,也能捕获外部与日志相关的参数。这样日志生成就变成日志线程对Block的调度。
还有一个问题,当Block执行时,我们会根据外面捕获的参数组装日志模型,假如这个日志模型叫WBLogModel,那么,是不是每次生成新日志的时候都要创建一个WBLogModel实例呢?答案是否定的,因为对象的创建和销毁比较消耗CPU 资源,而日志API在APP的整个生命周期内可能会被频繁的调用。那么有没有方式在不创建多个日志模型的情况下,就能做到日志正确的装配呢?经过细思考,我们发现因为有了独立的日志线程,相应的日志生成过程就变成了一个串行的工作。那么,WBLogModel对象也就没有必要频繁的创建和销毁了。我们只需要创建一次,在每个日志生成完毕后将WBLogModel实例的所有字段清空,待下次日志生成时就可以对WBLogModel实例重新赋值,这样,就解决了实例不需要频繁创建的问题。这部分对应的代码如下:
3. 针对API的设计,通过上图的实现我们看到,一个日志接口函数完成了日志的生成和写入两部分功能,这也就解决了外部只需调用一行API即可实现埋点的目的。当然,项目中还有一些其他样式的API,方式类似,再此,就不一一列举了。
4. 针对上传日志包有可能过大的问题,我们制定了一个N(一个全局参数,根据需要灵活配置)分钟生成一份日志文件并尝试上传策略。但是理论上在弱网情况下,日志文件可能越积累越多,这样上报的日志包可能过大,这样对用户的瞬时流量会产生很大的影响,同时包越大,上传的过程中越容易失败。对此,我们上传时采用每次最多上传最近的100(变量,灵活配置)个日志文件的压缩包(zip压缩)。而剩余的日志文件在下一个时间周期到来时上传(转变成一道数学里的追及问题~)策略如下图:
三、测试
1. 日志系统根据预先的设计并实现后,是不是就可以应用了呢?答案是否定的。无论做什么功能,做完后我们都要写好测试用例。针对上面的背景,我们要保证三件事情,第一,埋点支持多线程;第二,埋点能正确生成日志文件;第三,日志文件能正常上传到服务器上。针对以上问题,我们的自测用例如下
2. 测试用例:并发写10万条日志,每条日志都有编号且是自增的,从0一直排列到99999。在配合断网、联网的手动切换操作。每个日志包成功上传到服务器后,客户端也收集一下,比如放到桌面上。待所有日志包成功上传后,桌面上就收集到所有zip包,解压,我们就得到了所有上传成功的日志文件。利用Python脚本解析所有日志文件,收集每条日志编号,看编号是否有遗失或增加,测试代码如下
,Python代码篇幅较大,我会放在我的Github上,,如有需要,请自行下载。
四、实施
上面的流程阐述了日志系统的工作流程,可以说万事俱备,接下来我需要把项目中所有旧埋点的API替换成新的API。当我搜索了一下埋点相关的代码后,我的内心是崩溃的...由于业务发展多年,整个项目里有数千处埋点(夸张,相当于白发三千尺~),而且埋点API又有很多种形式。如果人工改造的话,我估计我在58的职业生涯就要在修改埋点中度过了…作为一名有追求的高级开发工程师~,显然是不能接受这个残酷的现实的。于是我又想到了Python...,大体思路是找到工程里所有的.m(执行文件),利用正则表达式分析出所有样式的老埋点,提取老埋点中的相关参数,提取参数后配装新版本的埋点API,然后将新生成的埋点代码文本把老文本替换掉。这样,我的主要工作就变成了测试正则表达式的正确性了。针对所有埋点API写好正则后,运行脚本。提交代码到Git上,Python 代码片段如下:
因为Python的实现方式和上面的类似,就不过多解释了,详细请看我的Github。
五、容错方案
日志修改是作为iOS组内部的技术需求提出来的,因此投入的测试的时间和人力比较少,而且日志一般也比较晦涩难懂,QA测试起来很不方便。那么问题来了,上线之后出现问题怎么办?为此我们制定了一套日志降级方案(示例代码如下~)
写了一套新、旧埋点的桥接代码。思路就是当上面的某一类埋点代码出现问题了,例如第一类埋点代码出现问题,我们可以通过上线后打Patch的方式,把WBFirstReplaceAPI当参数传入到patchForLogFunction:函数中,这样第一类新埋点都会桥接到老的埋点方法中去,这样我们就能做到,哪类不对改哪里,妈妈在也不担心我的埋点代码出问题了~
六、回顾与总结
经过几个版本的验证,(包括和之前埋点数据的对比等),支持多线程的日志系统正式上线~终于有机会和大家分享了,由于本人撰写水平、技术水平有限,如有纰漏,欢迎指正交流。上面就是我做日志系统的点滴,马上就要过年了,祝大家新的一年工作愉快。
以上是关于iOS支持多线程的日志系统设计与实现的主要内容,如果未能解决你的问题,请参考以下文章