Memcached-java-client一致性哈希实现

本文介绍了如何在Memcached-java-client中实现一致性哈希。通过设置pool.setHashingAlg(SockIOPool.CONSISTENT_HASH)启用此算法。详细阐述了MemcachedClient和SockIOPool的初始化过程,以及一致性哈希的查找和服务器选择机制,包括哈希值计算和在虚拟节点圆环上的定位方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一致性哈希算法介绍:一致性哈希算法

如果我们想使用一致性哈希算法,只需要添加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());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值