日志处理

现在这份工作入职后,由于一些部门内的人事调动,一段时间内整个组里只有我和我老大两个员工,剩下的就是无穷无尽的面试了。由于技术面试至少2轮才能通过,所以第一面毫无疑问的落到了我的头上。

作为公司里级别最低的员工(仅仅高于实习生),我对面试别人这个事情是非常发怵的。问的太简单了,招进来的人不能干活;问的太难了,自己都不知道答案,更没法评论别人是否能够胜任了(就不说如果被别人问个问题自己不知道之后有多尴尬了)。后来,我找到了一个很讨巧的办法。

鉴于我们找人进来都是来干活的,我就把平时工作中遇到的问题拿出来抽象一下,然后找张纸给面试者。一面不需要关心你的离职原因,做人理念;也不关心你原来手底下有多少小弟,自己做的系统能支持多少并发;我只要过滤掉不能干活的人,剩下的交给我老大去评估好了。能干活的标准很简单:能解决问题,编码风格良好,有参数检查(诡异的是,90%的人都不会干这事);干活速度可接受。

这个系列里我会列出来一系列工作中遇到的“小问题”,基本都谈不到“算法”二字,最多算是“方法”。

首先介绍一个处理日志的小问题:

 

在一个lighttpd/apache的access log里,每一行是一条访问记录(例如格式是 “host ip time uri refer blahblah…”)。如果我有一个文件“access.log”,需要取出来访问次数最多的n个ip地址

 

如果熟悉bash,一句话就可以了:

Cat access.log | awk '{print $2}' | uniq -c | sort -n -r | head -n N #N 就是你想要的个数了

 

如果你用php写,也是很轻松的(伪代码,没有参数检查等等): 

 

<?php

$rst = array();
$file_handler = fopen('access.log', 'r');
if ($file_handler)
{
    $ip_cnt = array();
    $ln = fgets($file_handler);
    while ($ln !== false)
    {
        $parts = explode(' ', $ln, 3);
        $ip = $parts[2];
        if (isset($ip_cnt[$ip]))
        {
            $ip_cnt[$ip] = $ip_cnt[$ip] + 1;
        }
        else
        {
            $ip_cnt[$ip] = 0;
        }
        $ln = fgets($file_handler);
    }
    $rst = arsort($ip_cnt);
}
$rst = array_slice($rst, 0, $n);

   

 

这里面有比较多的问题,比如排序的时候如果有n个ip访问次数是一样的会如何处理等等。去除这些不谈,最大的问题是它会把全部的ip访问次数排序,这样做其实是没有必要的,因为我们只需要前面n个,不需要全部按顺序排好。所以扣除那些用来迷惑人的日志格式处理,这个问题变成了:

写道
从一个数组中取出n个最大的数字

 

也就等同于

写道
从一个数组中取出第n大的数字

 

看过《算法导论》前100页的人应该能看到(也就是看过算法导论的绝大部份人,惭愧),这就是一个快排的问题,完全没有必要把整个数组排序。

后面是一个使用python但是一点都不pythonic的实现,考虑了有次数相等的情况(为了简化,文件里一行就是一个ip地址):

 

#!/usr/bin/python
# -*- encoding=utf-8 -*-
"""  """

import random

def get_top_n(items, cnt, cmp_func, pre_val):
    lst_len = len(items)
    if lst_len <= cnt:
        pre_val = pre_val or []
        pre_val.extend(items)
        return pre_val

    # get a pivot in qsort
    pivot_idx = random.randrange(0, lst_len)
    big_lst = [item for item in items
                    if (cmp_func(item, items[pivot_idx]) > 0)]
    big_lst_len = len(big_lst)

    if big_lst_len >= cnt:
        return get_top_n(big_lst, cnt, cmp_func, pre_val)
    else:
        pre_val = pre_val or []
        pre_val.extend(big_lst)

        #equal cases
        equal_lst = [item for item in items
                          if (cmp_func(item, items[pivot_idx]) == 0)]
        pre_val.extend(equal_lst)

        equal_lst_len = len(equal_lst)
        if (big_lst_len + equal_lst_len) >= cnt:
            return pre_val

        sml_lst = [item for item in items
                        if (cmp_func(item, items[pivot_idx]) < 0)]

        return get_top_n(sml_lst, (cnt - big_lst_len - equal_lst_len),
                         cmp_func, pre_val)

def ip_cmp_func(x, y):
    if x[1] == y[1]:
        return 0
    return x[1] > y[1] and 1 or -1

if __name__ == '__main__':
    f = open('ip_only.log', 'r')
    ip_cnt_dict = {}
    for line in f.xreadlines():
        if not line:
            continue
        line = line[:-1]
        cnt = ip_cnt_dict.get(line, 0)
        #make a {ip: cnt} dict
        ip_cnt_dict[line] = cnt + 1
    f.close()

    # make a (ip, cnt) list
    ip_list = [(ip_addr, ip_cnt) for ip_addr, ip_cnt in ip_cnt_dict.iteritems()]
    # get top 100 ips
    rst = get_top_n(ip_list, 100, ip_cmp_func, None)
    # sort the result
    srt_rst = sorted(rst, key=lambda x: -x[1])

    for ip_addr, pv in srt_rst:
        print 'IP: %s\tPV: %s' %(ip_addr, pv)

 

还有几行测试文件

 

#!/usr/bin/python
# -*- encoding=utf-8 -*-
"""  """

import random

if __name__ == '__main__':
    for k in xrange(100000):
        ip = '%s.%s.%s.%s' % (random.randrange(0, 255),
                              random.randrange(0, 255),
                              random.randrange(0, 255),
                              random.randrange(0, 255))
        print ip
        if (k % 100) == 0:
            for k in range(k % 199):
                print ip

 

可以用下面的命令生成一堆日志,然后测试下处理时间

chmod +x *.py
for cnt in $(seq 5); do ./mklog.py > ip_only.log; ./get_top_ip.py | nl;echo '\n'; done

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值