From 700ae3af0a95f2672fb23ec0990d1b461d812d43 Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Fri, 25 Nov 2022 14:57:50 +0800 Subject: [PATCH 1/6] =?UTF-8?q?1.Update=20=E8=B0=83=E6=95=B4=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E7=BB=93=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../consistenthash/AbstractHashStrategy.java | 17 +++++ .../ConsistentHashLoadBalancer.java | 48 -------------- .../cnkirito/consistenthash/HashStrategy.java | 11 ---- .../consistenthash/JdkHashCodeStrategy.java | 14 ---- .../consistenthash/LoadBalanceable.java | 22 +++++++ .../cnkirito/consistenthash/LoadBalancer.java | 12 ---- .../consistenthash/StatisticsUtil.java | 39 ----------- .../consistenthash/{ => bo}/Invocation.java | 9 ++- .../consistenthash/{ => bo}/Server.java | 9 ++- .../balancer/ConsistentHashLoadBalancer.java | 30 +++++++++ .../load/balancer/ConsistentHashRing.java | 65 +++++++++++++++++++ .../KetamaConsistentHashLoadBalancer.java | 44 +++++-------- .../balancer/constant/HashRingConstant.java | 10 +++ .../CrcHashStrategy.java} | 26 ++++---- .../{ => strategy}/FnvHashStrategy.java | 12 ++-- .../strategy/HashStratetyFactory.java | 27 ++++++++ .../strategy/JdkHashCodeStrategy.java | 16 +++++ .../{ => strategy}/KetamaHashStrategy.java | 20 ++---- .../{ => strategy}/MurmurHashStrategy.java | 14 ++-- .../cnkirito/consistenthash/util/Md5Util.java | 21 ++++++ .../consistenthash/util/StatisticsUtil.java | 41 ++++++++++++ .../consistenthash/LoadBalanceTest.java | 12 +++- 22 files changed, 322 insertions(+), 197 deletions(-) create mode 100644 src/main/java/moe/cnkirito/consistenthash/AbstractHashStrategy.java delete mode 100644 src/main/java/moe/cnkirito/consistenthash/ConsistentHashLoadBalancer.java delete mode 100644 src/main/java/moe/cnkirito/consistenthash/HashStrategy.java delete mode 100644 src/main/java/moe/cnkirito/consistenthash/JdkHashCodeStrategy.java create mode 100644 src/main/java/moe/cnkirito/consistenthash/LoadBalanceable.java delete mode 100644 src/main/java/moe/cnkirito/consistenthash/LoadBalancer.java delete mode 100644 src/main/java/moe/cnkirito/consistenthash/StatisticsUtil.java rename src/main/java/moe/cnkirito/consistenthash/{ => bo}/Invocation.java (82%) rename src/main/java/moe/cnkirito/consistenthash/{ => bo}/Server.java (81%) create mode 100644 src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashLoadBalancer.java create mode 100644 src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java rename src/main/java/moe/cnkirito/consistenthash/{ => load/balancer}/KetamaConsistentHashLoadBalancer.java (65%) create mode 100644 src/main/java/moe/cnkirito/consistenthash/load/balancer/constant/HashRingConstant.java rename src/main/java/moe/cnkirito/consistenthash/{CRCHashStrategy.java => strategy/CrcHashStrategy.java} (82%) rename src/main/java/moe/cnkirito/consistenthash/{ => strategy}/FnvHashStrategy.java (62%) create mode 100644 src/main/java/moe/cnkirito/consistenthash/strategy/HashStratetyFactory.java create mode 100644 src/main/java/moe/cnkirito/consistenthash/strategy/JdkHashCodeStrategy.java rename src/main/java/moe/cnkirito/consistenthash/{ => strategy}/KetamaHashStrategy.java (62%) rename src/main/java/moe/cnkirito/consistenthash/{ => strategy}/MurmurHashStrategy.java (72%) create mode 100644 src/main/java/moe/cnkirito/consistenthash/util/Md5Util.java create mode 100644 src/main/java/moe/cnkirito/consistenthash/util/StatisticsUtil.java diff --git a/src/main/java/moe/cnkirito/consistenthash/AbstractHashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/AbstractHashStrategy.java new file mode 100644 index 0000000..de21702 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/AbstractHashStrategy.java @@ -0,0 +1,17 @@ +package moe.cnkirito.consistenthash; + +/** + * @author daofeng.xjf + * @date 2019/2/15 + */ +public interface AbstractHashStrategy { + + /** + * 根据服务器地址标识符生成在一致性hash环上的hash值 + * + * @param origin 服务器地址标识字符串 + * @return 一致性hash环上的hash值 + */ + int getHashCode(String origin); + +} diff --git a/src/main/java/moe/cnkirito/consistenthash/ConsistentHashLoadBalancer.java b/src/main/java/moe/cnkirito/consistenthash/ConsistentHashLoadBalancer.java deleted file mode 100644 index b88e132..0000000 --- a/src/main/java/moe/cnkirito/consistenthash/ConsistentHashLoadBalancer.java +++ /dev/null @@ -1,48 +0,0 @@ -package moe.cnkirito.consistenthash; - - -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -/** - * @author daofeng.xjf - * @date 2019/2/15 - */ -public class ConsistentHashLoadBalancer implements LoadBalancer{ - - private HashStrategy hashStrategy = new FnvHashStrategy(); - - private final static int VIRTUAL_NODE_SIZE = 10; - private final static String VIRTUAL_NODE_SUFFIX = "&&"; - - @Override - public Server select(List servers, Invocation invocation) { - int invocationHashCode = hashStrategy.getHashCode(invocation.getHashKey()); - TreeMap ring = buildConsistentHashRing(servers); - Server server = locate(ring, invocationHashCode); - return server; - } - - private Server locate(TreeMap ring, int invocationHashCode) { - // 向右找到第一个 key - Map.Entry locateEntry = ring.ceilingEntry(invocationHashCode); - if (locateEntry == null) { - // 想象成一个环,超过尾部则取第一个 key - locateEntry = ring.firstEntry(); - } - return locateEntry.getValue(); - } - - private TreeMap buildConsistentHashRing(List servers) { - TreeMap virtualNodeRing = new TreeMap<>(); - for (Server server : servers) { - for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { - // 新增虚拟节点的方式如果有影响,也可以抽象出一个由物理节点扩展虚拟节点的类 - virtualNodeRing.put(hashStrategy.getHashCode(server.getUrl() + VIRTUAL_NODE_SUFFIX + i), server); - } - } - return virtualNodeRing; - } - -} diff --git a/src/main/java/moe/cnkirito/consistenthash/HashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/HashStrategy.java deleted file mode 100644 index b451260..0000000 --- a/src/main/java/moe/cnkirito/consistenthash/HashStrategy.java +++ /dev/null @@ -1,11 +0,0 @@ -package moe.cnkirito.consistenthash; - -/** - * @author daofeng.xjf - * @date 2019/2/15 - */ -public interface HashStrategy { - - int getHashCode(String origin); - -} diff --git a/src/main/java/moe/cnkirito/consistenthash/JdkHashCodeStrategy.java b/src/main/java/moe/cnkirito/consistenthash/JdkHashCodeStrategy.java deleted file mode 100644 index f6cdfa3..0000000 --- a/src/main/java/moe/cnkirito/consistenthash/JdkHashCodeStrategy.java +++ /dev/null @@ -1,14 +0,0 @@ -package moe.cnkirito.consistenthash; - -/** - * @author daofeng.xjf - * @date 2019/2/15 - */ -public class JdkHashCodeStrategy implements HashStrategy { - - @Override - public int getHashCode(String origin) { - return origin.hashCode(); - } - -} diff --git a/src/main/java/moe/cnkirito/consistenthash/LoadBalanceable.java b/src/main/java/moe/cnkirito/consistenthash/LoadBalanceable.java new file mode 100644 index 0000000..21bf3c9 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/LoadBalanceable.java @@ -0,0 +1,22 @@ +package moe.cnkirito.consistenthash; + +import moe.cnkirito.consistenthash.bo.Invocation; +import moe.cnkirito.consistenthash.bo.Server; + +import java.util.List; + +/** + * @author daofeng.xjf + * @date 2019/2/16 + */ +public interface LoadBalanceable { + + /** + * 根据数据的hash值在一致性hash环上选择第一个服务器 + * + * @param servers 服务器列表 + * @param invocation 包含数据hash值的请求 + * @return 选择到的服务器 + */ + Server select(List servers, Invocation invocation); +} diff --git a/src/main/java/moe/cnkirito/consistenthash/LoadBalancer.java b/src/main/java/moe/cnkirito/consistenthash/LoadBalancer.java deleted file mode 100644 index 86edd02..0000000 --- a/src/main/java/moe/cnkirito/consistenthash/LoadBalancer.java +++ /dev/null @@ -1,12 +0,0 @@ -package moe.cnkirito.consistenthash; - -import java.util.List; - -/** - * @author daofeng.xjf - * @date 2019/2/16 - */ -public interface LoadBalancer { - - Server select(List servers, Invocation invocation); -} diff --git a/src/main/java/moe/cnkirito/consistenthash/StatisticsUtil.java b/src/main/java/moe/cnkirito/consistenthash/StatisticsUtil.java deleted file mode 100644 index c9b6c0b..0000000 --- a/src/main/java/moe/cnkirito/consistenthash/StatisticsUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package moe.cnkirito.consistenthash; - -/** - * @author daofeng.xjf - * @date 2019/2/16 - */ -public class StatisticsUtil { - - //方差s^2=[(x1-x)^2 +...(xn-x)^2]/n - public static double variance(Long[] x) { - int m = x.length; - double sum = 0; - for (int i = 0; i < m; i++) {//求和 - sum += x[i]; - } - double dAve = sum / m;//求平均值 - double dVar = 0; - for (int i = 0; i < m; i++) {//求方差 - dVar += (x[i] - dAve) * (x[i] - dAve); - } - return dVar / m; - } - - //标准差σ=sqrt(s^2) - public static double standardDeviation(Long[] x) { - int m = x.length; - double sum = 0; - for (int i = 0; i < m; i++) {//求和 - sum += x[i]; - } - double dAve = sum / m;//求平均值 - double dVar = 0; - for (int i = 0; i < m; i++) {//求方差 - dVar += (x[i] - dAve) * (x[i] - dAve); - } - return Math.sqrt(dVar / m); - } - -} diff --git a/src/main/java/moe/cnkirito/consistenthash/Invocation.java b/src/main/java/moe/cnkirito/consistenthash/bo/Invocation.java similarity index 82% rename from src/main/java/moe/cnkirito/consistenthash/Invocation.java rename to src/main/java/moe/cnkirito/consistenthash/bo/Invocation.java index 6a01ca5..bf90acc 100644 --- a/src/main/java/moe/cnkirito/consistenthash/Invocation.java +++ b/src/main/java/moe/cnkirito/consistenthash/bo/Invocation.java @@ -1,19 +1,18 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.bo; /** * @author daofeng.xjf * @date 2019/2/15 */ public class Invocation { - public Invocation() { - } + private String hashKey; + + public Invocation() {} public Invocation(String hashKey) { this.hashKey = hashKey; } - private String hashKey; - public String getHashKey() { return hashKey; } diff --git a/src/main/java/moe/cnkirito/consistenthash/Server.java b/src/main/java/moe/cnkirito/consistenthash/bo/Server.java similarity index 81% rename from src/main/java/moe/cnkirito/consistenthash/Server.java rename to src/main/java/moe/cnkirito/consistenthash/bo/Server.java index 219749a..b5a5a35 100644 --- a/src/main/java/moe/cnkirito/consistenthash/Server.java +++ b/src/main/java/moe/cnkirito/consistenthash/bo/Server.java @@ -1,4 +1,4 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.bo; /** * @author daofeng.xjf @@ -6,15 +6,14 @@ */ public class Server { - public Server() { - } + private String url; + + public Server() {} public Server(String url) { this.url = url; } - private String url; - public String getUrl() { return url; } diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashLoadBalancer.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashLoadBalancer.java new file mode 100644 index 0000000..0fc3893 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashLoadBalancer.java @@ -0,0 +1,30 @@ +package moe.cnkirito.consistenthash.load.balancer; + + +import moe.cnkirito.consistenthash.AbstractHashStrategy; +import moe.cnkirito.consistenthash.bo.Invocation; +import moe.cnkirito.consistenthash.LoadBalanceable; +import moe.cnkirito.consistenthash.bo.Server; +import moe.cnkirito.consistenthash.strategy.FnvHashStrategy; +import moe.cnkirito.consistenthash.strategy.HashStratetyFactory; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * @author daofeng.xjf + * @date 2019/2/15 + */ +public class ConsistentHashLoadBalancer implements LoadBalanceable { + + private static final AbstractHashStrategy hashStrategy = HashStratetyFactory.getHashStrategy(); + + @Override + public Server select(List servers, Invocation invocation) { + int invocationHashCode = hashStrategy.getHashCode(invocation.getHashKey()); + ConsistentHashRing consistentHashRing = ConsistentHashRing.getInstance(servers); + return consistentHashRing.locate(invocationHashCode); + } + +} diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java new file mode 100644 index 0000000..28084a1 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java @@ -0,0 +1,65 @@ +package moe.cnkirito.consistenthash.load.balancer; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; +import moe.cnkirito.consistenthash.bo.Server; +import moe.cnkirito.consistenthash.strategy.FnvHashStrategy; +import moe.cnkirito.consistenthash.strategy.HashStratetyFactory; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static moe.cnkirito.consistenthash.load.balancer.constant.HashRingConstant.VIRTUAL_NODE_SIZE; +import static moe.cnkirito.consistenthash.load.balancer.constant.HashRingConstant.VIRTUAL_NODE_SUFFIX; + +/** + * 一致性hash环 + * + * @author dailj + * @date 2022/11/25 15:54 + */ +public class ConsistentHashRing { + private static ConsistentHashRing instance; + private static TreeMap virtualNodeRing; + private static AbstractHashStrategy hashStrategy = HashStratetyFactory.getHashStrategy(); + + public static ConsistentHashRing getInstance(List servers) { + if (instance == null) { + synchronized (ConsistentHashRing.class) { + if (instance == null) { + instance = new ConsistentHashRing(servers); + } + } + } + return instance; + } + + public Server locate(Integer invocationHashCode) { + // 向右找到第一个 key + Map.Entry locateEntry = virtualNodeRing.ceilingEntry(invocationHashCode); + if (locateEntry == null) { + // 想象成一个环,超过尾部则取第一个 key + locateEntry = virtualNodeRing.firstEntry(); + } + return locateEntry.getValue(); + } + + private ConsistentHashRing(List servers) { + init(servers); + } + + private void init(List servers) { + virtualNodeRing = buildConsistentHashRing(servers); + } + + private TreeMap buildConsistentHashRing(List servers) { + TreeMap virtualNodeRing = new TreeMap<>(); + for (Server server : servers) { + for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { + // 新增虚拟节点的方式如果有影响,也可以抽象出一个由物理节点扩展虚拟节点的类 + virtualNodeRing.put(hashStrategy.getHashCode(server.getUrl() + VIRTUAL_NODE_SUFFIX + i), server); + } + } + return virtualNodeRing; + } +} diff --git a/src/main/java/moe/cnkirito/consistenthash/KetamaConsistentHashLoadBalancer.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/KetamaConsistentHashLoadBalancer.java similarity index 65% rename from src/main/java/moe/cnkirito/consistenthash/KetamaConsistentHashLoadBalancer.java rename to src/main/java/moe/cnkirito/consistenthash/load/balancer/KetamaConsistentHashLoadBalancer.java index c307800..98e266d 100644 --- a/src/main/java/moe/cnkirito/consistenthash/KetamaConsistentHashLoadBalancer.java +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/KetamaConsistentHashLoadBalancer.java @@ -1,8 +1,12 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.load.balancer; +import moe.cnkirito.consistenthash.bo.Invocation; +import moe.cnkirito.consistenthash.LoadBalanceable; +import moe.cnkirito.consistenthash.bo.Server; +import moe.cnkirito.consistenthash.util.Md5Util; + import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -11,27 +15,16 @@ * @author daofeng.xjf * @date 2019/2/15 */ -public class KetamaConsistentHashLoadBalancer implements LoadBalancer { - - private static MessageDigest md5Digest; - - static { - try { - md5Digest = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5 not supported", e); - } - } - - private final static int VIRTUAL_NODE_SIZE = 12; - private final static String VIRTUAL_NODE_SUFFIX = "-"; +public class KetamaConsistentHashLoadBalancer implements LoadBalanceable { + private static final Integer BASE_SIZE = 4; + private static final int VIRTUAL_NODE_SIZE = 12; + private static final String VIRTUAL_NODE_SUFFIX = "-"; @Override public Server select(List servers, Invocation invocation) { long invocationHashCode = getHashCode(invocation.getHashKey()); TreeMap ring = buildConsistentHashRing(servers); - Server server = locate(ring, invocationHashCode); - return server; + return locate(ring, invocationHashCode); } private Server locate(TreeMap ring, Long invocationHashCode) { @@ -47,12 +40,12 @@ private Server locate(TreeMap ring, Long invocationHashCode) { private TreeMap buildConsistentHashRing(List servers) { TreeMap virtualNodeRing = new TreeMap<>(); for (Server server : servers) { - for (int i = 0; i < VIRTUAL_NODE_SIZE / 4; i++) { + for (int i = 0; i < VIRTUAL_NODE_SIZE / BASE_SIZE; i++) { byte[] digest = computeMd5(server.getUrl() + VIRTUAL_NODE_SUFFIX + i); - for (int h = 0; h < 4; h++) { - Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24) - | ((long) (digest[2 + h * 4] & 0xFF) << 16) - | ((long) (digest[1 + h * 4] & 0xFF) << 8) + for (int h = 0; h < BASE_SIZE; h++) { + Long k = ((long) (digest[3 + h * BASE_SIZE] & 0xFF) << 24) + | ((long) (digest[2 + h * BASE_SIZE] & 0xFF) << 16) + | ((long) (digest[1 + h * BASE_SIZE] & 0xFF) << 8) | (digest[h * 4] & 0xFF); virtualNodeRing.put(k, server); @@ -64,17 +57,16 @@ private TreeMap buildConsistentHashRing(List servers) { private long getHashCode(String origin) { byte[] bKey = computeMd5(origin); - long rv = ((long) (bKey[3] & 0xFF) << 24) + return ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8) | (bKey[0] & 0xFF); - return rv; } private static byte[] computeMd5(String k) { MessageDigest md5; try { - md5 = (MessageDigest) md5Digest.clone(); + md5 = (MessageDigest) Md5Util.MD5_DIGEST.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException("clone of MD5 not supported", e); } diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/constant/HashRingConstant.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/constant/HashRingConstant.java new file mode 100644 index 0000000..db65993 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/constant/HashRingConstant.java @@ -0,0 +1,10 @@ +package moe.cnkirito.consistenthash.load.balancer.constant; + +/** + * @author dailj + * @date 2022/11/25 16:20 + */ +public class HashRingConstant { + public static final int VIRTUAL_NODE_SIZE = 10; + public static final String VIRTUAL_NODE_SUFFIX = "&&"; +} diff --git a/src/main/java/moe/cnkirito/consistenthash/CRCHashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/strategy/CrcHashStrategy.java similarity index 82% rename from src/main/java/moe/cnkirito/consistenthash/CRCHashStrategy.java rename to src/main/java/moe/cnkirito/consistenthash/strategy/CrcHashStrategy.java index 5ead2fd..899609f 100644 --- a/src/main/java/moe/cnkirito/consistenthash/CRCHashStrategy.java +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/CrcHashStrategy.java @@ -1,14 +1,16 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.strategy; -import java.nio.charset.Charset; +import moe.cnkirito.consistenthash.AbstractHashStrategy; + +import java.nio.charset.StandardCharsets; /** * @author daofeng.xjf * @date 2019/2/16 */ -public class CRCHashStrategy implements HashStrategy { +public class CrcHashStrategy implements AbstractHashStrategy { - private static final int LOOKUP_TABLE[] = {0x0000, 0x1021, 0x2042, 0x3063, + private static final int[] LOOKUP_TABLE = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, @@ -46,10 +48,10 @@ public class CRCHashStrategy implements HashStrategy { * Create a CRC16 checksum from the bytes. implementation is from * mp911de/lettuce, modified with some more optimizations * - * @param bytes + * @param bytes 字节数组 * @return CRC16 as integer value */ - public static int getCRC16(byte[] bytes) { + public static int getCrc16(byte[] bytes) { int crc = 0x0000; for (byte b : bytes) { @@ -58,14 +60,16 @@ public static int getCRC16(byte[] bytes) { return crc & 0xFFFF; } - public static int getCRC16(String key) { - return getCRC16(key.getBytes(Charset.forName("UTF-8"))); + public static int getCrc16(String key) { + return getCrc16(key.getBytes(StandardCharsets.UTF_8)); } @Override public int getHashCode(String origin) { - // optimization with modulo operator with power of 2 - // equivalent to getCRC16(key) % 16384 - return getCRC16(origin) & (16384 - 1); + /** + * optimization with modulo operator with power of + * equivalent to getCRC16(key) % 16384 + */ + return getCrc16(origin) & (16384 - 1); } } diff --git a/src/main/java/moe/cnkirito/consistenthash/FnvHashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/strategy/FnvHashStrategy.java similarity index 62% rename from src/main/java/moe/cnkirito/consistenthash/FnvHashStrategy.java rename to src/main/java/moe/cnkirito/consistenthash/strategy/FnvHashStrategy.java index 42871e9..6ab1457 100644 --- a/src/main/java/moe/cnkirito/consistenthash/FnvHashStrategy.java +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/FnvHashStrategy.java @@ -1,4 +1,6 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.strategy; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; /** * @author daofeng.xjf @@ -6,17 +8,17 @@ *

* FNV1_32_HASH 算法 */ -public class FnvHashStrategy implements HashStrategy { +public class FnvHashStrategy implements AbstractHashStrategy { private static final long FNV_32_INIT = 2166136261L; private static final int FNV_32_PRIME = 16777619; @Override public int getHashCode(String origin) { - final int p = FNV_32_PRIME; int hash = (int) FNV_32_INIT; - for (int i = 0; i < origin.length(); i++) - hash = (hash ^ origin.charAt(i)) * p; + for (int i = 0; i < origin.length(); i++) { + hash = (hash ^ origin.charAt(i)) * FNV_32_PRIME; + } hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; diff --git a/src/main/java/moe/cnkirito/consistenthash/strategy/HashStratetyFactory.java b/src/main/java/moe/cnkirito/consistenthash/strategy/HashStratetyFactory.java new file mode 100644 index 0000000..100d5a7 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/HashStratetyFactory.java @@ -0,0 +1,27 @@ +package moe.cnkirito.consistenthash.strategy; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dailj + * @date 2022/11/25 17:47 + */ +public class HashStratetyFactory { + private static String type = "fnv"; + private static ConcurrentHashMap hashStrategyMap = new ConcurrentHashMap(); + + static { + hashStrategyMap.put("crc", new CrcHashStrategy()); + hashStrategyMap.put("fnv", new FnvHashStrategy()); + hashStrategyMap.put("jdk", new JdkHashCodeStrategy()); + hashStrategyMap.put("ket", new KetamaHashStrategy()); + hashStrategyMap.put("mur", new MurmurHashStrategy()); + } + + public static AbstractHashStrategy getHashStrategy() { + return hashStrategyMap.get(type); + } + +} diff --git a/src/main/java/moe/cnkirito/consistenthash/strategy/JdkHashCodeStrategy.java b/src/main/java/moe/cnkirito/consistenthash/strategy/JdkHashCodeStrategy.java new file mode 100644 index 0000000..9f3e3ec --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/JdkHashCodeStrategy.java @@ -0,0 +1,16 @@ +package moe.cnkirito.consistenthash.strategy; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; + +/** + * @author daofeng.xjf + * @date 2019/2/15 + */ +public class JdkHashCodeStrategy implements AbstractHashStrategy { + + @Override + public int getHashCode(String origin) { + return origin.hashCode(); + } + +} diff --git a/src/main/java/moe/cnkirito/consistenthash/KetamaHashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/strategy/KetamaHashStrategy.java similarity index 62% rename from src/main/java/moe/cnkirito/consistenthash/KetamaHashStrategy.java rename to src/main/java/moe/cnkirito/consistenthash/strategy/KetamaHashStrategy.java index 7283d1c..b27660e 100644 --- a/src/main/java/moe/cnkirito/consistenthash/KetamaHashStrategy.java +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/KetamaHashStrategy.java @@ -1,23 +1,15 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.strategy; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; +import moe.cnkirito.consistenthash.util.Md5Util; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; /** * @author daofeng.xjf * @date 2019/2/16 */ -public class KetamaHashStrategy implements HashStrategy { - - private static MessageDigest md5Digest; - - static { - try { - md5Digest = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5 not supported", e); - } - } +public class KetamaHashStrategy implements AbstractHashStrategy { @Override public int getHashCode(String origin) { @@ -35,7 +27,7 @@ public int getHashCode(String origin) { public static byte[] computeMd5(String k) { MessageDigest md5; try { - md5 = (MessageDigest) md5Digest.clone(); + md5 = (MessageDigest) Md5Util.MD5_DIGEST.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException("clone of MD5 not supported", e); } diff --git a/src/main/java/moe/cnkirito/consistenthash/MurmurHashStrategy.java b/src/main/java/moe/cnkirito/consistenthash/strategy/MurmurHashStrategy.java similarity index 72% rename from src/main/java/moe/cnkirito/consistenthash/MurmurHashStrategy.java rename to src/main/java/moe/cnkirito/consistenthash/strategy/MurmurHashStrategy.java index 4380f3e..b21945c 100644 --- a/src/main/java/moe/cnkirito/consistenthash/MurmurHashStrategy.java +++ b/src/main/java/moe/cnkirito/consistenthash/strategy/MurmurHashStrategy.java @@ -1,4 +1,6 @@ -package moe.cnkirito.consistenthash; +package moe.cnkirito.consistenthash.strategy; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -7,7 +9,9 @@ * @author daofeng.xjf * @date 2019/2/16 */ -public class MurmurHashStrategy implements HashStrategy { +public class MurmurHashStrategy implements AbstractHashStrategy { + private static final Integer BUFF_SIZE = 8; + @Override public int getHashCode(String origin) { @@ -23,7 +27,7 @@ public int getHashCode(String origin) { long h = seed ^ (buf.remaining() * m); long k; - while (buf.remaining() >= 8) { + while (buf.remaining() >= BUFF_SIZE) { k = buf.getLong(); k *= m; @@ -37,8 +41,8 @@ public int getHashCode(String origin) { if (buf.remaining() > 0) { ByteBuffer finish = ByteBuffer.allocate(8).order( ByteOrder.LITTLE_ENDIAN); - // for big-endian version, do this first: - // finish.position(8-buf.remaining()); + /// for big-endian version, do this first: + // finish.position(8-buf.remaining()); finish.put(buf).rewind(); h ^= finish.getLong(); h *= m; diff --git a/src/main/java/moe/cnkirito/consistenthash/util/Md5Util.java b/src/main/java/moe/cnkirito/consistenthash/util/Md5Util.java new file mode 100644 index 0000000..0f0dc35 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/util/Md5Util.java @@ -0,0 +1,21 @@ +package moe.cnkirito.consistenthash.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author dailj + * @date 2022/11/25 14:21 + */ +public class Md5Util { + + public static final MessageDigest MD5_DIGEST; + + static { + try { + MD5_DIGEST = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 not supported", e); + } + } +} diff --git a/src/main/java/moe/cnkirito/consistenthash/util/StatisticsUtil.java b/src/main/java/moe/cnkirito/consistenthash/util/StatisticsUtil.java new file mode 100644 index 0000000..6b9c6c0 --- /dev/null +++ b/src/main/java/moe/cnkirito/consistenthash/util/StatisticsUtil.java @@ -0,0 +1,41 @@ +package moe.cnkirito.consistenthash.util; + +/** + * @author daofeng.xjf + * @date 2019/2/16 + */ +public class StatisticsUtil { + + /** + * 计算方差 s^2=[(x1-x)^2 +...(xn-x)^2]/n + * + * @param x 参数列表 + * @return 方差 + */ + public static double variance(Long[] x) { + int m = x.length; + double sum = 0; + for (int i = 0; i < m; i++) { + sum += x[i]; + } + // 求平均值 + double dAve = sum / m; + double dVar = 0; + // 求方差 + for (int i = 0; i < m; i++) { + dVar += (x[i] - dAve) * (x[i] - dAve); + } + return dVar / m; + } + + /** + * 计算标准差 σ=sqrt(s^2) + * @param x 参数列表 + * @return 标准差 + */ + public static double standardDeviation(Long[] x) { + double varianceValue = variance(x); + return Math.sqrt(varianceValue); + } + +} diff --git a/src/test/java/moe/cnkirito/consistenthash/LoadBalanceTest.java b/src/test/java/moe/cnkirito/consistenthash/LoadBalanceTest.java index 62b81c0..37d18ef 100644 --- a/src/test/java/moe/cnkirito/consistenthash/LoadBalanceTest.java +++ b/src/test/java/moe/cnkirito/consistenthash/LoadBalanceTest.java @@ -1,6 +1,13 @@ package moe.cnkirito.consistenthash; import com.google.common.util.concurrent.AtomicLongMap; +import moe.cnkirito.consistenthash.bo.Invocation; +import moe.cnkirito.consistenthash.bo.Server; +import moe.cnkirito.consistenthash.load.balancer.ConsistentHashLoadBalancer; +import moe.cnkirito.consistenthash.load.balancer.ConsistentHashRing; +import moe.cnkirito.consistenthash.strategy.FnvHashStrategy; +import moe.cnkirito.consistenthash.util.StatisticsUtil; +import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -22,7 +29,7 @@ public void testDistribution() { for (String ip : ips) { servers.add(new Server(ip+":8080")); } - LoadBalancer chloadBalance = new ConsistentHashLoadBalancer(); + LoadBalanceable chloadBalance = new ConsistentHashLoadBalancer(); // 构造 10000 随机请求 List invocations = new ArrayList<>(); for (int i = 0; i < 10000; i++) { @@ -51,7 +58,7 @@ public void testNodeAddAndRemove() { servers.add(new Server(ip)); } List serverChanged = servers.subList(0, 80); - LoadBalancer chloadBalance = new ConsistentHashLoadBalancer(); + LoadBalanceable chloadBalance = new ConsistentHashLoadBalancer(); // 构造 10000 随机请求 List invocations = new ArrayList<>(); for (int i = 0; i < 10000; i++) { @@ -170,3 +177,4 @@ public void testNodeAddAndRemove() { "11.10.192.118" }; } + From c8d540b8e4246542416decd2bffb1043dc81446c Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Sat, 26 Nov 2022 16:28:23 +0800 Subject: [PATCH 2/6] =?UTF-8?q?1.Update=20=E6=B7=BB=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 26 ++++++++- .../load/balancer/ConsistentHashRing.java | 10 +++- .../load/balancer/ConsistentHashRingTest.java | 58 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java diff --git a/pom.xml b/pom.xml index 89935e3..4a918c0 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,30 @@ junit 4.12 + + net.bytebuddy + byte-buddy + 1.8.15 + test + + + net.bytebuddy + byte-buddy-agent + 1.8.15 + test + + + org.objenesis + objenesis + 2.6 + test + + + org.mockito + mockito-core + 2.21.0 + test + moe.cnkirito.sample @@ -34,4 +58,4 @@ - \ No newline at end of file + diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java index 28084a1..3abe8f2 100644 --- a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java @@ -1,5 +1,6 @@ package moe.cnkirito.consistenthash.load.balancer; +import com.google.common.annotations.VisibleForTesting; import moe.cnkirito.consistenthash.AbstractHashStrategy; import moe.cnkirito.consistenthash.bo.Server; import moe.cnkirito.consistenthash.strategy.FnvHashStrategy; @@ -44,15 +45,20 @@ public Server locate(Integer invocationHashCode) { return locateEntry.getValue(); } - private ConsistentHashRing(List servers) { + @VisibleForTesting + protected ConsistentHashRing(List servers) { init(servers); } + @VisibleForTesting + protected ConsistentHashRing() {} + private void init(List servers) { virtualNodeRing = buildConsistentHashRing(servers); } - private TreeMap buildConsistentHashRing(List servers) { + @VisibleForTesting + protected TreeMap buildConsistentHashRing(List servers) { TreeMap virtualNodeRing = new TreeMap<>(); for (Server server : servers) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java new file mode 100644 index 0000000..ab3b331 --- /dev/null +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java @@ -0,0 +1,58 @@ +package moe.cnkirito.consistenthash.load.balancer; + +import moe.cnkirito.consistenthash.AbstractHashStrategy; +import moe.cnkirito.consistenthash.bo.Server; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.TreeMap; + +import static org.mockito.Mockito.*; + +/** + * @author dailj + * @date 2022/11/26 14:51 + */ +public class ConsistentHashRingTest { + @Mock + AbstractHashStrategy hashStrategy; + ConsistentHashRing consistentHashRing; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testGetInstance() throws Exception { + /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + + ConsistentHashRing result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); + Assert.assertNotEquals(null, result); + } + + @Test + public void testLocate() throws Exception { + /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + + consistentHashRing = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); + Server result = consistentHashRing.locate(Integer.valueOf(0)); + Assert.assertNotNull(result.getUrl()); + } + + @Test + public void testBuildConsistentHashRing() throws Exception { + /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + + consistentHashRing = new ConsistentHashRing(); + TreeMap result = consistentHashRing.buildConsistentHashRing( + Arrays.asList(new Server("url"))); + Assert.assertEquals(10, result.entrySet().size()); + } +} From abe9171cf1ba4cd0eb19f6364c9a2d0612b49f2a Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Sun, 27 Nov 2022 18:35:49 +0800 Subject: [PATCH 3/6] =?UTF-8?q?1.Update=20=E6=B7=BB=E5=8A=A0=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=9E=84=E9=80=A0=E6=96=B9=E6=B3=95=E5=92=8C=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=96=B9=E6=B3=95=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 27 +++++----- .../load/balancer/ConsistentHashRing.java | 9 ++-- .../load/balancer/ConsistentHashRingTest.java | 50 +++++++++++++------ 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/pom.xml b/pom.xml index 4a918c0..212ef91 100644 --- a/pom.xml +++ b/pom.xml @@ -16,27 +16,21 @@ 4.12 - net.bytebuddy - byte-buddy - 1.8.15 + org.powermock + powermock-core + ${powermock.version} test - net.bytebuddy - byte-buddy-agent - 1.8.15 + org.powermock + powermock-api-mockito2 + ${powermock.version} test - org.objenesis - objenesis - 2.6 - test - - - org.mockito - mockito-core - 2.21.0 + org.powermock + powermock-module-junit4 + ${powermock.version} test @@ -44,6 +38,9 @@ moe.cnkirito.sample consistent-hash 1.0-SNAPSHOT + + 2.0.9 + diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java index 3abe8f2..44e9320 100644 --- a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java @@ -45,20 +45,17 @@ public Server locate(Integer invocationHashCode) { return locateEntry.getValue(); } - @VisibleForTesting - protected ConsistentHashRing(List servers) { + private ConsistentHashRing(List servers) { init(servers); } - @VisibleForTesting - protected ConsistentHashRing() {} + private ConsistentHashRing() {} private void init(List servers) { virtualNodeRing = buildConsistentHashRing(servers); } - @VisibleForTesting - protected TreeMap buildConsistentHashRing(List servers) { + private TreeMap buildConsistentHashRing(List servers) { TreeMap virtualNodeRing = new TreeMap<>(); for (Server server : servers) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java index ab3b331..4ad65fe 100644 --- a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java @@ -8,6 +8,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.reflect.Whitebox; import java.util.Arrays; import java.util.HashMap; @@ -17,9 +20,11 @@ /** * @author dailj - * @date 2022/11/26 14:51 + * @date 2022/11/27 17:42 */ public class ConsistentHashRingTest { + @Mock + ConsistentHashRing instance; @Mock AbstractHashStrategy hashStrategy; ConsistentHashRing consistentHashRing; @@ -30,29 +35,42 @@ public void setUp() { } @Test - public void testGetInstance() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用,不会被单元测试覆盖率统计 + // @PrepareForTest({ConsistentHashRing.class}) + public void testGetInstance() { + when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用 + /** + * PowerMockito.doReturn(new TreeMap(new HashMap() {{ + * put(Integer.valueOf(0), new Server("url"));}})) + * .when(ConsistentHashRing.class, "buildConsistentHashRing", Arrays.asList(new Server("url")));' + */ ConsistentHashRing result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Assert.assertNotEquals(null, result); + Assert.assertSame(ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))), result); + /// 验证方法被调用过一次 + /** + * Mockito.verify(hashStrategy).getHashCode("url"); + * Mockito.verifyNoMoreInteractions(hashStrategy); + */ } @Test - public void testLocate() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + public void testBuildConsistentHashRing() throws Exception { + // when(hashStrategy.getHashCode(anyString())).thenReturn(0); - consistentHashRing = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Server result = consistentHashRing.locate(Integer.valueOf(0)); - Assert.assertNotNull(result.getUrl()); + // 调用无权限的构造方法 + consistentHashRing = (ConsistentHashRing)Whitebox.invokeConstructor(ConsistentHashRing.class); + // 调用无权限的方法 + TreeMap result = (TreeMap)Whitebox.invokeMethod(consistentHashRing, + "buildConsistentHashRing", Arrays.asList(new Server("url"))); + Assert.assertEquals(10, result.entrySet().size()); } @Test - public void testBuildConsistentHashRing() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); - - consistentHashRing = new ConsistentHashRing(); - TreeMap result = consistentHashRing.buildConsistentHashRing( - Arrays.asList(new Server("url"))); - Assert.assertEquals(10, result.entrySet().size()); + public void testLocate() { + Server result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))) + .locate(Integer.valueOf(0)); + Assert.assertNotNull(result); } } From b03931b8ebc946ee8ed07dedafbcee19ba141eca Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Sun, 27 Nov 2022 18:35:49 +0800 Subject: [PATCH 4/6] =?UTF-8?q?1.Update=20=E6=B7=BB=E5=8A=A0=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=9E=84=E9=80=A0=E6=96=B9=E6=B3=95=E5=92=8C=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=96=B9=E6=B3=95=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 27 +++++----- .../load/balancer/ConsistentHashRing.java | 9 ++-- .../load/balancer/ConsistentHashRingTest.java | 54 +++++++++++++------ 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/pom.xml b/pom.xml index 4a918c0..212ef91 100644 --- a/pom.xml +++ b/pom.xml @@ -16,27 +16,21 @@ 4.12 - net.bytebuddy - byte-buddy - 1.8.15 + org.powermock + powermock-core + ${powermock.version} test - net.bytebuddy - byte-buddy-agent - 1.8.15 + org.powermock + powermock-api-mockito2 + ${powermock.version} test - org.objenesis - objenesis - 2.6 - test - - - org.mockito - mockito-core - 2.21.0 + org.powermock + powermock-module-junit4 + ${powermock.version} test @@ -44,6 +38,9 @@ moe.cnkirito.sample consistent-hash 1.0-SNAPSHOT + + 2.0.9 + diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java index 3abe8f2..44e9320 100644 --- a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java @@ -45,20 +45,17 @@ public Server locate(Integer invocationHashCode) { return locateEntry.getValue(); } - @VisibleForTesting - protected ConsistentHashRing(List servers) { + private ConsistentHashRing(List servers) { init(servers); } - @VisibleForTesting - protected ConsistentHashRing() {} + private ConsistentHashRing() {} private void init(List servers) { virtualNodeRing = buildConsistentHashRing(servers); } - @VisibleForTesting - protected TreeMap buildConsistentHashRing(List servers) { + private TreeMap buildConsistentHashRing(List servers) { TreeMap virtualNodeRing = new TreeMap<>(); for (Server server : servers) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java index ab3b331..61900c2 100644 --- a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java @@ -5,9 +5,14 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; import java.util.Arrays; import java.util.HashMap; @@ -17,9 +22,13 @@ /** * @author dailj - * @date 2022/11/26 14:51 + * @date 2022/11/27 17:42 */ +// 配合@Mock注解使用 +@RunWith(PowerMockRunner.class) public class ConsistentHashRingTest { + @Mock + ConsistentHashRing instance; @Mock AbstractHashStrategy hashStrategy; ConsistentHashRing consistentHashRing; @@ -30,29 +39,42 @@ public void setUp() { } @Test - public void testGetInstance() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用,不会被单元测试覆盖率统计 + // @PrepareForTest({ConsistentHashRing.class}) + public void testGetInstance() { + when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用 + /** + * PowerMockito.doReturn(new TreeMap(new HashMap() {{ + * put(Integer.valueOf(0), new Server("url"));}})) + * .when(ConsistentHashRing.class, "buildConsistentHashRing", Arrays.asList(new Server("url")));' + */ ConsistentHashRing result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Assert.assertNotEquals(null, result); + Assert.assertSame(ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))), result); + /// 验证方法被调用过一次 + /** + * Mockito.verify(hashStrategy).getHashCode("url"); + * Mockito.verifyNoMoreInteractions(hashStrategy); + */ } @Test - public void testLocate() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + public void testBuildConsistentHashRing() throws Exception { + // when(hashStrategy.getHashCode(anyString())).thenReturn(0); - consistentHashRing = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Server result = consistentHashRing.locate(Integer.valueOf(0)); - Assert.assertNotNull(result.getUrl()); + // 调用无权限的构造方法 + consistentHashRing = (ConsistentHashRing)Whitebox.invokeConstructor(ConsistentHashRing.class); + // 调用无权限的方法 + TreeMap result = (TreeMap)Whitebox.invokeMethod(consistentHashRing, + "buildConsistentHashRing", Arrays.asList(new Server("url"))); + Assert.assertEquals(10, result.entrySet().size()); } @Test - public void testBuildConsistentHashRing() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); - - consistentHashRing = new ConsistentHashRing(); - TreeMap result = consistentHashRing.buildConsistentHashRing( - Arrays.asList(new Server("url"))); - Assert.assertEquals(10, result.entrySet().size()); + public void testLocate() { + Server result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))) + .locate(Integer.valueOf(0)); + Assert.assertNotNull(result); } } From 29d1891aabebd05faa2ba8fefb5a83a09ce02549 Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Sun, 27 Nov 2022 18:35:49 +0800 Subject: [PATCH 5/6] =?UTF-8?q?1.Update=20=E6=B7=BB=E5=8A=A0=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=9E=84=E9=80=A0=E6=96=B9=E6=B3=95=E5=92=8C=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=96=B9=E6=B3=95=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 27 ++++----- .../load/balancer/ConsistentHashRing.java | 9 +-- .../load/balancer/ConsistentHashRingTest.java | 57 +++++++++++++------ .../load/balancer/MockPrivateClass.java | 18 ++++++ .../load/balancer/MockPrivateClassTest.java | 33 +++++++++++ 5 files changed, 107 insertions(+), 37 deletions(-) create mode 100644 src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java create mode 100644 src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java diff --git a/pom.xml b/pom.xml index 4a918c0..212ef91 100644 --- a/pom.xml +++ b/pom.xml @@ -16,27 +16,21 @@ 4.12 - net.bytebuddy - byte-buddy - 1.8.15 + org.powermock + powermock-core + ${powermock.version} test - net.bytebuddy - byte-buddy-agent - 1.8.15 + org.powermock + powermock-api-mockito2 + ${powermock.version} test - org.objenesis - objenesis - 2.6 - test - - - org.mockito - mockito-core - 2.21.0 + org.powermock + powermock-module-junit4 + ${powermock.version} test @@ -44,6 +38,9 @@ moe.cnkirito.sample consistent-hash 1.0-SNAPSHOT + + 2.0.9 + diff --git a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java index 3abe8f2..44e9320 100644 --- a/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java +++ b/src/main/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRing.java @@ -45,20 +45,17 @@ public Server locate(Integer invocationHashCode) { return locateEntry.getValue(); } - @VisibleForTesting - protected ConsistentHashRing(List servers) { + private ConsistentHashRing(List servers) { init(servers); } - @VisibleForTesting - protected ConsistentHashRing() {} + private ConsistentHashRing() {} private void init(List servers) { virtualNodeRing = buildConsistentHashRing(servers); } - @VisibleForTesting - protected TreeMap buildConsistentHashRing(List servers) { + private TreeMap buildConsistentHashRing(List servers) { TreeMap virtualNodeRing = new TreeMap<>(); for (Server server : servers) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java index ab3b331..c71549b 100644 --- a/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java @@ -5,9 +5,14 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; import java.util.Arrays; import java.util.HashMap; @@ -17,9 +22,13 @@ /** * @author dailj - * @date 2022/11/26 14:51 + * @date 2022/11/27 17:42 */ +// 配合@Mock注解使用 +@RunWith(PowerMockRunner.class) public class ConsistentHashRingTest { + @Mock + ConsistentHashRing instance; @Mock AbstractHashStrategy hashStrategy; ConsistentHashRing consistentHashRing; @@ -30,29 +39,45 @@ public void setUp() { } @Test - public void testGetInstance() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用,不会被单元测试覆盖率统计 + // @PrepareForTest({ConsistentHashRing.class}) + public void testGetInstance() { + when(hashStrategy.getHashCode(anyString())).thenReturn(0); + /// 模拟私有方法调用 + /** + * @Spy + * private ConsistentHashRing consistentHashRing = Mockito.spy(ConsistentHashRing.class); + * + * PowerMockito.doReturn(new TreeMap(new HashMap() {{ + * put(Integer.valueOf(0), new Server("url"));}})) + * .when(consistentHashRing, "buildConsistentHashRing", Arrays.asList(new Server("url"))); + */ ConsistentHashRing result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Assert.assertNotEquals(null, result); + Assert.assertSame(ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))), result); + /// 验证方法被调用过一次 + /** + * Mockito.verify(hashStrategy).getHashCode("url"); + * Mockito.verifyNoMoreInteractions(hashStrategy); + */ } @Test - public void testLocate() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); + public void testBuildConsistentHashRing() throws Exception { + // when(hashStrategy.getHashCode(anyString())).thenReturn(0); - consistentHashRing = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))); - Server result = consistentHashRing.locate(Integer.valueOf(0)); - Assert.assertNotNull(result.getUrl()); + // 调用无权限的构造方法 + consistentHashRing = (ConsistentHashRing)Whitebox.invokeConstructor(ConsistentHashRing.class); + // 调用无权限的方法 + TreeMap result = (TreeMap)Whitebox.invokeMethod(consistentHashRing, + "buildConsistentHashRing", Arrays.asList(new Server("url"))); + Assert.assertEquals(10, result.entrySet().size()); } @Test - public void testBuildConsistentHashRing() throws Exception { - /// when(hashStrategy.getHashCode(anyString())).thenReturn(0); - - consistentHashRing = new ConsistentHashRing(); - TreeMap result = consistentHashRing.buildConsistentHashRing( - Arrays.asList(new Server("url"))); - Assert.assertEquals(10, result.entrySet().size()); + public void testLocate() { + Server result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))) + .locate(Integer.valueOf(0)); + Assert.assertNotNull(result); } } diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java new file mode 100644 index 0000000..3966747 --- /dev/null +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java @@ -0,0 +1,18 @@ +package moe.cnkirito.consistenthash.load.balancer; + +/** + * @author dailj + * @date 2022/11/28 22:09 + */ +public class MockPrivateClass { + + private MockPrivateClass() {} + + public String mockPrivateFunc() { + return privateFunc(); + } + + private String privateFunc() { + return "private func"; + } +} diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java new file mode 100644 index 0000000..9b2ba6a --- /dev/null +++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java @@ -0,0 +1,33 @@ +package moe.cnkirito.consistenthash.load.balancer; + +import moe.cnkirito.consistenthash.LoadBalanceable; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * @author dailj + * @date 2022/11/28 22:09 + */ +@RunWith(PowerMockRunner.class) +public class MockPrivateClassTest { + + @Spy + private MockPrivateClass mockPrivateClass = Mockito.spy(MockPrivateClass.class); + + @Test + @PrepareForTest({MockPrivateClass.class}) + public void testMockPrivateFunc() throws Exception { + // mockPrivateClass = Mockito.mock(MockPrivateClass.class); + PowerMockito.when(mockPrivateClass, "privateFunc").thenReturn("test"); + String realResult = mockPrivateClass.mockPrivateFunc(); + Assert.assertEquals("test", realResult); + PowerMockito.verifyPrivate(mockPrivateClass, Mockito.times(1)).invoke("privateFunc"); + } +} From a6b25e7de7dfa55c767b334dc371498f69938983 Mon Sep 17 00:00:00 2001 From: dailianjie <371660929@qq.com> Date: Mon, 28 Nov 2022 23:30:21 +0800 Subject: [PATCH 6/6] =?UTF-8?q?1.Update=20Merge=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../load/balancer/MockPrivateClass.java | 18 ---------- .../load/balancer/MockPrivateClassTest.java | 33 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java delete mode 100644 src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java deleted file mode 100644 index 3966747..0000000 --- a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClass.java +++ /dev/null @@ -1,18 +0,0 @@ -package moe.cnkirito.consistenthash.load.balancer; - -/** - * @author dailj - * @date 2022/11/28 22:09 - */ -public class MockPrivateClass { - - private MockPrivateClass() {} - - public String mockPrivateFunc() { - return privateFunc(); - } - - private String privateFunc() { - return "private func"; - } -} diff --git a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java b/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java deleted file mode 100644 index 9b2ba6a..0000000 --- a/src/test/java/moe/cnkirito/consistenthash/load/balancer/MockPrivateClassTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package moe.cnkirito.consistenthash.load.balancer; - -import moe.cnkirito.consistenthash.LoadBalanceable; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -/** - * @author dailj - * @date 2022/11/28 22:09 - */ -@RunWith(PowerMockRunner.class) -public class MockPrivateClassTest { - - @Spy - private MockPrivateClass mockPrivateClass = Mockito.spy(MockPrivateClass.class); - - @Test - @PrepareForTest({MockPrivateClass.class}) - public void testMockPrivateFunc() throws Exception { - // mockPrivateClass = Mockito.mock(MockPrivateClass.class); - PowerMockito.when(mockPrivateClass, "privateFunc").thenReturn("test"); - String realResult = mockPrivateClass.mockPrivateFunc(); - Assert.assertEquals("test", realResult); - PowerMockito.verifyPrivate(mockPrivateClass, Mockito.times(1)).invoke("privateFunc"); - } -}