新來了個技術總監,居然要我做一個 IP 屬地功能。。不服就乾!

来源:https://www.cnblogs.com/javastack/archive/2022/08/12/16580853.html
-Advertisement-
Play Games

作者:ThinkingKeep 鏈接:https://juejin.cn/post/7118954784853327903 細心的朋友應該會發現,最近,繼新浪微博之後,頭條、騰訊、抖音、知乎、快手、小紅書等各大平臺陸陸續續都上線了“網路用戶IP地址顯示功能”,境外用戶顯示的是國家,國內的用戶顯示的省 ...


作者:ThinkingKeep
鏈接:https://juejin.cn/post/7118954784853327903

細心的朋友應該會發現,最近,繼新浪微博之後,頭條、騰訊、抖音、知乎、快手、小紅書等各大平臺陸陸續續都上線了“網路用戶IP地址顯示功能”,境外用戶顯示的是國家,國內的用戶顯示的省份,而且此項顯示無法關閉,歸屬地強制顯示。

作為技術人,那!這個功能要怎麼實現呢?

下麵,我就來講講,Java中是如何獲取IP屬地的,主要分為以下幾步:

  • 通過 HttpServletRequest 對象,獲取用戶的 IP 地址
  • 通過 IP 地址,獲取對應的省份、城市

首先需要寫一個 IP 獲取的工具類,因為每一次用戶的 Request 請求,都會攜帶上請求的 IP 地址放到請求頭中

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫

通過此方法,從請求Header中獲取到用戶的IP地址

目前本人在做的項目中,也有獲取IP地址歸屬地省份、城市的需求,用的是:淘寶IP庫

地址:ip.taobao.com/

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫

原來的請求源碼如下:

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫

可以看到日誌log文件中,大量的the request over max qps for user問題

再見ip.taobao,全網顯示 IP 歸屬地,快用這個開源庫


下麵,給大家介紹下之前在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 文件的查詢,單次查詢響應時間在十微秒級別,可通過如下兩種方式開啟記憶體加速查詢:

  1. vIndex 索引緩存 :使用固定的 512KiB 的記憶體空間緩存 vector index 數據,減少一次 IO 磁碟操作,保持平均查詢效率穩定在10-20微秒之間。
  2. 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.taobao,全網顯示 IP 歸屬地,快用這個開源庫

完全基於文件的查詢

ip屬地國內的話,會展示省份,國外的話,只會展示國家。可以通過如下圖這個方法進行進一步封裝,得到獲取IP屬地的信息。

再見ip.taobao,全網顯示 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最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 轉行做嵌入式也有一段時間了,原來做c#以及一些其它的上層語言, 本想的是也就是僅僅是語法上有點不一樣。但是實際使用的切身體會真的是只有自己才知道。很多方面刷新了我對c語言以及電腦結構體系的認知 ,絕對不僅僅是語法不一樣那麼簡單。 關於字元串傳遞函數引起的 一切源於給函數傳遞字元串變數這種 原來在其 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 1. 在 Spring Boot 中集成 Redis (1)完成配置基礎項。 添加 Redis、MySQL、MyBatis 依賴。 (2)配置MySQL、Redis伺服器 可以直接在application.yml文件中逬行配置,具體配置方法見以下代碼: 查看代碼 # 應用名稱 spring: red ...
  • 1、前言 fixture中文名翻譯為夾具,作用與上一篇中setup和teardown一致,都是用來做前後置處理的,但fixture更靈活更強大。它支持部分前後置,比如有時候我們不需要為每一條測試用例都添加前後置,使用setup和teardown要麼為整個模塊添加前後置,要麼為整個類,所有類方法,所有 ...
  • ​ /* *作者:呆萌老師 *☑csdn認證講師 *☑51cto高級講師 *☑騰訊課堂認證講師 *☑網易雲課堂認證講師 *☑華為開發者學堂認證講師 *☑愛奇藝千人名師計劃成員 *在這裡給大家分享技術、知識和生活 *各種乾貨,記得關註哦! *vx:it_daimeng */ 1 什麼是JSTL JST ...
  • 《對比Excel,輕鬆學習Python數據分析》免費下載地址 內容簡介 · · · · · · 集Python、Excel、數據分析為一體是本書的一大特色。 《對比Excel,輕鬆學習Python數據分析》圍繞整個數據分析的常規流程:熟悉工具—明確目的—獲取數據—熟悉數據—處理數據—分析數據—得出結 ...
  • 一、cffi cffi是連接Python與c的橋梁,可實現在Python中調用c文件。cffi為c語言的外部介面,在Python中使用該介面可以實現在Python中使用外部c文件的數據結構及函數。 二、直接在python中通過cffi定義c函數並使用 1、先通過pip3安裝cffi : pip3 i ...
  • 後面的ssm三個框架學的比較潦草,只要是這些東西都是一些配置文件和一些文件的固定寫法這些東西只有在老一點的軟體會用,現在大多數的都是用的springboot的寫的在後面的代碼審計裡面再去慢慢研究這些框架,這樣無實戰的敲框架代碼意義不大,框架學的潦草因為我是學安全的不是學開發的就算以後開發以是用spr... ...
