<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          新來(lái)個(gè)技術(shù)總監(jiān)要我做一個(gè) IP 屬地功能~

          共 10532字,需瀏覽 22分鐘

           ·

          2022-08-01 06:52

          文章來(lái)源:juejin.cn/post/7118954784853327903


          背景


          細(xì)心的朋友應(yīng)該會(huì)發(fā)現(xiàn),最近,繼新浪微博之后,頭條、騰訊、抖音、知乎、快手、小紅書(shū)等各大平臺(tái)陸陸續(xù)續(xù)都上線了“網(wǎng)絡(luò)用戶 IP 地址顯示功能”,境外用戶顯示的是國(guó)家,國(guó)內(nèi)的用戶顯示的省份,而且此項(xiàng)顯示無(wú)法關(guān)閉,歸屬地強(qiáng)制顯示。


          作為技術(shù)人,那!這個(gè)功能要怎么實(shí)現(xiàn)呢?


          HttpServletRequest 獲取 IP


          下面,我就來(lái)講講,Java 中是如何獲取 IP 屬地的,主要分為以下幾步:


          • 通過(guò) HttpServletRequest 對(duì)象,獲取用戶的 「IP」 地址

          • 通過(guò) IP 地址,獲取對(duì)應(yīng)的省份、城市


          首先需要寫(xiě)一個(gè) IP 獲取的工具類,因?yàn)槊恳淮斡脩舻?Request 請(qǐng)求,都會(huì)攜帶上請(qǐng)求的 IP 地址放到請(qǐng)求頭中。


          import javax.servlet.http.HttpServletRequest;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.UnknownHostException;
          /** * 常用獲取客戶端信息的工具 */public class NetworkUtil {
          /** * 獲取ip地址 */ public static String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 本機(jī)訪問(wèn) if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)){ // 根據(jù)網(wǎng)卡取本機(jī)配置的IP InetAddress inet; try { inet = InetAddress.getLocalHost(); ip = inet.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } } // 對(duì)于通過(guò)多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割 if (null != ip && ip.length() > 15) { if (ip.indexOf(",") > 15) { ip = ip.substring(0, ip.indexOf(",")); } } return ip;    }
          /** * 獲取mac地址 */ public static String getMacAddress() throws Exception { // 取mac地址 byte[] macAddressBytes = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress(); // 下面代碼是把mac地址拼裝成String StringBuilder sb = new StringBuilder(); for (int i = 0; i < macAddressBytes.length; i++) { if (i != 0) { sb.append("-"); } // mac[i] & 0xFF 是為了把byte轉(zhuǎn)化為正整數(shù) String s = Integer.toHexString(macAddressBytes[i] & 0xFF); sb.append(s.length() == 1 ? 0 + s : s); } return sb.toString().trim().toUpperCase();    }}


          通過(guò)此方法,從請(qǐng)求 Header 中獲取到用戶的 IP 地址。


          之前我在做的項(xiàng)目中,也有獲取 IP 地址歸屬地省份、城市的需求,用的是:淘寶 IP 庫(kù),地址:


          https://ip.taobao.com/



          taobao 的 ip 庫(kù)下線了,再見(jiàn) ip.taobao,全網(wǎng)顯示 IP 歸屬地。



          ip 歸屬地,原來(lái)的請(qǐng)求源碼如下:




          可以看到日志 log 文件中,大量的 the request over max qps for user 問(wèn)題。


          留下了難過(guò)的淚水。


          Ip2region


          下面,給大家介紹下之前在 Github 沖浪時(shí)發(fā)現(xiàn)的今天的主角:Ip2region 開(kāi)源項(xiàng)目。


          github 地址:https://github.com/lionsoul2014/ip2region


          目前最新已更新到了 v2.0 版本,ip2region v2.0 是一個(gè)離線 IP 地址定位庫(kù)和 IP 定位數(shù)據(jù)管理框架,10 微秒級(jí)別的查詢效率,準(zhǔn)提供了眾多主流編程語(yǔ)言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn)。


          ①99.9% 準(zhǔn)確率


          數(shù)據(jù)聚合了一些知名 ip 到地名查詢提供商的數(shù)據(jù),這些是他們官方的的準(zhǔn)確率,經(jīng)測(cè)試著實(shí)比經(jīng)典的純真 IP 定位準(zhǔn)確一些。


          ip2region 的數(shù)據(jù)聚合自以下服務(wù)商的開(kāi)放 API 或者數(shù)據(jù)(升級(jí)程序每秒請(qǐng)求次數(shù) 2 到 4 次):


          • 01,>80%,淘寶IP地址庫(kù),http://ip.taobao.com/%5C

          • 02,≈10%,GeoIP,https://geoip.com/%5C

          • 03,≈2%,純真 IP 庫(kù),http://www.cz88.net/%5C


          備注:如果上述開(kāi)放 API 或者數(shù)據(jù)都不給開(kāi)放數(shù)據(jù)時(shí) ip2region 將停止數(shù)據(jù)的更新服務(wù)。


          ②多查詢客戶端的支持


          已經(jīng)集成的客戶端有:java、C#、php、c、python、nodejs、php擴(kuò)展(php5 和 php7)、golang、rust、lua、lua_c,nginx。



          Ip2region V2.0 特性


          ①標(biāo)準(zhǔn)化的數(shù)據(jù)格式


          每個(gè) ip 數(shù)據(jù)段的 region 信息都固定了格式:國(guó)家|區(qū)域|省份|城市|ISP,只有中國(guó)的數(shù)據(jù)絕大部分精確到了城市,其他國(guó)家部分?jǐn)?shù)據(jù)只能定位到國(guó)家,后前的選項(xiàng)全部是 0。


          ②數(shù)據(jù)去重和壓縮


          xdb 格式生成程序會(huì)自動(dòng)去重和壓縮部分?jǐn)?shù)據(jù),默認(rèn)的全部 IP 數(shù)據(jù),生成的 ip2region.xdb 數(shù)據(jù)庫(kù)是 11MiB,隨著數(shù)據(jù)的詳細(xì)度增加數(shù)據(jù)庫(kù)的大小也慢慢增大。


          ③極速查詢響應(yīng)


          即使是完全基于 xdb 文件的查詢,單次查詢響應(yīng)時(shí)間在十微秒級(jí)別。


          可通過(guò)如下兩種方式開(kāi)啟內(nèi)存加速查詢:


          • vIndex 索引緩存:使用固定的 512KiB 的內(nèi)存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤(pán)操作,保持平均查詢效率穩(wěn)定在 10-20 微秒之間。

          • xdb 整個(gè)文件緩存:將整個(gè) xdb 文件全部加載到內(nèi)存,內(nèi)存占用等同于 xdb 文件大小,無(wú)磁盤(pán) IO 操作,保持微秒級(jí)別的查詢效率。


          ④極速查詢響應(yīng)


          v2.0 格式的 xdb 支持億級(jí)別的 IP 數(shù)據(jù)段行數(shù),region 信息也可以完全自定義,例如:你可以在 region 中追加特定業(yè)務(wù)需求的數(shù)據(jù),例如:GPS信息/國(guó)際統(tǒng)一地域信息編碼/郵編等。也就是你完全可以使用 ip2region 來(lái)管理你自己的 IP 定位數(shù)據(jù)。


          ip2region xdb java 查詢客戶端實(shí)現(xiàn)


          ①使用方式


          引入 maven 倉(cāng)庫(kù):


          <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、創(chuàng)建 searcher 對(duì)象 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、備注:并發(fā)使用,每個(gè)線程需要?jiǎng)?chuàng)建一個(gè)獨(dú)立的 searcher 對(duì)象單獨(dú)使用。 }}


          ③緩存 VectorIndex 索引


          我們可以提前從 xdb 文件中加載出來(lái) VectorIndex 數(shù)據(jù),然后全局緩存,每次創(chuàng)建 Searcher 對(duì)象的時(shí)候使用全局的 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 中預(yù)先加載 VectorIndex 緩存,并且把這個(gè)得到的數(shù)據(jù)作為全局變量,后續(xù)反復(fù)使用。 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 創(chuàng)建帶 VectorIndex 緩存的查詢對(duì)象。 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);        }
          // 備注:每個(gè)線程需要單獨(dú)創(chuàng)建一個(gè)獨(dú)立的 Searcher 對(duì)象,但是都共享全局的制度 vIndex 緩存。 }}


          ④緩存整個(gè) xdb 數(shù)據(jù)


          我們也可以預(yù)先加載整個(gè) ip2region.xdb 的數(shù)據(jù)到內(nèi)存,然后基于這個(gè)數(shù)據(jù)創(chuàng)建查詢對(duì)象來(lái)實(shí)現(xiàn)完全基于文件的查詢,類似之前的 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 加載整個(gè) xdb 到內(nèi)存。 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 創(chuàng)建一個(gè)完全基于內(nèi)存的查詢對(duì)象。 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);        }
          // 備注:并發(fā)使用,用整個(gè) xdb 數(shù)據(jù)緩存創(chuàng)建的查詢對(duì)象可以安全的用于并發(fā),也就是你可以把這個(gè) searcher 對(duì)象做成全局對(duì)象去跨線程訪問(wèn)。 }}


          IDEA 中做個(gè)測(cè)試



          ①完全基于文件的查詢


          ip 屬地國(guó)內(nèi)的話,會(huì)展示省份,國(guó)外的話,只會(huì)展示國(guó)家。可以通過(guò)如下圖這個(gè)方法進(jìn)行進(jìn)一步封裝,得到獲取 IP 屬地的信息。



          下面是官網(wǎng)給出的命令運(yùn)行 jar 方式給出的測(cè)試 demo,可以理解下。


          ②編譯測(cè)試程序


          通過(guò) maven 來(lái)編譯測(cè)試程序。


          # cd 到 java binding 的根目錄cd binding/java/mvn compile package


          然后會(huì)在當(dāng)前目錄的 target 目錄下得到一個(gè) ip2region-{version}.jar 的打包文件。


          ③查詢測(cè)試


          可以通過(guò) java -jar ip2region-{version}.jar search 命令來(lái)測(cè)試查詢:


          ?  java git:(v2.0_xdb) ? java -jar target/ip2region-2.6.0.jar searchjava -jar ip2region-{version}.jar search [command options]options: --db string              ip2region binary xdb file path --cache-policy string    cache policy: file/vectorIndex/content


          例如:使用默認(rèn)的 data/ip2region.xdb 文件進(jìn)行查詢測(cè)試:


          ?  java git:(v2.0_xdb) ? java -jar target/ip2region-2.6.0.jar search --db=../../data/ip2region.xdbip2region xdb searcher test program, cachePolicy: vectorIndextype 'quit' to exitip2region>> 1.2.3.4{region: 美國(guó)|0|華盛頓|0|谷歌, ioCount: 7, took: 82 μs}ip2region>>


          輸入 ip 即可進(jìn)行查詢測(cè)試,也可以分別設(shè)置 cache-policy 為 file/vectorIndex/content 來(lái)測(cè)試三種不同緩存實(shí)現(xiàn)的查詢效果。


          ④bench 測(cè)試


          可以通過(guò) java -jar ip2region-{version}.jar bench 命令來(lái)進(jìn)行 bench 測(cè)試,一方面確保 xdb 文件沒(méi)有錯(cuò)誤,一方面可以評(píng)估查詢性能:


          ?  java git:(v2.0_xdb) ? java -jar target/ip2region-2.6.0.jar benchjava -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


          例如:通過(guò)默認(rèn)的 data/ip2region.xdb 和 data/ip.merge.txt 文件進(jìn)行 bench 測(cè)試:


          ?  java git:(v2.0_xdb) ? java -jar target/ip2region-2.6.0.jar bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txtBench finished, {cachePolicy: vectorIndex, total: 3417955, took: 8s, cost: 2 μs/op}


          可以通過(guò)分別設(shè)置 cache-policy 為 file/vectorIndex/content 來(lái)測(cè)試三種不同緩存實(shí)現(xiàn)的效果。


          @Note:注意 bench 使用的 src 文件要是生成對(duì)應(yīng) xdb 文件相同的源文件。

          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级A级毛片 | 91无码内射 | 91综合娱乐 | 青青操久久 | 亚洲日韩国产剧情自制在线观看 |