java ip2region search service is ready

This commit is contained in:
lionsoul2014 2025-12-01 13:03:11 +08:00
parent b5ae94b36d
commit 942f3d9dbd
6 changed files with 188 additions and 21 deletions

View File

@ -7,10 +7,48 @@
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
</dependency>
```
### 关于查询服务
`3.2.0` 版本开始提供了一个双协议兼容且并发安全的 `Ip2Region` 查询服务,**建议优先使用该方式来进行查询调用**,具体使用方式如下:
```java
// 1, 创建 v4 的配置:指定缓存策略和 v4 的 xdb 文件路径
final Config v4Config = Config.custom()
.setCachePolicy(Config.VIndexCache) // 指定缓存策略: NoCache / VIndexCache / BufferCache
.setSeachers(15) // 设置初始化的查询器数量
.setXdbPath("ip2region v4 xdb path") // 设置 v4 xdb 文件的路径
.asV4(); // 指定为 v4 配置
// 2, 创建 v6 的配置:指定缓存策略和 v6 的 xdb 文件路径
final Config v6Config = Config.custom()
.setCachePolicy(Config.VIndexCache) // 指定缓存策略: NoCache / VIndexCache / BufferCache
.setSeachers(15) // 设置初始化的查询器数量
.setXdbPath("ip2region v6 xdb path") // 设置 v6 xdb 文件的路径
.asV6(); // 指定为 v6 配置
// 3通过上述配置创建 Ip2Region 查询服务
final Ip2Region ip2Region = Ip2Region.create(v4Config, v6Config);
// 4导出 ip2region 服务进行双版本的IP地址的并发查询例如
final String v4Region = ip2Region.search("113.92.157.29"); // 进行 IPv4 查询
final String v6Region = ip2Region.search("240e:3b7:3272:d8d0:db09:c067:8d59:539e"); // 进行 IPv6 查询
// 5在服务需要关闭的时候同时关闭 ip2region 查询服务
ip2Region.close();
```
关于 `Ip2Region`查询服务的信息:
1. 该查询服务的 API 并发安全且同时支持 `IPv4``Ipv6` 的地址,内部实现会自动判断。
2. v4 和 v6 的配置需要单独创建,可以给 v4 和 v6 设置使用不同的缓存策略,也可以指定其中一个为 `null` 则该版本的 IP 地址查询都会返回 `null`
3. 请结合您项目的并发数给 `setSearchers` 一个合适的查询器数量,默认为 20 个,这个值在运行过程中是固定的,每次查询会从池子里租借一个查询器来完成查询操作,查询完成后再归还回去,如果租借的时候池子已经空了则等待直到有可用的查询器来完成查询服务,租借的锁是使用的 `ReentrantLock` 来管理,也可以通过如下方式来设置 `Ip2Region` 查询服务使用公平锁:
```java
final Ip2Region ip2region = Ip2Region.create(v4Config, v6Config, true);
```
4. 如果配置设置的缓存策略为 `Config.BufferCache``全内存缓存` 则默认会使用单实例的内存查询器,该实现天生并发安全,此时通过 `setSearchers` 指定的查询器数量无效。
5. 如果 `ip2region` 查询器在提供服务期间,调用 close 默认会最大等待 10 秒钟来等待尽量多的查询器归还。
### 关于查询 API
定位信息查询 API 的原型为:
```java

View File

@ -4,7 +4,7 @@
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
<packaging>jar</packaging>
<name>ip2region</name>

View File

