一、前言
有做過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
表現還不錯。呵呵
結束了,有什麼更好的方式可以一起討論下,謝謝~