From a6cb5161ff8b8d002d08c32d631e96239de2962d Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Thu, 20 Nov 2025 15:40:54 +0800 Subject: [PATCH 01/10] config and config builder is ready --- .../java/org/lionsoul/ip2region/Config.java | 70 +++++++++++++++++++ .../org/lionsoul/ip2region/ConfigBuilder.java | 60 ++++++++++++++++ .../org/lionsoul/ip2region/ConfigTest.java | 39 +++++++++++ 3 files changed, 169 insertions(+) create mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/Config.java create mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java create mode 100644 binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Config.java b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java new file mode 100644 index 0000000..64069f7 --- /dev/null +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java @@ -0,0 +1,70 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. +package org.lionsoul.ip2region; + +import java.io.IOException; + +import org.lionsoul.ip2region.xdb.Header; +import org.lionsoul.ip2region.xdb.Searcher; +import org.lionsoul.ip2region.xdb.Version; +import org.lionsoul.ip2region.xdb.XdbException; + +/** + * ip2region config class + * @Author Lion + * @Date 2025/11/20 +*/ +public class Config { + // cache policy consts + public static final int NoCache = 0; + public static final int VIndexCache = 1; + public static final int BufferCache = 2; + + // search cache policy + public final int cachePolicy; + + // xdb file path + public final String xdbPath; + public final Header header; + public final Version ipVersion; + + // search limitation for NoCache or VIndexCache Only + public final int minSearchers; + public final int maxSearchers; + + // config builder + public static ConfigBuilder custom() { + return new ConfigBuilder(); + } + + public Config(int cachePolicy, String xdbPath) throws IOException, XdbException { + this(cachePolicy, xdbPath, 10, 30); + } + + public Config(int cachePolicy, String xdbPath, int minSearchers, int maxSearchers) throws IOException, XdbException { + this.cachePolicy = cachePolicy; + this.xdbPath = xdbPath; + + // load the header from the xdb path + final Header header = Searcher.loadHeaderFromFile(xdbPath); + this.header = header; + this.ipVersion = Version.fromHeader(header); + + this.minSearchers = minSearchers; + this.maxSearchers = maxSearchers; + } + + @Override public String toString() { + final StringBuffer sb = new StringBuffer(); + sb.append('{'); + sb.append("cache_policy:").append(cachePolicy).append(','); + sb.append("xdb_path:").append(xdbPath).append(','); + sb.append("header:").append(header.toString()).append(','); + sb.append("version:").append(ipVersion.toString()).append(','); + sb.append("min_searchers:").append(minSearchers).append(','); + sb.append("max_searchers:").append(maxSearchers); + sb.append('}'); + return sb.toString(); + } +} diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java new file mode 100644 index 0000000..c25f9c1 --- /dev/null +++ b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java @@ -0,0 +1,60 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. + +package org.lionsoul.ip2region; + +import java.io.IOException; + +import org.lionsoul.ip2region.xdb.XdbException; + +/** + * ip2region config builder + * @Author Lion + * @Date 2025/11/20 +*/ +public class ConfigBuilder { + + // cache policy + private int cachePolicy = Config.VIndexCache; + + // xdb file path + private String xdbPath = null; + + // min searchers + private int minSearchers = 10; + + // max searchers + private int maxSearchers = 30; + + public ConfigBuilder() {} + + public ConfigBuilder(String xdbPath) { + this.xdbPath = xdbPath; + } + + public ConfigBuilder setCachePolicy(int cachePolicy) { + this.cachePolicy = cachePolicy; + return this; + } + + public ConfigBuilder setXdbPath(String xdbPath) { + this.xdbPath = xdbPath; + return this; + } + + public ConfigBuilder setMinSearchers(int minSearchers) { + this.minSearchers = minSearchers; + return this; + } + + public ConfigBuilder setMaxSearchers(int maxSearchers) { + this.maxSearchers = maxSearchers; + return this; + } + + // build the final #Config instance with the current config items + public Config build() throws IOException, XdbException { + return new Config(cachePolicy, xdbPath, minSearchers, maxSearchers); + } +} \ No newline at end of file diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java new file mode 100644 index 0000000..58e365e --- /dev/null +++ b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java @@ -0,0 +1,39 @@ +package org.lionsoul.ip2region; +import java.io.IOException; +import java.security.CodeSource; + +import org.junit.Test; +import org.lionsoul.ip2region.xdb.Log; +import org.lionsoul.ip2region.xdb.XdbException; + +public class ConfigTest { + + private static final Log log = Log.getLogger(ConfigTest.class).setLevel(Log.DEBUG); + + public String getDataPath(String xdbFile) { + final CodeSource cs = this.getClass().getProtectionDomain().getCodeSource(); + if (cs != null) { + // log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/")); + return cs.getLocation().getPath().concat("../../../../data/").concat(xdbFile); + } else { + return "../../../../data/".concat(xdbFile); + } + } + + @Test + public void testConfig() throws IOException, XdbException { + final Config config = new Config(Config.VIndexCache, getDataPath("ip2region_v4.xdb"), 5, 10); + log.debugf("config: %s", config); + } + + @Test + public void testBuildConfig() throws IOException, XdbException { + final Config config = Config.custom() + .setCachePolicy(Config.BufferCache) + .setXdbPath(getDataPath("ip2region_v6.xdb")) + .setMinSearchers(10) + .setMaxSearchers(30) + .build(); + log.debugf("builded config: %s", config); + } +} From 2c7b170637cb6c1dde508b718944b752293543bf Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Fri, 21 Nov 2025 14:49:31 +0800 Subject: [PATCH 02/10] still working on the solutions --- .../java/org/lionsoul/ip2region/Config.java | 22 ++++-- .../org/lionsoul/ip2region/ConfigBuilder.java | 17 +++-- .../org/lionsoul/ip2region/Ip2Region.java | 68 +++++++++++++++++++ .../org/lionsoul/ip2region/SearcherPool.java | 33 +++++++++ .../org/lionsoul/ip2region/ConfigTest.java | 9 +-- 5 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java create mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Config.java b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java index 64069f7..196c6a0 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Config.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java @@ -1,4 +1,4 @@ -// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Copyright 2022 The Ip2Region Authors. All rights reserved. // Use of this source code is governed by a Apache2.0-style // license that can be found in the LICENSE file. package org.lionsoul.ip2region; @@ -29,7 +29,7 @@ public class Config { public final Header header; public final Version ipVersion; - // search limitation for NoCache or VIndexCache Only + // searcher pool limitation public final int minSearchers; public final int maxSearchers; @@ -38,18 +38,26 @@ public class Config { return new ConfigBuilder(); } - public Config(int cachePolicy, String xdbPath) throws IOException, XdbException { - this(cachePolicy, xdbPath, 10, 30); + public Config(int cachePolicy, Version ipVersion, String xdbPath) throws IOException, XdbException { + this(cachePolicy, ipVersion, xdbPath, 10, 50); } - public Config(int cachePolicy, String xdbPath, int minSearchers, int maxSearchers) throws IOException, XdbException { + public Config(int cachePolicy, Version ipVersion, String xdbPath, int minSearchers, int maxSearchers) throws IOException, XdbException { this.cachePolicy = cachePolicy; this.xdbPath = xdbPath; // load the header from the xdb path final Header header = Searcher.loadHeaderFromFile(xdbPath); this.header = header; - this.ipVersion = Version.fromHeader(header); + final Version xVersion = Version.fromHeader(header); + + // this.ipVersion = Version.fromHeader(header); + this.ipVersion = ipVersion; + + // verify the ip version (ipVersion and the version of the xdb file should be the same) + if (header.ipVersion != ipVersion.id) { + throw new XdbException("ip verison not match: xdb file " + xdbPath + " (" + xVersion.name + "), as " + ipVersion.name + " expected"); + } this.minSearchers = minSearchers; this.maxSearchers = maxSearchers; @@ -67,4 +75,4 @@ public class Config { sb.append('}'); return sb.toString(); } -} +} \ No newline at end of file diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java index c25f9c1..d0ee092 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java @@ -1,4 +1,4 @@ -// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Copyright 2022 The Ip2Region Authors. All rights reserved. // Use of this source code is governed by a Apache2.0-style // license that can be found in the LICENSE file. @@ -6,6 +6,7 @@ package org.lionsoul.ip2region; import java.io.IOException; +import org.lionsoul.ip2region.xdb.Version; import org.lionsoul.ip2region.xdb.XdbException; /** @@ -17,7 +18,7 @@ public class ConfigBuilder { // cache policy private int cachePolicy = Config.VIndexCache; - + // xdb file path private String xdbPath = null; @@ -53,8 +54,14 @@ public class ConfigBuilder { return this; } - // build the final #Config instance with the current config items - public Config build() throws IOException, XdbException { - return new Config(cachePolicy, xdbPath, minSearchers, maxSearchers); + // build the final #Config instance for IPv4 + public Config asV4() throws IOException, XdbException { + return new Config(cachePolicy, Version.IPv4, xdbPath, minSearchers, maxSearchers); } + + // build the final #Config instance for IPv6 + public Config asV6() throws IOException, XdbException { + return new Config(cachePolicy, Version.IPv6, xdbPath, minSearchers, maxSearchers); + } + } \ No newline at end of file diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java new file mode 100644 index 0000000..76dd3df --- /dev/null +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java @@ -0,0 +1,68 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. + +package org.lionsoul.ip2region; + +import java.io.IOException; + +import org.lionsoul.ip2region.xdb.InetAddressException; +import org.lionsoul.ip2region.xdb.Searcher; +import org.lionsoul.ip2region.xdb.Util; + +/** + * ip2region searcher manager service to provider: + * 1. Unified query interface for IPv4 and IPv6 address. + * 2. Concurrency search support. + * + * @Author Lion + * Date 2025/11/21 +*/ +public class Ip2Region { + + /* v4 pool */ + private final SearcherPool v4Pool; + + /* v6 pool */ + private final SearcherPool v6Pool; + + public Ip2Region(Config v4Config, Config v6Config) { + this.v4Pool = new SearcherPool(v4Config); + this.v6Pool = new SearcherPool(v6Config); + } + + public String search(String ipString) throws InetAddressException, IOException { + return search(Util.parseIP(ipString)); + } + + public String search(byte[] ipBytes) throws InetAddressException, IOException { + // 1, define the pool with the input ip + final SearcherPool pool; + if (ipBytes.length == 4) { + pool = v4Pool; + } else if (ipBytes.length == 16) { + pool = v6Pool; + } else { + throw new InetAddressException("invalid byte ip address with length=" + ipBytes.length); + } + + // 2, get a searcher from the pool + final Searcher searcher = pool.getSearcher(); + + try { + // 3, do the search + final String region = searcher.search(ipBytes); + return region; + } catch (InetAddressException e) { + // for the inet address error and we should return the searcher + throw e; + } catch (IOException e) { + // for the IOException usually means something is wrong with the read operation to the xdb file + // and we choose to keep the searcher and destory it right now + // so we will create a new one for the next search + try {searcher.close();} catch (IOException e1) {} + throw e; + } + } + +} \ No newline at end of file diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java new file mode 100644 index 0000000..d2c90ea --- /dev/null +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -0,0 +1,33 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. + +package org.lionsoul.ip2region; + +import java.util.LinkedList; +import java.util.Queue; + +import org.lionsoul.ip2region.xdb.Searcher; + +/** + * ip2region searcher pool manager to provider Concurrency search support. + * + * @author Leon + * Date 2025/11/21 +*/ +public class SearcherPool { + // config instance + private final Config config; + + // searcher pool + private final Queue pool; + + public SearcherPool(Config config) { + this.config = config; + this.pool = new LinkedList<>(); + } + + public Searcher getSearcher() { + return null; + } +} \ No newline at end of file diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java index 58e365e..c86b0d0 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java @@ -1,9 +1,10 @@ -package org.lionsoul.ip2region; +package org.lionsoul.ip2region; import java.io.IOException; import java.security.CodeSource; import org.junit.Test; import org.lionsoul.ip2region.xdb.Log; +import org.lionsoul.ip2region.xdb.Version; import org.lionsoul.ip2region.xdb.XdbException; public class ConfigTest { @@ -22,7 +23,7 @@ public class ConfigTest { @Test public void testConfig() throws IOException, XdbException { - final Config config = new Config(Config.VIndexCache, getDataPath("ip2region_v4.xdb"), 5, 10); + final Config config = new Config(Config.VIndexCache, Version.IPv4, getDataPath("ip2region_v4.xdb"), 5, 10); log.debugf("config: %s", config); } @@ -33,7 +34,7 @@ public class ConfigTest { .setXdbPath(getDataPath("ip2region_v6.xdb")) .setMinSearchers(10) .setMaxSearchers(30) - .build(); + .asV6(); log.debugf("builded config: %s", config); } -} +} \ No newline at end of file From 317a900077994717fd6db865dcdda044cdf31c71 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Tue, 25 Nov 2025 15:03:31 +0800 Subject: [PATCH 03/10] add vIndex & cBuffer fields and auto build --- .../java/org/lionsoul/ip2region/Config.java | 53 ++++++++++--------- .../org/lionsoul/ip2region/ConfigBuilder.java | 40 +++++++++----- .../org/lionsoul/ip2region/ConfigTest.java | 22 ++++---- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Config.java b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java index 196c6a0..3012ef8 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Config.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Config.java @@ -6,7 +6,7 @@ package org.lionsoul.ip2region; import java.io.IOException; import org.lionsoul.ip2region.xdb.Header; -import org.lionsoul.ip2region.xdb.Searcher; +import org.lionsoul.ip2region.xdb.LongByteArray; import org.lionsoul.ip2region.xdb.Version; import org.lionsoul.ip2region.xdb.XdbException; @@ -23,55 +23,60 @@ public class Config { // search cache policy public final int cachePolicy; + public final Version ipVersion; // xdb file path public final String xdbPath; public final Header header; - public final Version ipVersion; - // searcher pool limitation - public final int minSearchers; - public final int maxSearchers; + public final byte[] vIndex; + public final LongByteArray cBuffer; + + public final int searchers; // config builder public static ConfigBuilder custom() { return new ConfigBuilder(); } - public Config(int cachePolicy, Version ipVersion, String xdbPath) throws IOException, XdbException { - this(cachePolicy, ipVersion, xdbPath, 10, 50); - } - - public Config(int cachePolicy, Version ipVersion, String xdbPath, int minSearchers, int maxSearchers) throws IOException, XdbException { + protected Config(int cachePolicy, Version ipVersion, String xdbPath, + Header header, byte[] vIndex, LongByteArray cBuffer, int searchers) throws IOException, XdbException { this.cachePolicy = cachePolicy; - this.xdbPath = xdbPath; - - // load the header from the xdb path - final Header header = Searcher.loadHeaderFromFile(xdbPath); - this.header = header; - final Version xVersion = Version.fromHeader(header); - - // this.ipVersion = Version.fromHeader(header); this.ipVersion = ipVersion; + this.xdbPath = xdbPath; + this.header = header; + this.vIndex = vIndex; + this.cBuffer = cBuffer; + + final Version xVersion = Version.fromHeader(header); // verify the ip version (ipVersion and the version of the xdb file should be the same) if (header.ipVersion != ipVersion.id) { - throw new XdbException("ip verison not match: xdb file " + xdbPath + " (" + xVersion.name + "), as " + ipVersion.name + " expected"); + throw new XdbException("ip verison not match: xdb file " + + xdbPath + " (" + xVersion.name + "), as " + ipVersion.name + " expected"); } - this.minSearchers = minSearchers; - this.maxSearchers = maxSearchers; + this.searchers = searchers; } @Override public String toString() { final StringBuffer sb = new StringBuffer(); sb.append('{'); sb.append("cache_policy:").append(cachePolicy).append(','); + sb.append("version:").append(ipVersion.toString()).append(','); sb.append("xdb_path:").append(xdbPath).append(','); sb.append("header:").append(header.toString()).append(','); - sb.append("version:").append(ipVersion.toString()).append(','); - sb.append("min_searchers:").append(minSearchers).append(','); - sb.append("max_searchers:").append(maxSearchers); + if (vIndex == null) { + sb.append("v_index: null, "); + } else { + sb.append("v_index: {bytes: ").append(vIndex.length).append("},"); + } + if (cBuffer == null) { + sb.append("c_buffer: null, "); + } else { + sb.append("c_buffer: {bytes: ").append(cBuffer.length()).append("},"); + } + sb.append("searchers:").append(searchers); sb.append('}'); return sb.toString(); } diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java index d0ee092..4bdb77d 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/ConfigBuilder.java @@ -5,7 +5,11 @@ package org.lionsoul.ip2region; import java.io.IOException; +import java.io.RandomAccessFile; +import org.lionsoul.ip2region.xdb.Header; +import org.lionsoul.ip2region.xdb.LongByteArray; +import org.lionsoul.ip2region.xdb.Searcher; import org.lionsoul.ip2region.xdb.Version; import org.lionsoul.ip2region.xdb.XdbException; @@ -22,11 +26,8 @@ public class ConfigBuilder { // xdb file path private String xdbPath = null; - // min searchers - private int minSearchers = 10; - - // max searchers - private int maxSearchers = 30; + // searchers + private int searchers = 20; public ConfigBuilder() {} @@ -44,24 +45,39 @@ public class ConfigBuilder { return this; } - public ConfigBuilder setMinSearchers(int minSearchers) { - this.minSearchers = minSearchers; + public ConfigBuilder setSeachers(int searchers) { + this.searchers = searchers; return this; } - public ConfigBuilder setMaxSearchers(int maxSearchers) { - this.maxSearchers = maxSearchers; - return this; + private Config build(Version ipVersion) throws IOException, XdbException { + // load the header and the cache buffer + final RandomAccessFile raf = new RandomAccessFile(xdbPath, "r"); + + // 1, verify the xdb + Searcher.verify(raf); + + // 2, load the header + final Header header = Searcher.loadHeader(raf); + + // 3, check and load the vector index buffer + final byte[] vIndex = cachePolicy == Config.VIndexCache ? Searcher.loadVectorIndex(raf) : null; + + // 4, check and load the content buffer + final LongByteArray cBuffer = cachePolicy == Config.BufferCache ? Searcher.loadContent(raf) : null; + + raf.close(); + return new Config(cachePolicy, ipVersion, xdbPath, header, vIndex, cBuffer, searchers); } // build the final #Config instance for IPv4 public Config asV4() throws IOException, XdbException { - return new Config(cachePolicy, Version.IPv4, xdbPath, minSearchers, maxSearchers); + return build(Version.IPv4); } // build the final #Config instance for IPv6 public Config asV6() throws IOException, XdbException { - return new Config(cachePolicy, Version.IPv6, xdbPath, minSearchers, maxSearchers); + return build(Version.IPv6); } } \ No newline at end of file diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java index c86b0d0..5664012 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java @@ -4,7 +4,6 @@ import java.security.CodeSource; import org.junit.Test; import org.lionsoul.ip2region.xdb.Log; -import org.lionsoul.ip2region.xdb.Version; import org.lionsoul.ip2region.xdb.XdbException; public class ConfigTest { @@ -22,19 +21,22 @@ public class ConfigTest { } @Test - public void testConfig() throws IOException, XdbException { - final Config config = new Config(Config.VIndexCache, Version.IPv4, getDataPath("ip2region_v4.xdb"), 5, 10); - log.debugf("config: %s", config); + public void testBuildV4Config() throws IOException, XdbException { + final Config v4Config = Config.custom() + .setCachePolicy(Config.BufferCache) + .setXdbPath(getDataPath("ip2region_v4.xdb")) + .setSeachers(20) + .asV4(); + log.debugf("builded config: %s", v4Config); } @Test - public void testBuildConfig() throws IOException, XdbException { - final Config config = Config.custom() - .setCachePolicy(Config.BufferCache) + public void testBuildV6Config() throws IOException, XdbException { + final Config v4Config = Config.custom() + .setCachePolicy(Config.VIndexCache) .setXdbPath(getDataPath("ip2region_v6.xdb")) - .setMinSearchers(10) - .setMaxSearchers(30) + .setSeachers(20) .asV6(); - log.debugf("builded config: %s", config); + log.debugf("builded config: %s", v4Config); } } \ No newline at end of file From a8087e06b7fa368bac21888481ee3044859d2449 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Tue, 25 Nov 2025 15:10:21 +0800 Subject: [PATCH 04/10] searcher pool init --- .../org/lionsoul/ip2region/SearcherPool.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 d2c90ea..3256e09 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -4,8 +4,11 @@ package org.lionsoul.ip2region; +import java.io.IOException; import java.util.LinkedList; import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; import org.lionsoul.ip2region.xdb.Searcher; @@ -22,12 +25,37 @@ public class SearcherPool { // searcher pool private final Queue pool; + // searcher lock + private final ReentrantLock lock; + + // searcher numbers that was loaned out + private final AtomicInteger loanCount; + public SearcherPool(Config config) { + this(config, false); + } + + public SearcherPool(Config config, boolean fair) { this.config = config; this.pool = new LinkedList<>(); + this.lock = new ReentrantLock(fair); + this.loanCount = new AtomicInteger(0); + } + + // init the searcher pool + public void init() throws IOException { + for (int i = 0; i < config.searchers; i++) { + final Searcher searcher = new Searcher(config.ipVersion, config.xdbPath, config.vIndex, config.cBuffer); + pool.add(searcher); + } } public Searcher getSearcher() { return null; } + + // close the searcher pool + public void close() { + this.loanCount.set(config.searchers); + } } \ No newline at end of file From f8a154aeda91fde58ea52d354c748ea86735d04d Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Thu, 27 Nov 2025 15:11:37 +0800 Subject: [PATCH 05/10] searcher pool and tests --- .../org/lionsoul/ip2region/Ip2Region.java | 22 ++---- .../org/lionsoul/ip2region/SearcherPool.java | 78 +++++++++++++++---- .../org/lionsoul/ip2region/xdb/Searcher.java | 12 +++ .../lionsoul/ip2region/SearcherPoolTest.java | 70 +++++++++++++++++ 4 files changed, 154 insertions(+), 28 deletions(-) create mode 100644 binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java 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 76dd3df..b5c116c 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java @@ -26,16 +26,16 @@ public class Ip2Region { /* v6 pool */ private final SearcherPool v6Pool; - public Ip2Region(Config v4Config, Config v6Config) { + public Ip2Region(Config v4Config, Config v6Config) throws IOException { this.v4Pool = new SearcherPool(v4Config); this.v6Pool = new SearcherPool(v6Config); } - public String search(String ipString) throws InetAddressException, IOException { + public String search(String ipString) throws InetAddressException, IOException, InterruptedException { return search(Util.parseIP(ipString)); } - public String search(byte[] ipBytes) throws InetAddressException, IOException { + public String search(byte[] ipBytes) throws InetAddressException, IOException, InterruptedException { // 1, define the pool with the input ip final SearcherPool pool; if (ipBytes.length == 4) { @@ -47,21 +47,13 @@ public class Ip2Region { } // 2, get a searcher from the pool - final Searcher searcher = pool.getSearcher(); + final Searcher searcher = pool.borrowSearcher(); try { // 3, do the search - final String region = searcher.search(ipBytes); - return region; - } catch (InetAddressException e) { - // for the inet address error and we should return the searcher - throw e; - } catch (IOException e) { - // for the IOException usually means something is wrong with the read operation to the xdb file - // and we choose to keep the searcher and destory it right now - // so we will create a new one for the next search - try {searcher.close();} catch (IOException e1) {} - throw e; + return searcher.search(ipBytes); + } finally { + pool.returnSearcher(searcher); } } 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 3256e09..6f362b8 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -5,9 +5,10 @@ package org.lionsoul.ip2region; import java.io.IOException; +import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.lionsoul.ip2region.xdb.Searcher; @@ -19,43 +20,94 @@ 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; // searcher pool private final Queue pool; - // searcher lock + // lock & conditions private final ReentrantLock lock; + private final Condition emptyCondition; + private final Condition fullCondition; // searcher numbers that was loaned out - private final AtomicInteger loanCount; + private int loanCount; - public SearcherPool(Config config) { + public SearcherPool(Config config) throws IOException { this(config, false); } - public SearcherPool(Config config, boolean fair) { + public SearcherPool(Config config, boolean fair) throws IOException { + assert config.searchers > 0; this.config = config; this.pool = new LinkedList<>(); this.lock = new ReentrantLock(fair); - this.loanCount = new AtomicInteger(0); - } + this.emptyCondition = this.lock.newCondition(); + this.fullCondition = this.lock.newCondition(); + this.loanCount = 0; - // init the searcher pool - public void init() throws IOException { + // create the searchers for (int i = 0; i < config.searchers; i++) { final Searcher searcher = new Searcher(config.ipVersion, config.xdbPath, config.vIndex, config.cBuffer); pool.add(searcher); } } - public Searcher getSearcher() { - return null; + public Config getConfig() { + return config; + } + + public Searcher borrowSearcher() throws InterruptedException { + lock.lock(); + try { + while (pool.isEmpty()) { + emptyCondition.await(); + } + + loanCount++; + return pool.poll(); + } finally { + lock.unlock(); + } + } + + public void returnSearcher(final Searcher searcher) { + lock.lock(); + try { + pool.add(searcher); + loanCount--; + emptyCondition.signal(); + + // check and signal the full condition. + // pool close + if (loanCount == 0) { + fullCondition.signal(); + } + } finally { + lock.unlock(); + } } // close the searcher pool - public void close() { - this.loanCount.set(config.searchers); + public void close() throws InterruptedException { + lock.lock(); + try { + while (loanCount > 0) { + fullCondition.wait(); + } + + final Iterator it = pool.iterator(); + while (it.hasNext()) { + final Searcher searcher = it.next(); + try {searcher.close();} catch (IOException e) {} + it.remove(); + } + } finally { + lock.unlock(); + } } } \ No newline at end of file diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java index dbae4de..5dd22c4 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java @@ -29,6 +29,7 @@ public class Searcher { private final Version version; // random access file handle for file-based search + private final String xdbPath; private final RandomAccessFile handle; private int ioCount = 0; @@ -61,6 +62,7 @@ public class Searcher { public Searcher(Version version, String dbFile, byte[] vectorIndex, LongByteArray cBuff) throws IOException { this.version = version; + this.xdbPath = dbFile; if (cBuff != null) { this.handle = null; this.vectorIndex = null; @@ -173,6 +175,16 @@ public class Searcher { } } + @Override public String toString() { + return String.format( + "%s->{version:%s, xdb:%s, vIndex:%s, cBuffer:%s}", + super.toString(), + version.name, xdbPath, + vectorIndex == null ? "null" : String.valueOf(vectorIndex.length), + contentBuff == null ? "null" : String.valueOf(contentBuff.length()) + ); + } + // --- static util function public static Header loadHeader(RandomAccessFile handle) throws IOException { diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java new file mode 100644 index 0000000..ef51252 --- /dev/null +++ b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java @@ -0,0 +1,70 @@ +package org.lionsoul.ip2region; + +import java.security.CodeSource; + +import org.junit.Test; +import org.lionsoul.ip2region.xdb.Log; +import org.lionsoul.ip2region.xdb.Searcher; + +public class SearcherPoolTest { + + private static final Log log = Log.getLogger(ConfigTest.class).setLevel(Log.DEBUG); + + public String getDataPath(String xdbFile) { + final CodeSource cs = this.getClass().getProtectionDomain().getCodeSource(); + if (cs != null) { + // log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/")); + return cs.getLocation().getPath().concat("../../../../data/").concat(xdbFile); + } else { + return "../../../../data/".concat(xdbFile); + } + } + + @Test + public void testV4SeacherPool() throws Exception { + final Config v4Config = Config.custom() + .setCachePolicy(Config.VIndexCache) + .setSeachers(5) + .setXdbPath(getDataPath("ip2region_v4.xdb")) + .asV4(); + + + final String ipStr = "58.250.36.41"; + final SearcherPool v4Pool = new SearcherPool(v4Config); + for (int i = 0; i < 20; i++) { + final Searcher searcher = v4Pool.borrowSearcher(); + log.debugf("borrowed searcher %d: %s", i, searcher.toString()); + final String region = searcher.search(ipStr); + log.debugf("search(%s)=%s", ipStr, region); + v4Pool.returnSearcher(searcher); + log.debugf("return searcher %d", i); + } + + v4Pool.close(); + log.debugf("v4 searcher pool closed gracefully"); + } + + @Test + public void testV6SeacherPool() throws Exception { + final Config v6Config = Config.custom() + .setCachePolicy(Config.VIndexCache) + .setSeachers(5) + .setXdbPath(getDataPath("ip2region_v6.xdb")) + .asV6(); + + + final String ipStr = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; + final SearcherPool v4Pool = new SearcherPool(v6Config); + for (int i = 0; i < 20; i++) { + final Searcher searcher = v4Pool.borrowSearcher(); + log.debugf("borrowed searcher %d: %s", i, searcher.toString()); + final String region = searcher.search(ipStr); + log.debugf("search(%s)=%s", ipStr, region); + v4Pool.returnSearcher(searcher); + log.debugf("return searcher %d", i); + } + + v4Pool.close(); + log.debugf("v6 searcher pool closed gracefully"); + } +} From 7f2d1ae789a784dcaf7afbe9c3f9d36abc8b62f2 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Fri, 28 Nov 2025 16:33:25 +0800 Subject: [PATCH 06/10] SearcherApp to SearcherTest --- binding/java/pom.xml | 2 +- .../org/lionsoul/ip2region/SearcherTest.java | 251 ++++++++++++++++++ 2 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/SearcherTest.java diff --git a/binding/java/pom.xml b/binding/java/pom.xml index 295628b..f7120b7 100644 --- a/binding/java/pom.xml +++ b/binding/java/pom.xml @@ -116,7 +116,7 @@ - org.lionsoul.ip2region.SearchApp + org.lionsoul.ip2region.SearcherTest diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherTest.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherTest.java new file mode 100644 index 0000000..448f3b5 --- /dev/null +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherTest.java @@ -0,0 +1,251 @@ +// Copyright 2022 The Ip2Region Authors. All rights reserved. +// Use of this source code is governed by a Apache2.0-style +// license that can be found in the LICENSE file. +// @Author Lion +// @Date 2022/06/23 + +package org.lionsoul.ip2region; + +import org.lionsoul.ip2region.xdb.InetAddressException; +import org.lionsoul.ip2region.xdb.XdbException; +import org.lionsoul.ip2region.xdb.LongByteArray; +import org.lionsoul.ip2region.xdb.Searcher; +import org.lionsoul.ip2region.xdb.Util; +import org.lionsoul.ip2region.xdb.Version; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +public class SearcherTest { + + public static void printHelp(String[] args) { + System.out.print("ip2region xdb searcher\n"); + System.out.print("java -jar ip2region-{version}.jar [command] [command options]\n"); + System.out.print("Command: \n"); + System.out.print(" search search input test\n"); + System.out.print(" bench search bench test\n"); + } + + public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException, XdbException { + final RandomAccessFile handle = new RandomAccessFile(dbPath, "r"); + + // verify the xdb file + // @Note: do NOT call it every time you create a searcher since this will slow + // down the search response. + // @see the util.Verify function for details. + Searcher.verify(handle); + + // get the ip version from header + final Version version = Version.fromHeader(Searcher.loadHeader(handle)); + + // create the final searcher + if ("file".equals(cachePolicy)) { + return Searcher.newWithFileOnly(version, dbPath); + } else if ("vectorIndex".equals(cachePolicy)) { + byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath); + return Searcher.newWithVectorIndex(version, dbPath, vIndex); + } else if ("content".equals(cachePolicy)) { + LongByteArray cBuff = Searcher.loadContentFromFile(dbPath); + return Searcher.newWithBuffer(version, cBuff); + } else { + throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content"); + } + } + + public static void searchTest(String[] args) throws IOException, XdbException { + String dbPath = "", cachePolicy = "vectorIndex"; + for (final String r : args) { + if (r.length() < 5) { + continue; + } + + if (r.indexOf("--") != 0) { + continue; + } + + int sIdx = r.indexOf('='); + if (sIdx < 0) { + System.out.printf("missing = for args pair `%s`\n", r); + return; + } + + String key = r.substring(2, sIdx); + String val = r.substring(sIdx + 1); + // System.out.printf("key=%s, val=%s\n", key, val); + if ("db".equals(key)) { + dbPath = val; + } else if ("cache-policy".equals(key)) { + cachePolicy = val; + } else { + System.out.printf("undefined option `%s`\n", r); + return; + } + } + + if (dbPath.isEmpty()) { + System.out.print("java -jar ip2region-{version}.jar search [command options]\n"); + System.out.print("options:\n"); + System.out.print(" --db string ip2region binary xdb file path\n"); + System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n"); + return; + } + + Searcher searcher = createSearcher(dbPath, cachePolicy); + final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + System.out.printf("ip2region xdb searcher test program\n" ++ "source xdb: %s (%s, %s)\n" ++ "type 'quit' to exit\n", dbPath, searcher.getIPVersion().name, cachePolicy); + while ( true ) { + System.out.print("ip2region>> "); + String line = reader.readLine().trim(); + if ( line.length() < 2 ) { + continue; + } + + if ( line.equalsIgnoreCase("quit") ) { + break; + } + + try { + double sTime = System.nanoTime(); + String region = searcher.search(line); + 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("{err: %s, ioCount: %d}\n", e, searcher.getIOCount()); + } + } + + reader.close(); + searcher.close(); + System.out.println("searcher test program exited, thanks for trying"); + } + + public static void benchTest(String[] args) throws IOException, XdbException, InetAddressException { + String dbPath = "", srcPath = "", cachePolicy = "vectorIndex"; + for (final String r : args) { + if (r.length() < 5) { + continue; + } + + if (r.indexOf("--") != 0) { + continue; + } + + int sIdx = r.indexOf('='); + if (sIdx < 0) { + System.out.printf("missing = for args pair `%s`\n", r); + return; + } + + String key = r.substring(2, sIdx); + String val = r.substring(sIdx + 1); + if ("db".equals(key)) { + dbPath = val; + } else if ("src".equals(key)) { + srcPath = val; + } else if ("cache-policy".equals(key)) { + cachePolicy = val; + } else { + System.out.printf("undefined option `%s`\n", r); + return; + } + } + + if (dbPath.length() < 1 || srcPath.length() < 1) { + System.out.print("java -jar ip2region-{version}.jar bench [command options]\n"); + System.out.print("options:\n"); + System.out.print(" --db string ip2region binary xdb file path\n"); + System.out.print(" --src string source ip text file path\n"); + System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n"); + return; + } + + Searcher searcher = createSearcher(dbPath, cachePolicy); + long count = 0, costs = 0, tStart = System.nanoTime(); + String line; + final Charset charset = Charset.forName("utf-8"); + final FileInputStream fis = new FileInputStream(srcPath); + final BufferedReader reader = new BufferedReader(new InputStreamReader(fis, charset)); + while ((line = reader.readLine()) != null) { + String l = line.trim(); + String[] ps = l.split("\\|", 3); + if (ps.length != 3) { + reader.close(); + System.out.printf("invalid ip segment `%s`\n", l); + return; + } + + byte[] sip; + try { + sip = Util.parseIP(ps[0]); + } catch (Exception e) { + reader.close(); + System.out.printf("check start ip `%s`: %s\n", ps[0], e); + return; + } + + byte[] eip; + try { + eip = Util.parseIP(ps[1]); + } catch (Exception e) { + reader.close(); + System.out.printf("check end ip `%s`: %s\n", ps[1], e); + return; + } + + if (Util.ipCompare(sip, eip) > 0) { + reader.close(); + System.out.printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1]); + return; + } + + for (final byte[] ip : new byte[][]{sip, eip}) { + long sTime = System.nanoTime(); + String region = searcher.search(ip); + costs += System.nanoTime() - sTime; + + // check the region info + if (!ps[2].equals(region)) { + System.out.printf("failed search(%s) with (%s != %s)\n", Util.ipToString(ip), region, ps[2]); + reader.close(); + return; + } + + count++; + } + } + + reader.close(); + searcher.close(); + long took = System.nanoTime() - tStart; + System.out.printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %d μs/op}\n", + cachePolicy, count, TimeUnit.NANOSECONDS.toSeconds(took), + count == 0 ? 0 : TimeUnit.NANOSECONDS.toMicros(costs/count)); + } + + public static void main(String[] args) { + if (args.length < 1) { + printHelp(args); + return; + } + + if ("search".equals(args[0])) { + try { + searchTest(args); + } catch (Exception e) { + System.out.printf("failed running search test: %s\n", e); + } + } else if ("bench".equals(args[0])) { + try { + benchTest(args); + } catch (Exception e) { + System.out.printf("fwailed running bench test: %s\n", e); + } + } else { + printHelp(args); + } + } + +} From 5c5c0ab160336e81505b5e170aa534c633c1d6ce Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Fri, 28 Nov 2025 16:33:47 +0800 Subject: [PATCH 07/10] add ipAddOne and ipSubOne impls --- .../java/org/lionsoul/ip2region/xdb/Util.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java index 461d24c..496c4cc 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Util.java @@ -79,4 +79,36 @@ public class Util return 0; } + public static byte[] ipAddOne(byte[] ip) { + final byte[] r = new byte[ip.length]; + System.arraycopy(ip, 0, r, 0, ip.length); + for (int i = ip.length - 1; i >= 0; i--) { + final int v = (int)(r[i] & 0xFF); + if (v < 255) { // No overflow + r[i]++; + break; + } + + r[i] = 0; + } + + return r; + } + + public static byte[] ipSubOne(byte[] ip) { + final byte[] r = new byte[ip.length]; + System.arraycopy(ip, 0, r, 0, ip.length); + for (int i = ip.length - 1; i >= 0; i--) { + final int v = (int)(r[i] & 0xFF); + if (v > 0) { // No borrow needed + r[i]--; + break; + } + + r[i] = (byte) 0xFF; // borrow from the next byte + } + + return r; + } + } From efbbb576ee65bad1bcaefed127811c08b6727734 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Fri, 28 Nov 2025 16:34:06 +0800 Subject: [PATCH 08/10] SearcherApp to SearcherTest --- .../org/lionsoul/ip2region/SearchApp.java | 251 ------------------ 1 file changed, 251 deletions(-) delete mode 100644 binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java deleted file mode 100644 index 64fa6da..0000000 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearchApp.java +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2022 The Ip2Region Authors. All rights reserved. -// Use of this source code is governed by a Apache2.0-style -// license that can be found in the LICENSE file. -// @Author Lion -// @Date 2022/06/23 - -package org.lionsoul.ip2region; - -import org.lionsoul.ip2region.xdb.InetAddressException; -import org.lionsoul.ip2region.xdb.XdbException; -import org.lionsoul.ip2region.xdb.LongByteArray; -import org.lionsoul.ip2region.xdb.Searcher; -import org.lionsoul.ip2region.xdb.Util; -import org.lionsoul.ip2region.xdb.Version; - -import java.io.*; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; - -public class SearchApp { - - public static void printHelp(String[] args) { - System.out.print("ip2region xdb searcher\n"); - System.out.print("java -jar ip2region-{version}.jar [command] [command options]\n"); - System.out.print("Command: \n"); - System.out.print(" search search input test\n"); - System.out.print(" bench search bench test\n"); - } - - public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException, XdbException { - final RandomAccessFile handle = new RandomAccessFile(dbPath, "r"); - - // verify the xdb file - // @Note: do NOT call it every time you create a searcher since this will slow - // down the search response. - // @see the util.Verify function for details. - Searcher.verify(handle); - - // get the ip version from header - final Version version = Version.fromHeader(Searcher.loadHeader(handle)); - - // create the final searcher - if ("file".equals(cachePolicy)) { - return Searcher.newWithFileOnly(version, dbPath); - } else if ("vectorIndex".equals(cachePolicy)) { - byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath); - return Searcher.newWithVectorIndex(version, dbPath, vIndex); - } else if ("content".equals(cachePolicy)) { - LongByteArray cBuff = Searcher.loadContentFromFile(dbPath); - return Searcher.newWithBuffer(version, cBuff); - } else { - throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content"); - } - } - - public static void searchTest(String[] args) throws IOException, XdbException { - String dbPath = "", cachePolicy = "vectorIndex"; - for (final String r : args) { - if (r.length() < 5) { - continue; - } - - if (r.indexOf("--") != 0) { - continue; - } - - int sIdx = r.indexOf('='); - if (sIdx < 0) { - System.out.printf("missing = for args pair `%s`\n", r); - return; - } - - String key = r.substring(2, sIdx); - String val = r.substring(sIdx + 1); - // System.out.printf("key=%s, val=%s\n", key, val); - if ("db".equals(key)) { - dbPath = val; - } else if ("cache-policy".equals(key)) { - cachePolicy = val; - } else { - System.out.printf("undefined option `%s`\n", r); - return; - } - } - - if (dbPath.isEmpty()) { - System.out.print("java -jar ip2region-{version}.jar search [command options]\n"); - System.out.print("options:\n"); - System.out.print(" --db string ip2region binary xdb file path\n"); - System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n"); - return; - } - - Searcher searcher = createSearcher(dbPath, cachePolicy); - final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - System.out.printf("ip2region xdb searcher test program\n" -+ "source xdb: %s (%s, %s)\n" -+ "type 'quit' to exit\n", dbPath, searcher.getIPVersion().name, cachePolicy); - while ( true ) { - System.out.print("ip2region>> "); - String line = reader.readLine().trim(); - if ( line.length() < 2 ) { - continue; - } - - if ( line.equalsIgnoreCase("quit") ) { - break; - } - - try { - double sTime = System.nanoTime(); - String region = searcher.search(line); - 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("{err: %s, ioCount: %d}\n", e, searcher.getIOCount()); - } - } - - reader.close(); - searcher.close(); - System.out.println("searcher test program exited, thanks for trying"); - } - - public static void benchTest(String[] args) throws IOException, XdbException, InetAddressException { - String dbPath = "", srcPath = "", cachePolicy = "vectorIndex"; - for (final String r : args) { - if (r.length() < 5) { - continue; - } - - if (r.indexOf("--") != 0) { - continue; - } - - int sIdx = r.indexOf('='); - if (sIdx < 0) { - System.out.printf("missing = for args pair `%s`\n", r); - return; - } - - String key = r.substring(2, sIdx); - String val = r.substring(sIdx + 1); - if ("db".equals(key)) { - dbPath = val; - } else if ("src".equals(key)) { - srcPath = val; - } else if ("cache-policy".equals(key)) { - cachePolicy = val; - } else { - System.out.printf("undefined option `%s`\n", r); - return; - } - } - - if (dbPath.length() < 1 || srcPath.length() < 1) { - System.out.print("java -jar ip2region-{version}.jar bench [command options]\n"); - System.out.print("options:\n"); - System.out.print(" --db string ip2region binary xdb file path\n"); - System.out.print(" --src string source ip text file path\n"); - System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n"); - return; - } - - Searcher searcher = createSearcher(dbPath, cachePolicy); - long count = 0, costs = 0, tStart = System.nanoTime(); - String line; - final Charset charset = Charset.forName("utf-8"); - final FileInputStream fis = new FileInputStream(srcPath); - final BufferedReader reader = new BufferedReader(new InputStreamReader(fis, charset)); - while ((line = reader.readLine()) != null) { - String l = line.trim(); - String[] ps = l.split("\\|", 3); - if (ps.length != 3) { - reader.close(); - System.out.printf("invalid ip segment `%s`\n", l); - return; - } - - byte[] sip; - try { - sip = Util.parseIP(ps[0]); - } catch (Exception e) { - reader.close(); - System.out.printf("check start ip `%s`: %s\n", ps[0], e); - return; - } - - byte[] eip; - try { - eip = Util.parseIP(ps[1]); - } catch (Exception e) { - reader.close(); - System.out.printf("check end ip `%s`: %s\n", ps[1], e); - return; - } - - if (Util.ipCompare(sip, eip) > 0) { - reader.close(); - System.out.printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1]); - return; - } - - for (final byte[] ip : new byte[][]{sip, eip}) { - long sTime = System.nanoTime(); - String region = searcher.search(ip); - costs += System.nanoTime() - sTime; - - // check the region info - if (!ps[2].equals(region)) { - System.out.printf("failed search(%s) with (%s != %s)\n", Util.ipToString(ip), region, ps[2]); - reader.close(); - return; - } - - count++; - } - } - - reader.close(); - searcher.close(); - long took = System.nanoTime() - tStart; - System.out.printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %d μs/op}\n", - cachePolicy, count, TimeUnit.NANOSECONDS.toSeconds(took), - count == 0 ? 0 : TimeUnit.NANOSECONDS.toMicros(costs/count)); - } - - public static void main(String[] args) { - if (args.length < 1) { - printHelp(args); - return; - } - - if ("search".equals(args[0])) { - try { - searchTest(args); - } catch (Exception e) { - System.out.printf("failed running search test: %s\n", e); - } - } else if ("bench".equals(args[0])) { - try { - benchTest(args); - } catch (Exception e) { - System.out.printf("fwailed running bench test: %s\n", e); - } - } else { - printHelp(args); - } - } - -} From b5ae94b36d57275e41a47cb1637c01e1956743d4 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Fri, 28 Nov 2025 16:34:48 +0800 Subject: [PATCH 09/10] ip2region service with thread safe and auto ip version detect supports --- .../org/lionsoul/ip2region/Ip2Region.java | 114 ++++++++++++++++-- .../org/lionsoul/ip2region/SearcherPool.java | 6 +- .../org/lionsoul/ip2region/ConfigTest.java | 4 +- .../org/lionsoul/ip2region/Ip2RegionTest.java | 54 +++++++++ .../lionsoul/ip2region/SearcherPoolTest.java | 18 +-- 5 files changed, 166 insertions(+), 30 deletions(-) create mode 100644 binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java 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 b5c116c..0a41282 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java @@ -9,6 +9,7 @@ import java.io.IOException; import org.lionsoul.ip2region.xdb.InetAddressException; import org.lionsoul.ip2region.xdb.Searcher; import org.lionsoul.ip2region.xdb.Util; +import org.lionsoul.ip2region.xdb.XdbException; /** * ip2region searcher manager service to provider: @@ -20,15 +21,66 @@ import org.lionsoul.ip2region.xdb.Util; */ public class Ip2Region { - /* v4 pool */ + /* v4 pool for cache policy vIndex or NoCache */ private final SearcherPool v4Pool; - /* v6 pool */ + /* v4 xdb searcher for cache policy cBuffer */ + private final Searcher v4InMemSearcher; + + /* v6 pool for cache policy vIndex or NoCache*/ private final SearcherPool v6Pool; + /* v6 xdb searcher for cache policy cBuffer */ + private final Searcher v6InMemSearcher; + + /** + * init the ip2reigon with two xdb file path and default cachePolicy vIndex. + * set it to null to disabled the search for specified version + * + * @param v4XdbPath + * @param v6XdbPath + * @throws XdbException + * @throws IOException + */ + public 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() + ); + } + + /** + * init the ip2region with specified config. + * set it to null for disabled the search for specified version + * + * @param v4Config + * @param v6Config + * @throws IOException + */ public Ip2Region(Config v4Config, Config v6Config) throws IOException { - this.v4Pool = new SearcherPool(v4Config); - this.v6Pool = new SearcherPool(v6Config); + if (v4Config == null) { + // @Note: with IPv4 disabled ? + this.v4InMemSearcher = null; + this.v4Pool = null; + } else if (v4Config.cachePolicy == Config.BufferCache) { + this.v4InMemSearcher = Searcher.newWithBuffer(v4Config.ipVersion, v4Config.cBuffer); + this.v4Pool = null; + } else { + this.v4InMemSearcher = null; + this.v4Pool = new SearcherPool(v4Config); + } + + if (v6Config == null) { + // @Note: with IPv6 disabled ? + this.v6InMemSearcher = null; + this.v6Pool = null; + } else if (v6Config.cachePolicy == Config.BufferCache) { + this.v6InMemSearcher = Searcher.newWithBuffer(v6Config.ipVersion, v6Config.cBuffer); + this.v6Pool = null; + } else { + this.v6InMemSearcher = null; + this.v6Pool = new SearcherPool(v6Config); + } } public String search(String ipString) throws InetAddressException, IOException, InterruptedException { @@ -36,24 +88,62 @@ public class Ip2Region { } public String search(byte[] ipBytes) throws InetAddressException, IOException, InterruptedException { - // 1, define the pool with the input ip - final SearcherPool pool; if (ipBytes.length == 4) { - pool = v4Pool; + return v4Search(ipBytes); } else if (ipBytes.length == 16) { - pool = v6Pool; + return v6Search(ipBytes); } else { throw new InetAddressException("invalid byte ip address with length=" + ipBytes.length); } + } - // 2, get a searcher from the pool - final Searcher searcher = pool.borrowSearcher(); + protected String v4Search(final byte[] ipBytes) throws IOException, InetAddressException, InterruptedException { + if (v4InMemSearcher != null) { + return v4InMemSearcher.search(ipBytes); + } + // IPv4 search is disabled + if (v4Pool == null) { + return null; + } + + final Searcher searcher = v4Pool.borrowSearcher(); try { - // 3, do the search return searcher.search(ipBytes); } finally { - pool.returnSearcher(searcher); + v4Pool.returnSearcher(searcher); + } + } + + protected String v6Search(final byte[] ipBytes) throws IOException, InetAddressException, InterruptedException { + if (v6InMemSearcher != null) { + return v6InMemSearcher.search(ipBytes); + } + + // IPv6 search is disabled + if (v6Pool == null) { + return null; + } + + final Searcher searcher = v6Pool.borrowSearcher(); + try { + return searcher.search(ipBytes); + } finally { + v6Pool.returnSearcher(searcher); + } + } + + public void close() throws InterruptedException { + close(10000); + } + + public void close(long timeoutMillis) throws InterruptedException { + if (v4Pool != null) { + v4Pool.close(timeoutMillis); + } + + if (v6Pool != null) { + v6Pool.close(timeoutMillis); } } 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 6f362b8..d5167be 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -94,10 +94,14 @@ public class SearcherPool { // close the searcher pool public void close() throws InterruptedException { + close(10000); + } + + public void close(long timeoutMillis) throws InterruptedException { lock.lock(); try { while (loanCount > 0) { - fullCondition.wait(); + fullCondition.wait(timeoutMillis); } final Iterator it = pool.iterator(); diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java index 5664012..178e070 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/ConfigTest.java @@ -10,8 +10,8 @@ public class ConfigTest { private static final Log log = Log.getLogger(ConfigTest.class).setLevel(Log.DEBUG); - public String getDataPath(String xdbFile) { - final CodeSource cs = this.getClass().getProtectionDomain().getCodeSource(); + public static final String getDataPath(String xdbFile) { + final CodeSource cs = ConfigTest.class.getProtectionDomain().getCodeSource(); if (cs != null) { // log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/")); return cs.getLocation().getPath().concat("../../../../data/").concat(xdbFile); diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java new file mode 100644 index 0000000..f77dd2a --- /dev/null +++ b/binding/java/src/test/java/org/lionsoul/ip2region/Ip2RegionTest.java @@ -0,0 +1,54 @@ +package org.lionsoul.ip2region; + +import java.io.IOException; + +import org.junit.Test; +import org.lionsoul.ip2region.xdb.InetAddressException; +import org.lionsoul.ip2region.xdb.Log; +import org.lionsoul.ip2region.xdb.Util; +import org.lionsoul.ip2region.xdb.XdbException; + +public class Ip2RegionTest { + + private static final Log log = Log.getLogger(Ip2RegionTest.class).setLevel(Log.DEBUG); + + @Test + public void TestConfigCreate() throws IOException, XdbException, InetAddressException, InterruptedException { + final Config v4Config = Config.custom() + .setCachePolicy(Config.NoCache) + .setSeachers(10) + .setXdbPath(ConfigTest.getDataPath("ip2region_v4.xdb")) + .asV4(); + + final Config v6Config = Config.custom() + .setCachePolicy(Config.VIndexCache) + .setSeachers(10) + .setXdbPath(ConfigTest.getDataPath("ip2region_v6.xdb")) + .asV6(); + + byte[] v4Bytes = Util.parseIP("1.2.3.4"); + byte[] v6Bytes = Util.parseIP("240e:3b7:3272:d8d0:db09:c067:8d59:539e"); + final Ip2Region ip2Region = new Ip2Region(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 TestPathCreate() { + + } + + @Test + public void TestInMemSearch() { + + } + +} 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 ef51252..db80a7c 100644 --- a/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java +++ b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java @@ -1,31 +1,19 @@ package org.lionsoul.ip2region; -import java.security.CodeSource; - import org.junit.Test; import org.lionsoul.ip2region.xdb.Log; import org.lionsoul.ip2region.xdb.Searcher; public class SearcherPoolTest { - private static final Log log = Log.getLogger(ConfigTest.class).setLevel(Log.DEBUG); - - public String getDataPath(String xdbFile) { - final CodeSource cs = this.getClass().getProtectionDomain().getCodeSource(); - if (cs != null) { - // log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/")); - return cs.getLocation().getPath().concat("../../../../data/").concat(xdbFile); - } else { - return "../../../../data/".concat(xdbFile); - } - } + private static final Log log = Log.getLogger(SearcherPoolTest.class).setLevel(Log.DEBUG); @Test public void testV4SeacherPool() throws Exception { final Config v4Config = Config.custom() .setCachePolicy(Config.VIndexCache) .setSeachers(5) - .setXdbPath(getDataPath("ip2region_v4.xdb")) + .setXdbPath(ConfigTest.getDataPath("ip2region_v4.xdb")) .asV4(); @@ -49,7 +37,7 @@ public class SearcherPoolTest { final Config v6Config = Config.custom() .setCachePolicy(Config.VIndexCache) .setSeachers(5) - .setXdbPath(getDataPath("ip2region_v6.xdb")) + .setXdbPath(ConfigTest.getDataPath("ip2region_v6.xdb")) .asV6(); From 942f3d9dbd10086893368c88395e70f3c2b76aa2 Mon Sep 17 00:00:00 2001 From: lionsoul2014 Date: Mon, 1 Dec 2025 13:03:11 +0800 Subject: [PATCH 10/10] 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());