架构师训练营 - 作业 5
发布于: 2020 年 07 月 08 日
作业:用你熟悉的语言实现一致性 hash 算法,编写测试用例测试此算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算服务器分布数量的标准差,以评估算法的存储负载的不均衡性
/**
* Create By lhu on 7/7/2020
*/
public class ConsistentHashTest {
// 物理节点
private Set<String> physicalNodes = new TreeSet<String>() {};
//虚拟节点
private int VIRTUAL_COPIES = 150; // 物理节点至虚拟节点的复制倍数
private TreeMap<Long, String> virtualNodes = new TreeMap<>();
/**
* hash算法,输出64位的值
*/
public long hash(final byte[] data, int length, int seed) {
final long m = 0xc6a4a7935bd1e995L;
final int r = 47;
long h = (seed & 0xffffffffl) ^ (length * m);
int length8 = length / 8;
for (int i = 0; i < length8; i++) {
final int i8 = i * 8;
long k = ((long) data[i8 + 0] & 0xff)
+ (((long) data[i8 + 1] & 0xff) << 8)
+ (((long) data[i8 + 2] & 0xff) << 16)
+ (((long) data[i8 + 3] & 0xff) << 24)
+ (((long) data[i8 + 4] & 0xff) << 32)
+ (((long) data[i8 + 5] & 0xff) << 40)
+ (((long) data[i8 + 6] & 0xff) << 48)
+ (((long) data[i8 + 7] & 0xff) << 56);
k *= m;
k ^= k >>> r;
k *= m;
h ^= k;
h *= m;
}
switch (length % 8) {
case 7:
h ^= (long) (data[(length & ~7) + 6] & 0xff) << 48;
case 6:
h ^= (long) (data[(length & ~7) + 5] & 0xff) << 40;
case 5:
h ^= (long) (data[(length & ~7) + 4] & 0xff) << 32;
case 4:
h ^= (long) (data[(length & ~7) + 3] & 0xff) << 24;
case 3:
h ^= (long) (data[(length & ~7) + 2] & 0xff) << 16;
case 2:
h ^= (long) (data[(length & ~7) + 1] & 0xff) << 8;
case 1:
h ^= (long) (data[length & ~7] & 0xff);
h *= m;
}
h ^= h >>> r;
h *= m;
h ^= h >>> r;
return h;
}
public long hash(final byte[] data, int length) {
return hash(data, length, 0xe17a1465);
}
public long Hash(final String data) {
byte[] bytes = toBytesWithoutEncoding(data);
return hash(bytes, bytes.length);
}
private byte[] toBytesWithoutEncoding(String str) {
int len = str.length();
int pos = 0;
byte[] buf = new byte[len << 1];
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
buf[pos++] = (byte) (c & 0xFF);
buf[pos++] = (byte) (c >> 8);
}
return buf;
}
// 根据物理节点,构建虚拟节点映射表,使用默认的虚拟节点数量1
public ConsistentHashTest(Set<String> physicalNodes) {
this(physicalNodes,0);
}
// 根据物理节点,构建虚拟节点映射表
public ConsistentHashTest(Set<String> physicalNodes, int virtualCopyCount) {
for (String nodeIp : physicalNodes) {
addPhysicalNode(nodeIp);
}
if (virtualCopyCount>=1)
VIRTUAL_COPIES = virtualCopyCount;
}
// 添加物理节点
public void addPhysicalNode(String nodeIp) {
for (int idx = 0; idx < VIRTUAL_COPIES; ++idx) {
long hash = Hash(nodeIp + "#" + idx);
virtualNodes.put(hash, nodeIp);
}
physicalNodes.add(nodeIp);
}
// 删除物理节点
public void removePhysicalNode(String nodeIp) {
for (int idx = 0; idx < VIRTUAL_COPIES; ++idx) {
long hash = Hash(nodeIp + "#" + idx);
virtualNodes.remove(hash);
}
physicalNodes.remove(nodeIp);
}
// 查找对象映射的节点
public String getObjectNode(String object) {
long hash = Hash(object);
SortedMap<Long, String> tailMap = virtualNodes.tailMap(hash); // 所有大于 hash 的节点
Long key = tailMap.isEmpty() ? virtualNodes.firstKey() : tailMap.firstKey();
return virtualNodes.get(key);
}
// 统计对象与节点的映射关系
public void dumpObjectNodeMap(String label, int objectMin, int objectMax) {
// 统计
Map<String, Integer> objectNodeMap = new TreeMap<>(); // IP => COUNT
for (int object = objectMin; object <= objectMax; ++object) {
String nodeIp = getObjectNode(Integer.toString(object));
Integer count = objectNodeMap.get(nodeIp);
objectNodeMap.put(nodeIp, (count == null ? 1 : count + 1));
}
// 打印
double totalCount = objectMax - objectMin + 1;
System.out.println("======== " + label + " ========");
double[] counts4StandardDiviation = new double[objectNodeMap.entrySet().size()];
int i = 0;
for (Map.Entry<String, Integer> entry : objectNodeMap.entrySet()) {
int count = entry.getValue();
counts4StandardDiviation[i] = count;
System.out.println("IP=" + entry.getKey() + ": count=" + count + "");
i++;
}
System.out.println("Physical node count: "+physicalNodes.size()+", Virtual node count per physical node: "+VIRTUAL_COPIES+", standard deviation: "+StandardDeviation(counts4StandardDiviation));
}
//方差s^2=[(x1-x)^2 +...(xn-x)^2]/n 或者s^2=[(x1-x)^2 +...(xn-x)^2]/(n-1) 标准差σ=sqrt(s^2)
public static double StandardDeviation(double[] 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);
}
//reture Math.sqrt(dVar/(m-1));
return Math.sqrt(dVar/m);
}
public static void main(String[] args) {
Set<String> physicalNodes = new TreeSet<String>() {
{
add("192.168.1.101");
add("192.168.1.102");
add("192.168.1.103");
add("192.168.1.104");
add("192.168.1.105");
add("192.168.1.106");
add("192.168.1.107");
add("192.168.1.108");
add("192.168.1.109");
add("192.168.1.110");
}
};
ConsistentHashTest ch = new ConsistentHashTest(physicalNodes,180);
// 初始情况
ch.dumpObjectNodeMap("初始情况", 1, 1000000);
// 删除物理节点
ch.removePhysicalNode("192.168.1.103");
ch.dumpObjectNodeMap("删除物理节点", 1, 1000000);
// 添加物理节点
ch.addPhysicalNode("192.168.1.111");
ch.addPhysicalNode("192.168.1.112");
ch.dumpObjectNodeMap("添加物理节点", 1, 1000000);
}
}
复制代码
测试结果:
划线
评论
复制
发布于: 2020 年 07 月 08 日阅读数: 58
进击的炮灰
关注
还未添加个人签名 2020.05.13 加入
还未添加个人简介
评论