Memcache-Java-Client-Release源码阅读(之五)

一、主要内容
本章节的主要内容是介绍Memcache Client的失效转移机制,恢复机制和Sock状态检测机制的实现原理。

二、准备工作
1、服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例。
2、示例代码:

String[] servers = { "192.168.0.106:11211", "192.168.0.106:11212" };
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setInitConn(10);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setSocketTO(3000);
// 开启以下特性
pool.setFailover(true);
pool.setFailback(true);
pool.setAliveCheck(true);
pool.setNagle(false);
pool.initialize();
// 省略......

三、Failover 失效转移机制
1、简介
通俗地说,即当A无法为客户服务时,系统能够自动地切换,使B能够及时顶上继续为客户提供服务,且客户感觉不到,这个为他提供服务的对象已经替换了。
2、场景
要求必须有一个以上的服务端节点,并且SockIOPool设置开启失效转移机制,当其中一个节点因为某些原因不能再提供缓存服务时(比如网络故障,服务端实例宕机等),该机制会搜索其他的可用服务节点。

3、源码实现
在SchoonerSockIOPool类中的getSock()方法里面,相关源码如下:

// from here on, we are working w/ multiple servers
// keep trying different servers until we find one
// making sure we only try each server one time
Set<String> tryServers = new HashSet<String>(Arrays.asList(servers));
// get initial bucket
long bucket = getBucket(key, hashCode);
String server = (this.hashingAlg == CONSISTENT_HASH) ? consistentBuckets
        .get(bucket) : buckets.get((int) bucket);
while (!tryServers.isEmpty()) {
    // try to get socket from bucket
    SchoonerSockIO sock = getConnection(server);
    if (sock != null)
        return sock;

    // if we do not want to failover, then bail here
    if (!failover)
        return null;
    // log that we tried
    tryServers.remove(server);
    if (tryServers.isEmpty())
        break;
    // if we failed to get a socket from this server
    // then we try again by adding an incrementer to the
    // current key and then rehashing
    int rehashTries = 0;
    while (!tryServers.contains(server)) {
        String newKey = new StringBuffer().append(rehashTries)
                .append(key).toString();
        // String.format( "%s%s", rehashTries, key );
        bucket = getBucket(newKey, null);
        server = (this.hashingAlg == CONSISTENT_HASH) ? consistentBuckets
                .get(bucket) : buckets.get((int) bucket);
        rehashTries++;
    }
}

4、实现思路
1)根据原有key取server的Sock;
2)若为空,key前缀初始为0,再取一次hash,重新定位server;
3)若仍为空,前缀变为1(递增1),重复第2步,直到取到能正常获取Sock的server;
原理:利用key值前缀递增,hash映射会逐渐变化到不同的server,直到发现可用server为止,以此来实现从失效的服务端转移到可用的服务端。

5、问题思考
失效转移机制实现过程中,缓存对象的key值其实是有变化的,由key变为newkey,我们考虑这样的场景:有两个服务端实例11211和11212,客户端启用了失效转移机制,假设某个缓存对象映射到11211节点上,11211实例突然宕机了,然后对该缓存对象进行set操作,过一段时间后,进行get操作,但此时11211节点已经重启了,问:还能正常获取到缓存对象吗?
这个小实验各位可以自行测验一下,本人以测验的结果是缓存已经无法取到。原因是失效转移机制下,该缓存是以newkey存储在11212节点上,但get操作时,宕机的服务端节点已经恢复,又会以key值在11211节点上进行get操作,所以是取不到的。
所以这个地方要引起注意:启用失效转移机制后,宕机后的服务端节点重启之后,会导致部分缓存无法再正常访问的,正式生产环境的memcache服务节点,一般是会配置自动监听重启的(比如使用Monit组件进行监听)。

