一致性哈希算法介绍:一致性哈希算法
如果我们想使用一致性哈希算法,只需要添加pool.setHashingAlg(SockIOPool.CONSISTENT_HASH);这行代码即可
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
public class Test {
public static void main(String[] args) {
MemCachedClient client = new MemCachedClient();
String[] servers = {"192.168.52.129:9999",
"192.168.52.131:9999"};
Integer[] weights = {1, 1};
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setWeights(weights);
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
pool.setMaintSleep(30);
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
pool.setHashingAlg(SockIOPool.CONSISTENT_HASH); // 3
pool.initialize();
client.set("test", "This is a test String");
String test = (String) client.get("test");
System.out.println(test);
}
}
来看下实际效果
sean@ubuntu1:~$ telnet 192.168.52.131 9999
Trying 192.168.52.131...
Connected to 192.168.52.131.
Escape character is '^]'.
get test
END
sean1@ubuntu2:~$ telnet 192.168.52.129 9999
Trying 192.168.52.129...
Connected to 192.168.52.129.
Escape character is '^]'.
get test
VALUE test 32 21
This is a test String
END
MemcachedClient的初始化方法,通过该方法可确定Client的具体实现类为AscIIClient
public MemCachedClient() {
this(null, true, false);
}
public MemCachedClient(String var1, boolean var2, boolean var3) {
this.BLAND_DATA_SIZE = " ".getBytes();
if(var3) {
this.client = new BinaryClient(var1);
} else {
this.client = (MemCachedClient)(var2?
new AscIIClient(var1):new AscIIUDPClient(var1));
}
}
默认使用的连接池名称是default
public class AscIIClient extends MemCachedClient {
// var1 = null
public AscIIClient(String var1) {
super((MemCachedClient)null);
this.transCoder = new ObjectTransCoder();
this.poolName = var1;
this.init();
}
private void init() {
this.sanitizeKeys = true;
this.primitiveAsString = false;
this.compressEnable = false;
this.compressThreshold = 30720L;
this.defaultEncoding = "UTF-8";
this.poolName = this.poolName == null?"default":this.poolName;
this.pool = SchoonerSockIOPool.getInstance(this.poolName);
}
......
}
这里完成了连接池对象的创建(从这里可以看到一个client中可以保存多个连接池)
public class SchoonerSockIOPool {
// ConcurrentMap<连接池名称,连接池对象>
private static ConcurrentMap<String, SchoonerSockIOPool> pools =
new ConcurrentHashMap();
// var0 = "default" 服务器名称
public static SchoonerSockIOPool getInstance(String var0) {
ConcurrentMap var2 = pools;
synchronized(pools) {
if(!pools.containsKey(var0)) {
SchoonerSockIOPool var1 = new SchoonerSockIOPool(true);
pools.putIfAbsent(var0, var1);
}
}
return (SchoonerSockIOPool)pools.get(var0);
}
......
}
看完了MemcachedClient再来看看SockIOPool
这里直接取出了之前创建的、名字为default的连接池对象并进行该对象的初始化
public class SockIOPool {
public static SockIOPool getInstance() {
SockIOPool var0 = new SockIOPool();
var0.schoonerSockIOPool = SchoonerSockIOPool.getInstance("default");
return var0;
}
public void initialize() {
this.schoonerSockIOPool.initialize();
}
......
}
构建一致性哈希算法中的整个圆环(consistentBuckets),当然从具体实现上来看只是构建虚拟节点的集合
public class SchoonerSockIOPool {
// Map<"192.168.52.129:9999", 连接池>
Map<String, GenericObjectPool> socketPool;
// 一致性哈希环 TreeMap<hashkey, "192.168.52.129:9999">
private TreeMap<Long, String> consistentBuckets;
......
public void initialize() {
......
if (this.hashingAlg == 3)
populateConsistentBuckets();
else
populateBuckets();
......
}
private void populateConsistentBuckets(){
this.consistentBuckets = new TreeMap();
MessageDigest localMessageDigest = (MessageDigest)MD5.get();
// 获得总权重
// 如果指定了每个服务器的权重,则其和值为总权重
// 否则每个服务器权重为1,总权重为服务器个数
if ((this.totalWeight.intValue() <= 0) && (this.weights != null))
for (i = 0; i < this.weights.length; ++i){
SchoonerSockIOPool localSchoonerSockIOPool = this;
(localSchoonerSockIOPool.totalWeight = Integer.valueOf(
localSchoonerSockIOPool.totalWeight.intValue() +
((this.weights[i] == null) ? 1 : this.weights[i].intValue())));
}
else if (this.weights == null)
this.totalWeight = Integer.valueOf(this.servers.length);
// 循环遍历每一个服务器以便创建其虚拟节点
for (int i = 0; i < this.servers.length; ++i){
int j = 1;
if ((this.weights != null) && (this.weights[i] != null))
j = this.weights[i].intValue();
// 每个服务器的虚拟节点个数需参照该服务器的权重
double d = Math.floor(40 * this.servers.length * j /
this.totalWeight.intValue());
long l = 0L;
// 循环构建每一个节点
while (l < d){
byte[] arrayOfByte = localMessageDigest.digest(
this.servers[i] + "-" + l.getBytes());
for (int k = 0; k < 4; ++k){
Long localLong = Long.valueOf((arrayOfByte[(3 + k * 4)] & 0xFF) << 24
| (arrayOfByte[(2 + k * 4)] & 0xFF) << 16
| (arrayOfByte[(1 + k * 4)] & 0xFF) << 8
| arrayOfByte[(0 + k * 4)] & 0xFF);
// 将每个虚拟节点添加到圆环中
this.consistentBuckets.put(localLong, this.servers[i]);
}
l += 1L;
}
Object localObject;
// 构建socket工厂类
if (this.authInfo != null)
localObject = new AuthSchoonerSockIOFactory(
this.servers[i], this.isTcp, this.bufferSize,
this.socketTO, this.socketConnectTO, this.nagle, this.authInfo);
else
localObject = new SchoonerSockIOFactory(
this.servers[i], this.isTcp, this.bufferSize,
this.socketTO, this.socketConnectTO, this.nagle);
// 使用socket工厂类创建连接池
GenericObjectPool localGenericObjectPool = new GenericObjectPool(
(PoolableObjectFactory)localObject,
this.maxConn, 1, this.maxIdle, this.maxConn);
((SchoonerSockIOFactory)localObject).setSockets(localGenericObjectPool);
// 每个服务器都有自己的连接池
this.socketPool.put(this.servers[i], localGenericObjectPool);
}
}
}
set操作的实现方式如下
public class AscIIClient extends MemCachedClient {
private SchoonerSockIOPool pool;
// var1 = "test" var2 = "This is a test String"
public boolean set(String var1, Object var2) {
return this.set("set", var1, var2, (Date)null, (Integer)null,
Long.valueOf(0L), this.primitiveAsString);
}
private boolean set(String var1, String var2, Object var3, Date var4,
Integer var5, Long var6, boolean var7) {
......
SchoonerSockIO var8 = this.pool.getSock(var2, var5);
......
}
......
}
服务器的查找过程如下
// paramString = "test" paramInteger = 0
public final SchoonerSockIO getSock(String paramString, Integer paramInteger) {
......
// 计算Key的哈希值,并根据该哈希值得到对应的服务器节点哈希值
long l = getBucket(paramString, paramInteger);
// 根据服务器节点哈希值得到对应的服务器
String str1 = (this.hashingAlg == 3) ? (String) this.consistentBuckets
.get(Long.valueOf(l)) : (String) this.buckets.get((int) l);
while (!(((Set) localObject).isEmpty())) {
// 从服务器连接池中获取到特定服务器的连接
SchoonerSockIO localSchoonerSockIO = getConnection(str1);
......
}
public final SchoonerSockIO getConnection(String var1) {
......
GenericObjectPool var8 = (GenericObjectPool)this.socketPool.get(var1);
......
}
首选根据Key值计算出其哈希值(getHash),然后根据得到的哈希值确定其在圆环上对应的服务器节点(findPointFor)
// paramString = "test" paramInteger = 0
private final long getBucket(String paramString, Integer paramInteger) {
long l1 = getHash(paramString, paramInteger);
if (this.hashingAlg == 3)
return findPointFor(Long.valueOf(l1)).longValue();
long l2 = l1 % this.buckets.size();
if (l2 < 0L)
l2 *= -1L;
return l2;
}
Key的哈希值计算过程如下,和populateConsistentBuckets方法中用来生成服务器虚拟节点哈希值的算法是一样的
// paramString = "test" paramInteger = 0
private final long getHash(String paramString, Integer paramInteger) {
if (paramInteger != null) {
if (this.hashingAlg == 3)
return (paramInteger.longValue() & 0xFFFFFFFF);
return paramInteger.longValue();
}
switch (this.hashingAlg) {
case 0:
return paramString.hashCode();
case 1:
return origCompatHashingAlg(paramString);
case 2:
return newCompatHashingAlg(paramString);
case 3:
return md5HashingAlg(paramString);
}
this.hashingAlg = 0;
return paramString.hashCode();
}
private static long md5HashingAlg(String paramString) {
MessageDigest localMessageDigest = (MessageDigest) MD5.get();
localMessageDigest.reset();
localMessageDigest.update(paramString.getBytes());
byte[] arrayOfByte = localMessageDigest.digest();
long l = (arrayOfByte[3] & 0xFF) << 24 | (arrayOfByte[2] & 0xFF) << 16
| (arrayOfByte[1] & 0xFF) << 8 | arrayOfByte[0] & 0xFF;
return l;
}
在圆环上(consistentBuckets)查找Key的哈希值对应的服务器节点哈希值
参照populateConsistentBuckets中的代码,所有虚拟节点被存放在一个TreeMap中,所以这里可以使用tailMap方法获得大于等于Key哈希值的子树,然后获取该树中最小值即可
// paramLong是查询key经过计算得到的哈希值
private final Long findPointFor(Long paramLong) {
SortedMap localSortedMap = this.consistentBuckets.tailMap(paramLong);
return ((localSortedMap.isEmpty()) ? (Long) this.consistentBuckets
.firstKey() : (Long) localSortedMap.firstKey());
}