一周排行
    -Advertisement-
    Play Games
  • 1.部署歷史 猿友們好,作為初來實習的我,已經遭受社會的“毒打”,所以請容許我在下麵環節適當吐槽,3Q! 傳統部署 ​ 回顧以往在伺服器部署webapi項目(非獨立發佈),dotnet環境、守護進程兩個逃都逃不掉,正常情況下還得來個nginx代理。不僅僅這仨,可能牽扯到yum或npm。node等都要 ...
  • 隨著技術的進步,跨平臺開發已經成為了標配,在此大背景下,ASP.NET Core也應運而生。本文主要基於ASP.NET Core+Element+Sql Server開發一個校園圖書管理系統為例,簡述基於MVC三層架構開發的常見知識點,前一篇文章,已經簡單介紹瞭如何搭建開發框架,和登錄功能實現,本篇... ...
  • 這道題只要會自定義cmp恰當地進行排序,其他部分沒有什麼大問題。 上代碼: 1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,s,h1,h2,cnt; 4 struct apple{ 5 int height,ns;//height為蘋 ...
  • 這篇文章主要描述RPC的路由策略,包括為什麼需要請求隔離,為什麼不在註冊中心中實現請求隔離以及不同粒度的路由策略。 ...
  • 簡介: 中介者模式,屬於行為型的設計模式。用一個中介對象來封裝一系列的對象交互。中介者是各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變他們之間的交互。 適用場景: 如果平行對象間的依賴複雜,可以使用中介者解耦。 優點: 符合迪米特法則,減少成員間的依賴。 缺點: 不適用於系統出現對 ...
  • 【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
  • 簡介: 享元模式,屬於結構型的設計模式。運用共用技術有效地支持大量細粒度的對象。 適用場景: 具有相同抽象但是細節不同的場景中。 優點: 把公共的部分分離為抽象,細節依賴於抽象,符合依賴倒轉原則。 缺點: 增加複雜性。 代碼: //用戶類 class User { private $name; fu ...
  • 這次設計一個通用的多位元組SPI介面模塊,特點如下: 可以設置為1-128位元組的SPI通信模塊 可以修改CPOL、CPHA來進行不同的通信模式 可以設置輸出的時鐘 狀態轉移圖和思路與多位元組串口發送模塊一樣,這裡就不給出了,具體可看該隨筆。 一、模塊代碼 1、需要的模塊 通用8位SPI介面模塊 `tim ...
  • AOP-03 7.AOP-切入表達式 7.1切入表達式的具體使用 1.切入表達式的作用: 通過表達式的方式定義一個或多個具體的連接點。 2.語法細節: (1)切入表達式的語法格式: execution([許可權修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]) 若目標類、介面與 ...
  • 測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...