From 942f3d9dbd10086893368c88395e70f3c2b76aa2 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Mon, 1 Dec 2025 13:03:11 +0800 Subject: [PATCH] java ip2region search service is ready --- binding/java/ReadMe.md | 40 ++++++- binding/java/pom.xml | 2 +- .../org/lionsoul/ip2region/Ip2Region.java | 27 ++++- .../org/lionsoul/ip2region/SearcherPool.java | 26 +++-- .../org/lionsoul/ip2region/Ip2RegionTest.java | 110 +++++++++++++++++- .../lionsoul/ip2region/SearcherPoolTest.java | 4 +- 6 files changed, 188 insertions(+), 21 deletions(-) diff --git a/binding/java/ReadMe.md b/binding/java/ReadMe.md index 3b7b8ef..77d5ce2 100644 --- a/binding/java/ReadMe.md +++ b/binding/java/ReadMe.md @@ -7,10 +7,48 @@ org.lionsoul ip2region - 3.1.1 + 3.2.0 ``` +### 关于查询服务 +从 `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 diff --git a/binding/java/pom.xml b/binding/java/pom.xml index f7120b7..b7ea414 100644 --- a/binding/java/pom.xml +++ b/binding/java/pom.xml @@ -4,7 +4,7 @@ org.lionsoul ip2region - 3.1.1 + 3.2.0 jar ip2region diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java index 0a41282..03f5de1 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java @@ -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)); } diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java index d5167be..46f07da 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -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 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 { diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java index f77dd2a..f5b562b 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java @@ -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"); } } diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java index db80a7c..e6a5157 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java @@ -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());