一、前言
有做過IP歸屬地查詢功能的朋友應該都有聽說過純真IP庫,純真IP庫查詢類似這樣:
若你僅需要根據IP搜索出用戶的歸屬地文字然后顯示出來,只要按照該IP庫的規則進行二分查找並顯示就OK了。(格式詳解)
但如果你需要根據IP獲取歸屬地文字描述,然后進一步與自己已有的行政地區數據表關聯起來該如何處理呢?
- 這樣? 根據IP獲取歸屬地文字描述 -> 正則獲取地區名 -> 根據地區名去數據庫或緩存中獲取地區ID
- 還是這樣?將IP庫數據導入數據庫中(ip_start, ip_end, area_code) -> SQL查找
粗看這兩種應該是都可以實現,但是效率呢?都很差!特別是面對並發稍高的應用,這兩種方式都經不起考驗。
為什么不根據純真IP庫(其他IP庫也可以)的數據與自己的地區數據關聯起來,用自己的地區ID來代替純真IP庫的地區描述,最后制作一個自己的二進制IP庫文件呢?
讓我們進入正題,看看如何根據純真IP庫數據制作一個自己的二進制IP庫文件。
注:本文只說明大致思路,沒有詳細代碼,謝謝
二、准備工作
我們需要准備好兩部分的數據:
1. 純真IP庫解壓后的txt文件。
純真IP庫下載后會有個ip.exe工具,使用上面的解壓即可生成。
生成的數據如圖1-1,我這個版本有大概444290條。
圖1-1
2. 自己的國家省市級聯數據表。
這個網上應該比較多,自己進行導入,表結構類似(area_id, area_level, area_name, area_pid),分別代表地區ID,地區等級,地區名稱,父地區ID。
當然你也可以自己使用不同的結構,不影響我們這次的處理。
三、過程
數據已經有了,現在來規划下我們需要生成的IP庫的機構。
從標題中就知道,我們需要生成的IP庫是二進制的數據包,而不是普通文本文件,那么我們的IP庫文件結構應該是怎樣的呢?
如圖所示:
可以看到,我們的結構是這樣的:
- 頭部。位於文件的前8個字節。前4字節存放32位整數,值為數據部分的開始在文件中的位置;后4字節也存放32位整數,值為數據部分的結束在文件中的位置
- 主體數據部分。由N個固定結構體組成,每個結構體12字節,為一條IP范圍數據(ip_start, ip_end, area_code)。結構體的三個部分也分別為32位有符號整數,各4字節。(area_code若是量小的話也可以使用1個字符)
IP數據包的結構已經定下來了,后面就是一步步處理了。
1. 逐條讀取IP文本文件內容,IP轉為32位有符號整數(自定義的ip2long),地區文字分析獲取到最終地區
a. IP文本文件每行的規則為:前15字節為IP起始地址,后15字節為IP結束地址,最后為地區文字描述。
b. IP轉為32位有符號整數只占4字節,且解決了PHP函數ip2long在32位與64位系統下值不同的問題,新的函數如下:
function ip2Long32($ip) { $ip = unpack('l', pack('l', ip2long($ip))); return $ip[1]; } // end func
當然,你也可以自己開發PHP擴展,詳見這邊:http://www.cnblogs.com/iblaze/archive/2013/06/02/3112603.html
c. 地區需要獲取到各級別地區名稱(包括省、市、縣、區等,這邊國外只保留國家),正則如圖:
2. 將獲取到的地區信息轉為地區ID
這部分處理我不太好描述,因為可能每個人用到的地區都不一樣,但是大致原理就是先根據最低級地區名稱去查找ID(看實際情況,有可能要去掉市、縣之類),若是沒有則查找上一級,如此循環,直到獲取到地區ID。
若是沒有查找到地區ID,則都歸入未知。
3. 壓縮,壓縮后的文件約為5.08M
壓縮規則如圖,format中的值對應pack中的類型:
這邊有個地方必須提示下,由於IP轉為有符號32位整數,則128.0.0.0以后的IP都會為負數,所以需要判斷負數,並放入我們IP庫的前面去,畢竟是使用二分查找,需要為有序數據。
4. 查找IP,使用二分查找,44W條數據最多只需要搜索19次,類似如下:
4. 單個測試,看起來速度還可以
5. 簡單壓測看效果
a. ab壓測,使用本機的ab
b. 測試腳本在linux測試機(普通PC機)
c. 壓測腳本如下:
d. 壓測語句: ab -n 10000 -c 50 http://192.168.206.71/ipdata.php?type=php
表現還不錯。呵呵
結束了,有什么更好的方式可以一起討論下,謝謝~