四、Failback 恢复机制
1、简介
在簇网络系统(有两台或多台服务器互联的网络)中,由于要某台服务器进行维修,需要网络资源和服务暂时重定向到备用系统,在此之后将网络资源和服务器恢复为由原始主机提供的过程,称为自动恢复。
2、场景
服务端节点因为某些原因不能再提供缓存服务时(比如网络故障,服务端实例宕机等),若SockIOPool设置开启故障自动恢复机制,每次都会尝试连接服务端;若设置自动恢复机制为关闭,连接失败的服务端节点,会记录到服务端死信队列里,当到达一定的失效时间后,就不再进行尝试连接操作,相当于客户端已经完全放弃该服务端。
3、源码实现
在SchoonerSockIOPool类中的getConnection()方法里面,相关源码如下:


if (!failback && hostDead.containsKey(host)
        && hostDeadDur.containsKey(host)) {

    Date store = hostDead.get(host);
    long expire = hostDeadDur.get(host).longValue();

    if ((store.getTime() + expire) > System.currentTimeMillis())
        return null;
}

// 省略一些其他特性的代码

if (socket == null) {
    Date now = new Date();
    hostDead.put(host, now);

    long expire = (hostDeadDur.containsKey(host)) ? (((Long) hostDeadDur
            .get(host)).longValue() * 2) : 1000;

    if (expire > MAX_RETRY_DELAY)
        expire = MAX_RETRY_DELAY;

    hostDeadDur.put(host, new Long(expire));

    // also clear all entries for this host from availPool
    sockets.clear();
}

4、实现思路
咱们先看后一部分代码(中文注释语句后面的代码),如代码所示,当客户端连接服务端时的socket对象为空时,表示创建连接失败,会将这个服务器连接信息记录到hostDead死信队列里,并设置相应的过期时间,最长为10分钟,然后返回空,告知客户端创建连接失败。
接着看前面部分的代码(中文注释语句前面的代码),当客户端设置failback为true时,这部分代码不会执行,意味着每次都会去尝试连接服务器;当客户端设置failback为false,并且死信队列里有相应服务器信息的记录时,若超过失效时间,将永远返回null值,就意味着该服务器,客户端将不再尝试重新连接。

五、AliveCheck Sock状态检测机制
1、简介
顾名思义,就是每次在使用Sock对象前,会检测一下该Sock能否正常使用。
2、场景
SockIOPool设置开启状态检测机制,当服务端Sock能创建,但连接失效,就会触发连接性校验操作。这个机制默认是关闭的
3、源码实现
在SchoonerSockIOPool类中的getConnection()方法里面,相关源码如下:

if (socket != null && aliveCheck && !socket.isAlive()) {
    socket.close();
    try {
        socket.sockets.invalidateObject(socket);
    } catch (Exception e1) {
        log.error("++++ failed to close socket : " + socket.toString());
    }
    socket = null;
}

socket.isAlive()方法源码在SockIOPool类中,源码如下:
/* * checks to see that the connection is still working * * @return true if still alive */
public boolean isAlive() {

     if (!isConnected())
            return false;

     // try to talk to the server w/ a dumb query to ask its version
     try {
            this.write( "version\r\n".getBytes());
            this.flush();
            this.readLine();
     } catch (IOException ex) {
            return false;
     }

     return true;
}

4、实现思路
isAlive()方法实现原理很简单,就是向指定的服务器发送一条version命令,看能否正常使用。
涉及aliveCheck属性值的这部分代码,其实是调用Commons-pool框架GenericObjectPool类的invalidateObject方法,注意一下if的条件,isAlive为false才进入调用invalidateObject方法的,所以这个方法的大概思路是先销毁这个连接对象,然后再调用allocate()方法尝试重新初始化一下连接资源,这里就不再深入讨论了,有兴趣的童鞋可以研究一下Commons-pool框架。

六、FAQ
Q1:这三个特性相互之间有联系吗?
A1:有联系,首先恢复机制如果返回值为空的话,失效转移机制就会重新搜索新的服务器,然后继续执行恢复机制这部分代码,直至找到可以正常使用的服务端,而Sock状态检测机制是夹杂在恢复机制中间的,决定在恢复机制中是否校验Sock的连接性。

你可能感兴趣的:(源码,memcache,javaclient)