文章目录
- [一、Ip2region 是什么](#一、Ip2region 是什么)
- [二、ip2region xdb 生成实现](#二、ip2region xdb 生成实现)
- 三、Java使用ip2region
-
- 1.引入依赖
- 2.完全基于文件的查询
- [3.缓存 VectorIndex 索引](#3.缓存 VectorIndex 索引)
- [4.缓存整个 xdb 数据](#4.缓存整个 xdb 数据)
- 5.将ip2region.xdb放在resource
一、Ip2region 是什么
Ip2region 一个 github 的开源项目,即 Ip2region 开源项目。
项目地址:https://github.com/lionsoul2014/ip2region
ip2region - 是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的 xdb
数据生成和查询客户端实现。
ip2region是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别
已经有的客户端:Java、C#、php、C、Python、Node.js、PHP 拓展(PHP 5 和 PHP 7)等;
二、ip2region xdb 生成实现
如果不想生成可以直接使用github下载
https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb
下载Ip2region项目
1.编译安装
通过 maven 来编译可运行 jar 程序:
bash
# cd 到 maker/java 根目录
mvn clean compile package
然会会在当前目录的 target 目录下得到一个 ip2region-maker-{version}.jar 的打包文件。
2.数据生成
通过 java -jar ip2region-maker-{version}.jar 来生成 ip2region.xdb 二进制文件:
bash
➜ java git:(java_xdb_maker) ✗ java -jar ./target/ip2region-maker-1.0.0.jar
ip2region xdb maker
java -jar ip2region-maker-{version}.jar [command options]
options:
--src string source ip text file path
--dst string destination binary xdb file path
例如,通过默认的 data/ip.merge.txt 原数据,在当前目录生成一个 ip2region.xdb 二进制文件:
bash
➜ java git:(java_xdb_maker) ✗ java -jar ./target/ip2region-maker-1.0.0.jar --src=../../data/ip.merge.txt --dst=./ip2region.xdb
# 会看到一堆输出,最终会看到如下输出表示运行成功
...
2022-07-15 20:21:29 INFO org.lionsoul.ip2region.xdb.Maker try to write the vector index block ...
2022-07-15 20:21:29 INFO org.lionsoul.ip2region.xdb.Maker try to write the segment index ptr ...
2022-07-15 20:21:29 INFO org.lionsoul.ip2region.xdb.Maker write done, dataBlocks: 13804, indexBlocks: (683591, 720221), indexPtr: (982904, 11065984)
2022-07-15 20:21:29 INFO org.lionsoul.ip2region.MakerTest Done, elapsed: 50 s
三、Java使用ip2region
1.引入依赖
bash
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
在Maven里面添加依赖,防止把这个xdb文件编译:
bash
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xdb</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
2.完全基于文件的查询
java
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.close();
// 备注:并发使用,每个线程需要创建一个独立的 searcher 对象单独使用。
}
}
3.缓存 VectorIndex 索引
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
bash
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);
}
// 4、关闭资源
searcher.close();
// 备注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的制度 vIndex 缓存。
}
}
4.缓存整个 xdb 数据
我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。
java
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);
}
// 4、关闭资源 - 该 searcher 对象可以安全用于并发,等整个服务关闭的时候再关闭 searcher
// searcher.close();
// 备注:并发使用,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
}
}
5.将ip2region.xdb放在resource
bash
import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import java.io.*;
@Slf4j
public class IpdbUtils {
private static Searcher searcher;
/**
*
* 解决打包jar后找不到 ip2region.db 的问题
*/
public static String getIpAddress(String ip){
if ("127.0.0.1".equals(ip) || ip.startsWith("192.168")) {
return "局域网";
}
if (searcher == null) {
try {
ClassPathResource resource = new ClassPathResource("ipdb/ip2region.xdb");
InputStream inputStream = resource.getInputStream();
byte[] bytes = IoUtil.readBytes(inputStream);
searcher = Searcher.newWithBuffer(bytes);
} catch (Exception e) {
log.error("failed to create content cached searcher: ", e);
return null;
}
}
// 3、查询
String region=null;
try {
region = searcher.search(ip);
} catch (Exception e) {
throw new RuntimeException("获取IP地址异常");
}
return region;
}
}