@ -33,6 +33,14 @@ public class Ip2Region {
/* v6 xdb searcher for cache policy cBuffer */
private final Searcher v6InMemSearcher;
public static final Ip2Region create(final Config v4Config, final Config v6Config) throws IOException {
return new Ip2Region(v4Config, v6Config).init();
}
public static final Ip2Region create(final String v4XdbPath, final String v6XdbPath) throws IOException, XdbException {
return new Ip2Region(v4XdbPath, v6XdbPath).init();
}
/**
* init the ip2reigon with two xdb file path and default cachePolicy vIndex.
* set it to null to disabled the search for specified version
@ -42,7 +50,7 @@ public class Ip2Region {
* @throws XdbException
* @throws IOException
*/
public Ip2Region(String v4XdbPath, String v6XdbPath) throws IOException, XdbException {
protected Ip2Region(String v4XdbPath, String v6XdbPath) throws IOException, XdbException {
this(
v4XdbPath == null ? null : Config.custom().setXdbPath(v4XdbPath).asV4(),
v6XdbPath == null ? null : Config.custom().setXdbPath(v6XdbPath).asV6()
@ -57,7 +65,7 @@ public class Ip2Region {
* @param v6Config
* @throws IOException
*/
public Ip2Region(Config v4Config, Config v6Config) throws IOException {
protected Ip2Region(Config v4Config, Config v6Config) throws IOException {
if (v4Config == null) {
// @Note: with IPv4 disabled ?
this.v4InMemSearcher = null;
@ -79,10 +87,23 @@ public class Ip2Region {
this.v6Pool = null;
} else {
this.v6InMemSearcher = null;
this.v6Pool = new SearcherPool(v6Config);
this.v6Pool = new SearcherPool(v6Config);
}
}
// init the current ip2region service
protected Ip2Region init() throws IOException {
if (v4Pool != null) {
v4Pool.init();
}
if (v6Pool != null) {
v6Pool.init();
}
return this;
}
public String search(String ipString) throws InetAddressException, IOException, InterruptedException {
return search(Util.parseIP(ipString));
}

View File

@ -20,11 +20,8 @@ import org.lionsoul.ip2region.xdb.Searcher;
* Date 2025/11/21
*/
public class SearcherPool {
public static final int WAIT_OVERLOADED = 1;
public static final int NEW_OVERLOADED = 2;
// config instance
private final Config config;
public final Config config;
// searcher pool
private final Queue<Searcher> pool;
@ -37,11 +34,20 @@ public class SearcherPool {
// searcher numbers that was loaned out
private int loanCount;
public SearcherPool(Config config) throws IOException {
// static method to create and init the searcher pool
public static final SearcherPool create(final Config config) throws IOException {
return new SearcherPool(config).init();
}
public static final SearcherPool create(final Config config, boolean fair) throws IOException {
return new SearcherPool(config, fair).init();
}
protected SearcherPool(Config config) throws IOException {
this(config, false);
}
public SearcherPool(Config config, boolean fair) throws IOException {
protected SearcherPool(Config config, boolean fair) {
assert config.searchers > 0;
this.config = config;
this.pool = new LinkedList<>();
@ -49,16 +55,16 @@ public class SearcherPool {
this.emptyCondition = this.lock.newCondition();
this.fullCondition = this.lock.newCondition();
this.loanCount = 0;
}
protected SearcherPool init() throws IOException {
// create the searchers
for (int i = 0; i < config.searchers; i++) {
for (int i = pool.size(); i < config.searchers; i++) {
final Searcher searcher = new Searcher(config.ipVersion, config.xdbPath, config.vIndex, config.cBuffer);
pool.add(searcher);
}
}
public Config getConfig() {
return config;
return this;
}
public Searcher borrowSearcher() throws InterruptedException {

View File

@ -1,6 +1,7 @@
package org.lionsoul.ip2region;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
import org.lionsoul.ip2region.xdb.InetAddressException;
@ -26,9 +27,9 @@ public class Ip2RegionTest {
.setXdbPath(ConfigTest.getDataPath("ip2region_v6.xdb"))
.asV6();
byte[] v4Bytes = Util.parseIP("1.2.3.4");
byte[] v4Bytes = Util.parseIP("113.92.157.29");
byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e");
final Ip2Region ip2Region = new Ip2Region(v4Config, v6Config);
final Ip2Region ip2Region = Ip2Region.create(v4Config, v6Config);
for (int i = 0; i < 50; i++) {
v4Bytes = Util.ipAddOne(v4Bytes);
v6Bytes = Util.ipAddOne(v6Bytes);
@ -42,13 +43,114 @@ public class Ip2RegionTest {
}
@Test
public void TestPathCreate() {
public void TestPathCreate() throws InetAddressException, IOException, XdbException, InterruptedException {
byte[] v4Bytes = Util.parseIP("113.92.157.29");
byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e");
final Ip2Region ip2Region = Ip2Region.create(ConfigTest.getDataPath("ip2region_v4.xdb"), ConfigTest.getDataPath("ip2region_v6.xdb"));
for (int i = 0; i < 50; i++) {
v4Bytes = Util.ipAddOne(v4Bytes);
v6Bytes = Util.ipAddOne(v6Bytes);
final String v4Region = ip2Region.search(v4Bytes);
final String v6Region = ip2Region.search(v6Bytes);
log.debugf("search(%s)=%s, search(%s)=%s", Util.ipToString(v4Bytes), v4Region, Util.ipToString(v6Bytes), v6Region);
}
ip2Region.close();
log.debugf("ip2region closed gracefully");
}
@Test
public void TestInMemSearch() {
public void TestInMemSearch() throws IOException, XdbException, InetAddressException, InterruptedException {
final Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbPath(ConfigTest.getDataPath("ip2region_v4.xdb"))
.asV4();
final Config v6Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbPath(ConfigTest.getDataPath("ip2region_v6.xdb"))
.asV6();
byte[] v4Bytes = Util.parseIP("113.92.157.29");
byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e");
final Ip2Region ip2Region = Ip2Region.create(v4Config, v6Config);
for (int i = 0; i < 50; i++) {
v4Bytes = Util.ipAddOne(v4Bytes);
v6Bytes = Util.ipAddOne(v6Bytes);
final String v4Region = ip2Region.search(v4Bytes);
final String v6Region = ip2Region.search(v6Bytes);
log.debugf("search(%s)=%s, search(%s)=%s", Util.ipToString(v4Bytes), v4Region, Util.ipToString(v6Bytes), v6Region);
}
ip2Region.close();
log.debugf("ip2region closed gracefully");
}
@Test
public void TestConcurrentCall() throws IOException, XdbException, InetAddressException, InterruptedException {
final Config v4Config = Config.custom()
.setCachePolicy(Config.VIndexCache)
.setSeachers(15)
.setXdbPath(ConfigTest.getDataPath("ip2region_v4.xdb"))
.asV4();
final Config v6Config = Config.custom()
.setCachePolicy(Config.VIndexCache)
.setSeachers(15)
.setXdbPath(ConfigTest.getDataPath("ip2region_v6.xdb"))
.asV6();
byte[] v4Bytes = Util.parseIP("113.92.157.29");
byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e");
final int threads = 50;
final Ip2Region ip2Region = Ip2Region.create(v4Config, v6Config);
final CountDownLatch latch = new CountDownLatch(threads);
final long startTime = System.currentTimeMillis();
for (int i = 0; i < threads; i++) {
final Runnable t = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
final byte[] ipBytes = i % 2 == 0 ? v4Bytes : v6Bytes;
try {
ip2Region.search(ipBytes);
} catch (InetAddressException | IOException | InterruptedException e) {
log.errorf("failed to search(%s): %s", Util.ipToString(ipBytes), e.getMessage());
}
}
latch.countDown();
}
};
t.run();
}
latch.await();
final long costs = System.currentTimeMillis() - startTime;
log.debugf("all search finished in %dms", costs);
ip2Region.close();
log.debugf("ip2region closed gracefully");
}
@Test
public void TestV4Only() throws IOException, XdbException, InetAddressException, InterruptedException {
final Config v4Config = Config.custom()
.setCachePolicy(Config.NoCache)
.setXdbPath(ConfigTest.getDataPath("ip2region_v4.xdb"))
.asV4();
byte[] v4Bytes = Util.parseIP("113.92.157.29");
byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e");
final Ip2Region ip2Region = Ip2Region.create(v4Config, null);
for (int i = 0; i < 10; i++) {
v4Bytes = Util.ipAddOne(v4Bytes);
final String v4Region = ip2Region.search(v4Bytes);
final String v6Region = ip2Region.search(v6Bytes);
log.debugf("search(%s)=%s, search(%s)=%s", Util.ipToString(v4Bytes), v4Region, Util.ipToString(v6Bytes), v6Region);
}
ip2Region.close();
log.debugf("ip2region closed gracefully");
}
}

View File

@ -18,7 +18,7 @@ public class SearcherPoolTest {
final String ipStr = "58.250.36.41";
final SearcherPool v4Pool = new SearcherPool(v4Config);
final SearcherPool v4Pool = SearcherPool.create(v4Config);
for (int i = 0; i < 20; i++) {
final Searcher searcher = v4Pool.borrowSearcher();
log.debugf("borrowed searcher %d: %s", i, searcher.toString());
@ -42,7 +42,7 @@ public class SearcherPoolTest {
final String ipStr = "240e:3b7:3272:d8d0:db09:c067:8d59:539e";
final SearcherPool v4Pool = new SearcherPool(v6Config);
final SearcherPool v4Pool = SearcherPool.create(v6Config);
for (int i = 0; i < 20; i++) {
final Searcher searcher = v4Pool.borrowSearcher();
log.debugf("borrowed searcher %d: %s", i, searcher.toString());