作者:ThinkingKeep 鏈接:https://juejin.cn/post/7118954784853327903 細心的朋友應該會發現,最近,繼新浪微博之後,頭條、騰訊、抖音、知乎、快手、小紅書等各大平臺陸陸續續都上線了“網路用戶IP地址顯示功能”,境外用戶顯示的是國家,國內的用戶顯示的省 ...
作者:ThinkingKeep
鏈接:https://juejin.cn/post/7118954784853327903
細心的朋友應該會發現,最近,繼新浪微博之後,頭條、騰訊、抖音、知乎、快手、小紅書等各大平臺陸陸續續都上線了“網路用戶IP地址顯示功能”,境外用戶顯示的是國家,國內的用戶顯示的省份,而且此項顯示無法關閉,歸屬地強制顯示。
作為技術人,那!這個功能要怎麼實現呢?
下麵,我就來講講,Java中是如何獲取IP屬地的,主要分為以下幾步:
- 通過 HttpServletRequest 對象,獲取用戶的 IP 地址
- 通過 IP 地址,獲取對應的省份、城市
首先需要寫一個 IP 獲取的工具類,因為每一次用戶的 Request 請求,都會攜帶上請求的 IP 地址放到請求頭中
通過此方法,從請求Header中獲取到用戶的IP地址
目前本人在做的項目中,也有獲取IP地址歸屬地省份、城市的需求,用的是:淘寶IP庫
地址:ip.taobao.com/
原來的請求源碼如下:
可以看到日誌log文件中,大量的the request over max qps for user問題
下麵,給大家介紹下之前在Github衝浪時發現的今天的主角:
目前最新已更新到了v2.0版本,ip2region v2.0是一個離線IP地址定位庫和IP定位數據管理框架,10微秒級別的查詢效率,準提供了眾多主流編程語言的 xdb 數據生成和查詢客戶端實現。
99.9%準確率:
數據聚合了一些知名ip到地名查詢提供商的數據,這些是他們官方的的準確率,經測試著實比經典的純真IP定位準確一些。
多查詢客戶端的支持
已經集成的客戶端有:java、C#、php、c、python、nodejs、php擴展(php5和php7)、golang、rust、lua、lua_c, nginx。
binding | 描述 | 開髮狀態 | binary查詢耗時 | b-tree查詢耗時 | memory查詢耗時 |
---|---|---|---|---|---|
c | ANSC c binding | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
c# | c# binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
golang | golang binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
java | java binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
lua | lua實現的binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
lua_c | lua的c擴展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
nginx | nginx的c擴展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
nodejs | nodejs | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
php | php實現的binding | 已完成 | 0.x毫秒 | 0.1x毫秒 | 0.1x毫秒 |
php5_ext | php5的c擴展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
php7_ext | php7的c擴展 | 已完成 | 0.0毫秒 | 0.0x毫秒 | 0.00x毫秒 |
python | python bindng | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
rust | rust binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
Ip2region V2.0 特性
1、標準化的數據格式
每個 ip 數據段的 region 信息都固定了格式:國家|區域|省份|城市|ISP,只有中國的數據絕大部分精確到了城市,其他國家部分數據只能定位到國家,後前的選項全部是0。
2、數據去重和壓縮
xdb 格式生成程式會自動去重和壓縮部分數據,預設的全部 IP 數據,生成的 ip2region.xdb 資料庫是 11MiB,隨著數據的詳細度增加資料庫的大小也慢慢增大。
3、極速查詢響應
即使是完全基於 xdb 文件的查詢,單次查詢響應時間在十微秒級別,可通過如下兩種方式開啟記憶體加速查詢:
- vIndex 索引緩存 :使用固定的 512KiB 的記憶體空間緩存 vector index 數據,減少一次 IO 磁碟操作,保持平均查詢效率穩定在10-20微秒之間。
- xdb 整個文件緩存:將整個 xdb 文件全部載入到記憶體,記憶體占用等同於 xdb 文件大小,無磁碟 IO 操作,保持微秒級別的查詢效率。
4、極速查詢響應
v2.0 格式的 xdb 支持億級別的 IP 數據段行數,region 信息也可以完全自定義,例如:你可以在 region 中追加特定業務需求的數據,例如:GPS信息/國際統一地域信息編碼/郵編等。也就是你完全可以使用 ip2region 來管理你自己的 IP 定位數據。
ip2region xdb java 查詢客戶端實現
- 使用方式
引入maven倉庫:
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.4</version>
</dependency>
- 完全基於文件的查詢
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.*;
import java.util.concurrent.TimeUnit;
public class SearcherTest {
public static void main(String[] args) {
// 1、創建 searcher 對象
String dbPath = "ip2region.xdb file path";
Searcher searcher = null;
try {
searcher = Searcher.newWithFileOnly(dbPath);
} catch (IOException e) {
System.out.printf("failed to create searcher with `%s`: %s\n", dbPath, e);
return;
}
// 2、查詢
try {
String ip = "1.2.3.4";
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
// 3、備註:併發使用,每個線程需要創建一個獨立的 searcher 對象單獨使用。
}
}
- 緩存VectorIndex索引
我們可以提前從 xdb 文件中載入出來 VectorIndex 數據,然後全局緩存,每次創建 Searcher 對象的時候使用全局的 VectorIndex 緩存可以減少一次固定的 IO 操作,從而加速查詢,減少 IO 壓力。
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.*;
import java.util.concurrent.TimeUnit;
public class SearcherTest {
public static void main(String[] args) {
String dbPath = "ip2region.xdb file path";
// 1、從 dbPath 中預先載入 VectorIndex 緩存,並且把這個得到的數據作為全局變數,後續反覆使用。
byte[] vIndex;
try {
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
return;
}
// 2、使用全局的 vIndex 創建帶 VectorIndex 緩存的查詢對象。
Searcher searcher;
try {
searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
} catch (Exception e) {
System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
return;
}
// 3、查詢
try {
String ip = "1.2.3.4";
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
// 備註:每個線程需要單獨創建一個獨立的 Searcher 對象,但是都共用全局的制度 vIndex 緩存。
}
}
- 緩存整個xdb數據
我們也可以預先載入整個 ip2region.xdb 的數據到記憶體,然後基於這個數據創建查詢對象來實現完全基於文件的查詢,類似之前的 memory search。
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.*;
import java.util.concurrent.TimeUnit;
public class SearcherTest {
public static void main(String[] args) {
String dbPath = "ip2region.xdb file path";
// 1、從 dbPath 載入整個 xdb 到記憶體。
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load content from `%s`: %s\n", dbPath, e);
return;
}
// 2、使用上述的 cBuff 創建一個完全基於記憶體的查詢對象。
Searcher searcher;
try {
searcher = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
System.out.printf("failed to create content cached searcher: %s\n", e);
return;
}
// 3、查詢
try {
String ip = "1.2.3.4";
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
// 備註:併發使用,用整個 xdb 數據緩存創建的查詢對象可以安全的用於併發,也就是你可以把這個 searcher 對象做成全局對象去跨線程訪問。
}
}
IDEA中做個測試
完全基於文件的查詢
ip屬地國內的話,會展示省份,國外的話,只會展示國家。可以通過如下圖這個方法進行進一步封裝,得到獲取IP屬地的信息。
下麵是官網給出的命令運行jar方式給出的測試demo,可以理解下
編譯測試程式
通過 maven 來編譯測試程式。
# cd 到 java binding 的根目錄
cd binding/java/
mvn compile package
然後會在當前目錄的 target 目錄下得到一個 ip2region-{version}.jar 的打包文件。
查詢測試
可以通過 java -jar ip2region-{version}.jar search 命令來測試查詢:
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar search
java -jar ip2region-{version}.jar search [command options]
options:
--db string ip2region binary xdb file path
--cache-policy string cache policy: file/vectorIndex/content
例如:使用預設的 data/ip2region.xdb 文件進行查詢測試:
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar search --db=../../data/ip2region.xdb
ip2region xdb searcher test program, cachePolicy: vectorIndex
type 'quit' to exit
ip2region>> 1.2.3.4
{region: 美國|0|華盛頓|0|谷歌, ioCount: 7, took: 82 μs}
ip2region>>
輸入 ip 即可進行查詢測試,也可以分別設置 cache-policy 為 file/vectorIndex/content 來測試三種不同緩存實現的查詢效果。
bench 測試
可以通過 java -jar ip2region-{version}.jar bench 命令來進行 bench 測試,一方面確保 xdb 文件沒有錯誤,一方面可以評估查詢性能:
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar bench
java -jar ip2region-{version}.jar bench [command options]
options:
--db string ip2region binary xdb file path
--src string source ip text file path
--cache-policy string cache policy: file/vectorIndex/content
例如:通過預設的 data/ip2region.xdb 和 data/ip.merge.txt 文件進行 bench 測試:
➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
Bench finished, {cachePolicy: vectorIndex, total: 3417955, took: 8s, cost: 2 μs/op}
可以通過分別設置 cache-policy 為 file/vectorIndex/content 來測試三種不同緩存實現的效果。 @Note: 註意 bench 使用的 src 文件要是生成對應 xdb 文件相同的源文件。
到這裡獲取用戶IP屬地已經完成啦,這篇文章介紹的v2.0版本,有興趣的小伙伴可以登錄上門的github地址瞭解下v1.0版本
如若覺得有用,歡迎收藏+點贊,如遇到什麼問題,歡迎留言討論
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!