LogStat日志处理流程优化简析

达芬奇密码2018-06-22 18:00

Apache格式的Web访问日志在我们的日常数据处理中占据了很大的份额,这种日志本身具备良好的结构和规范,可以方便地进行各种解析和转 化。访问日志所携带的信息很多,视具体的日志配置不同而不同,但一般都会有IP,访问时间戳,请求类型,请求URL,状态码,字节数,Referer,域 名和浏览器信息等。统计流程所做的就是从这些信息中计算出需要分析的指标和数据。

   
一般来说,一条日志可能会对应于到多个统计指标中。如某 一次请求页面,既是当前页面的一次PV,也是总站的一个PV,同时也可以是外站一个新的来源。同样一个统计指标可能需要对日志中多个字段进行匹配后才能确 定。假设对于N条日志,需要统计M个指标,每个指标需要对日志进行K次匹配,那么其时间复杂度为ON*M*K)。日志的规模显然是一直在增长的,我们也 无法限制;需要统计的指标也是会随时间或者业务需求来增加,只有匹配次数K可以进行优化来降低整体的运行时间。

   
为了方便统计,我们的统计指标以XML配置文件的形式存放,以下对格式进行一下简要说明:

<?xml version="1.0" encoding="UTF8"?>
<statUnit>
<pvItem>1</pvItem>
<uvItem>2</uvItem>
<match>
<entry fieldName="request" matchType="contain"><![CDATA[GET /tag]]></entry>
<entry fieldName="hostName" matchType="equal"><![CDATA[lofter.163.com]]></entry>
</match>
</statUnit>
<statUnit>
<pvItem>3</pvItem>
<uvItem>4</uvItem>
<match>
<entry fieldName="request" matchType="contain"><![CDATA[GET /tag/lomo]]></entry>
<entry fieldName="hostName" matchType="equal"><![CDATA[lofter.163.com]]></entry>
</match>
</statUnit>


从配置文件看出这是计算4个统计指标,我们称之为2个统计配置(因为每个统计配置对应2个指标,由同样的统计方法得到,区别在于一个需要对用户去重,一个不 需要)。每条日志在处理时会顺序地完成所有统计配置的比较,很明显,统计项配置越少,每个统计项配置中的match项目越少则程序运行得越快;match项目的匹配方式越高效也能带来性能的提升,比如equal方式就比regexp(执行正则匹配)效率快,而是用正则匹配则不同表达式可以带来不同的匹配效 率。

  为了共享配置信息,减少统计项配置,或者说是减少日志重复的匹配次数,我们引入了配置组的概念。即对于一类相似的URL我们使用同一组配置项,下面是一个组配置


<?xml version="1.0" encoding="UTF8"?>
<statUnit>
<unitType>group</unitType>
<match groupMatchIndex="1">
<entry fieldName="request" matchType="regexp"><![CDATA[GET /tag(/lomo)?]]></entry>
<entry fieldName="hostName" matchType="equal"><![CDATA[lofter.163.com]]></entry>
</match>
<group>
<entry fieldName="1" matchType="equal"><![CDATA[]]></entry>
<pvItem>1</pvItem>
<uvItem>2</uvItem>
</group>
<group>
<entry fieldName="1" matchType="equal"><![CDATA[/lomo]]></entry>
<pvItem>3</pvItem>
<uvItem>4</uvItem>
</group>
</statUnit>


这样我们把2次的配置项比较转换成了一次比较,同时通过正则表达式捕获可变的部分,将其放入一个K/V内存结构中,后续只需要将group配置做一次单独的 Key操作即可。在上例中,"/lomo"NULL对象"被放入一个List,然后遍历整个group中的entry配置,看是否存在为空或者是" /lomo",存在则计数器加1,不存在则Pass

   
从此思路出发,很快可以对组配置中的匹配类型增加其他类型,如使用 regexp/contain/startsWiith等等,当然equal是最高效的,仅仅做一次Key的命中而已,其他方式则需要对组内其他配置进行 遍历。同时稍加修改也支持多个正则表达式组来进行匹配,如

<?xml version="1.0" encoding="UTF8"?>
<statUnit>
<unitType>group</unitType>
<match>
<entry fieldName="request" matchType="regexp"><![CDATA[GET /([^/]+)/([^/]+)/?from=(baidu|google)]]></entry>
<entry fieldName="hostName" matchType="equal"><![CDATA[www.lofter.com]]></entry>
</match>
<group>
<entry fieldName="1,2" matchType="combine"><![CDATA[hot lomo]]></entry>
<pvItem>1</pvItem>
<uvItem>2</uvItem>
</group>
<group>
<entry fieldName="1,3" matchType="combine"><![CDATA[hot baidu]]></entry>
<pvItem>3</pvItem>
<uvItem>4</uvItem>
</group>
<group>
<entry fieldName="1" matchType="contain"><![CDATA[hot]]></entry>
<pvItem>5</pvItem>
<uvItem>6</uvItem>
</group>
</statUnit>


当然也可以支持更复杂的匹配逻辑,但这与我们的初衷相违背,我们的目标就是要近可能减少匹配次数降低复杂度。如此一来我们可以将一类URL放入一个统计配置 组中,然后组内的配置可以通过线性的时间处理完成。当然这带来了对于统计配置的额外处理的复杂度,但是这和需要处理的庞大日志量以及统计指标来说,是值得 的。

    P.S.
我们使用HadoopMapReduce来完成上述处理过程,当然除了对于流程本身的调优,还有Hadoop自身的一些设置,如禁用shuffle过程 中的排序操作,也可以带来程序整体的运行效率(前提是结果不关心排序)。为什么不使用Hive来处理?因为反复起Hive任务本身开销很大,同时数据冗余 太多,每次计算某一指标都会爬整个数据集。

本文来自网易实践者社区,经作者金川授权发布。