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"
};
}
+
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..81a1a94
--- /dev/null
+++ b/src/test/java/moe/cnkirito/consistenthash/load/balancer/ConsistentHashRingTest.java
@@ -0,0 +1,83 @@
+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.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;
+import java.util.TreeMap;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * @author dailj
+ * @date 2022/11/26 14:51
+ */
+// 配合@Mock注解使用
+@RunWith(PowerMockRunner.class)
+public class ConsistentHashRingTest {
+ @Mock
+ ConsistentHashRing instance;
+ @Mock
+ AbstractHashStrategy hashStrategy;
+ ConsistentHashRing consistentHashRing;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ /// 模拟私有方法调用,不会被单元测试覆盖率统计
+ // @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.assertSame(ConsistentHashRing.getInstance(Arrays.asList(new Server("url"))), result);
+ /// 验证方法被调用过一次
+ /**
+ * Mockito.verify(hashStrategy).getHashCode("url");
+ * Mockito.verifyNoMoreInteractions(hashStrategy);
+ */
+ }
+
+ @Test
+ public void testBuildConsistentHashRing() throws Exception {
+ // when(hashStrategy.getHashCode(anyString())).thenReturn(0);
+
+ // 调用无权限的构造方法
+ 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 testLocate() {
+ Server result = ConsistentHashRing.getInstance(Arrays.asList(new Server("url")))
+ .locate(Integer.valueOf(0));
+ Assert.assertNotNull(result);
+ }
+}