这段时间一直在考虑替换ip库。
公司的业务中需要对ip归属地的准确性要求很高,之前的ip网段表已经不能满足日渐庞大的全国终端的ip检测。这个工作交到我手里后,leader让我去解析dat出ip段,方便入库查询,也是走之前查表的老路。
我拿到开源的dat和php接口文件,仔细看了一下解析代码,发现并不是很规范的解析,也不能直接套用qq纯真ip的dat的解析方式。不能完全解析,那么就不能弄出ip段,一时我陷入了困局。我没忘记和耀哥讨论,他提醒了我:为何一定要入库,对方提供的dat为二进制文件,直接利用接口查询,里面的索引足够快了。
入库的好处在于可以支持其他复杂查询,但是目前来看,这里只是做根据某个ip查出归属地的要求,接口解析dat足够了。鉴于之前的经验,我在需求讨论会议里面提出了自己的想法,然后分步骤去完成这个方案。
在讨论的过程里面,我从需求本质出发,首先要确定这个开源ip库的数据是否准确,然后再是采用入库还是dat,或者后期加缓存来做。
我先用所有用户登录的log表里面ip去遍历走开源接口读取dat,每次循环中我同时也查询138(百度ip库)接口的数据,两者写入txt文件,以逗号分隔,方便转为csv查看结果。关键性代码如下
// 所有符合条件的IP $ipArr = $this->db->select($sql); $all_ips = array(); foreach ($ipArr as $k) { array_push($all_ips, $k['soft_last_login_ip']); } if (!empty($exist_ips)) { $exist_ip_arr = array(); foreach ($exist_ips as $k) { $exist_ip_arr[] = $k['ip']; } $all_ips = array_diff($all_ips, $exist_ip_arr); } $all_ips = array_filter($all_ips); // clean out all if (empty($all_ips)) exit('已经没有ip需要入库了'); $ipUrl = 'http://www.xxxx.com/index.html'; // xxxx的查询地址 $url = 'http://www.ip138.com/ips138.asp'; // 138baidu的查询地址 $post_data = array(); //var_dump($ipArr);die(); foreach ($all_ips as $i) { $ip = $i; $post_data['ip'] = $ip; $res = $this->request_post($ipUrl, $post_data); $addressInfo = $this->matchAddress($res); $address = trim($addressInfo[0]); $response = sendRequest($url, array('ip' => $ip, 'action' => 2)); $response = mb_convert_encoding($response, "UTF-8", "gb2312"); $zhengze = '/<li>本站主数据:(.*?)</li>/'; preg_match_all($zhengze, $response, $result); if (count($result[1]) > 0) { // some codes to make the data sctruct
$this->db->insert('ip_ip_test', $row, true); usleep(mt_rand(100000, 2000000)); } }
其中 request_post func和sendRequest是curl方式发送request获取资源内容,后面matchAddress就是利用正则匹配出归属地字符串,这些知识点在之前的一篇聊php curl模拟请求的blog里面有写,不再赘述。
不过其中我在做curl模拟发请求的时候,发现有些网站对模拟出来的请求会报403 forbidden,这个需要仔细排查,是不是自己的cookie数据没有加入到请求里面,还是referer没有指定。最好是把header中的referer设置为原网站的某个站内链接。
在爬数据的时候,我还遇到个问题,有些数据是页面加载后发ajax请求获得的,我到现在写这篇文章之时也没想到能抓取页面ajax之后的数据的方法。
之后我对比了开源库和138的ip归属地,基本一致。于是和公司同事讨论,决定用这个开源的ip库,并后期使用高级版。
那么关于数据来源的处理问题又来了,到底是入库,还是直接用dat文件。首先dat文件有几种更新周期,公司预计会使用一周更新一次的版本。如果入库,可以批量初始化写入mysql,当每周更新的时候再通过脚本批量更新database。目前ip数目大概在30万左右,更新也只需要10到20分钟之间,因为做了主从,所以也不用担心读写压力。我思考了一下,根据业务,肯定是查询频率远大于写入,所以建存储的表用myisam.
如果是dat,它本来就是一个数据库,直接替换原来的查询来源即可,修改的地方集中在checkIp这个func上。还要考虑异地机房网络消耗和并发情况,做一个需求得全面思考。
leader这次协助我一起思考方案,决定把每一种方式都写一个测试接口,利用高并发压力测试来看哪一种方式的性能更好。
于是我写好了三个测试接口:读取mysql,读取dat,读取redis(初始化写入了内存)的方式。
利用压测工具webbench,逐步测试200个并发,1000个并发,10000个并发条件下的吞吐读取速度。
其中dat的某次结果如下:
webbench -t 50 -c 10000 http://interface.xxx.com/test/testGetIPByDat
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Benchmarking: GET http://interface.xxx.com/test/testGetIPByDat
10000 clients, running 50 sec.
Speed=337046 pages/min, 1979493 bytes/sec.
Requests: 280872 susceed, 0 failed.
测试出真知,速度由快到慢,最终结果为:redis > dat > mysql。 dat的速度比redis差,但是如果直接使用它不用每周还手动去更新redis里面的key,成本最低。
一步一步,数据为王,测试为证。虽然这次花时间不少,不过也收获了严谨求实的做事方式,真正的需求和解决方案是讨论出来的。感谢所有帮助我的人,leader,同事,耀哥。我的每一次进步,都心存感激。