您的位置:首页 > 数据库 > Memcache

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

2016-04-19 21:07 549 查看
一、主要内容

本章节的主要内容是介绍Memcache 最基本的set/get操作过程。

二、准备工作

1、服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例。

2、示例代码:

MemCachedClient mcc = new MemCachedClient();
boolean success = mcc.set("test1", "Hello!");
System.out.println(success);

String val = mcc.get("test1");
System.out.println(val);


三、源码阅读

1、客户端初始化过程

初始化客户端相对来说比较简单,默认是创建一个支持TCP协议的 AscIIClient实例化对象,并且获取一个poolName为default的连接池实例对象。

节省一点篇幅,这部分的源码就不展示了。

2、set操作

由于默认是创建了AscIIClient实例对象,set操作的实现自然是落在AscIIClient类上,我们看几个关键的代码片断:

1)key值处理,包含为空校验和转换成unicode编码,代码里取了一个很好听的名字,叫key值净化。

private boolean set(String cmdname, String key, Object value, Date expiry, Integer hashCode, Long casUnique,
boolean asString) {

if (cmdname == null || key == null) {
log.error("key is null or cmd is null/empty for set()");
return false;
}

try {
key = sanitizeKey(key);
} catch (UnsupportedEncodingException e) {

// if we have an errorHandler, use its hook
if (errorHandler != null)
errorHandler.handleErrorOnSet(this, e, key);

log.error("failed to sanitize your key!", e);
return false;
}

// 以下略 ...
}

private String sanitizeKey(String key) throws UnsupportedEncodingException {
return (sanitizeKeys) ? URLEncoder.encode(key, "UTF-8") : key;
}


2)根据key值从连接池中获取SchoonerSockIO对象,这个是Memcache Client操作的核心

/**
* Returns appropriate SockIO object given string cache key and optional
* hashcode.
*
* Trys to get SockIO from pool. Fails over to additional pools in event of
* server failure.
*
* @param key
*            hashcode for cache key
* @param hashCode
*            if not null, then the int hashcode to use
* @return SockIO obj connected to server
*/
public final SchoonerSockIO getSock(String key, Integer hashCode) {

if (!this.initialized) {
log.error("attempting to get SockIO from uninitialized pool!");
return null;
}

// if no servers return null
int size = 0;
if ((this.hashingAlg == CONSISTENT_HASH && consistentBuckets.size() == 0)
|| (buckets != null && (size = buckets.size()) == 0))
return null;
else if (size == 1) {
SchoonerSockIO sock = (this.hashingAlg == CONSISTENT_HASH) ? getConnection(consistentBuckets
.get(consistentBuckets.firstKey())) : getConnection(buckets.get(0));

return sock;
}

// 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++;
}
}
return null;
}


这段代码能很明显地看出一致性Hash算法和其他三个Hash算法处理逻辑的隔离,这里我们先讨论其他三种Hash算法的处理情况。

记得前面一章介绍过,buckets集合里面存储的是memcache服务端连接信息,socketPool集合里存储的是连接信息和GenericObjectPool连接池对象。getConnection()方法的基本思路就是从这两个集合中拿到GenericObjectPool连接池对象,再调用sockets.borrowObject();获得SchoonerSockIO实例。

如果只配置了一个服务端实例,就直接调用getConnection()方法,如果配置了多个服务端实例,这里就涉及到该选用哪一个实例做操作了,memcache中选取服务端的实例是根据key值的hashcode值来决定了,请看getBucket(key, hashCode)方法:

private final long getBucket(String key, Integer hashCode) {
long hc = getHash(key, hashCode);

if (this.hashingAlg == CONSISTENT_HASH) {
return findPointFor(hc);
} else {
long bucket = hc % buckets.size();
if (bucket < 0)
bucket *= -1;
return bucket;
}
}

/**
* Returns a bucket to check for a given key.
*
* @param key
*            String key cache is stored under
* @return int bucket
*/
private final long getHash(String key, Integer hashCode) {

if (hashCode != null) {
if (hashingAlg == CONSISTENT_HASH)
return hashCode.longValue() & 0xffffffffL;
else
return hashCode.longValue();
} else {
switch (hashingAlg) {
case NATIVE_HASH:
return (long) key.hashCode();
case OLD_COMPAT_HASH:
return origCompatHashingAlg(key);
case NEW_COMPAT_HASH:
return newCompatHashingAlg(key);
case CONSISTENT_HASH:
return md5HashingAlg(key);
default:
// use the native hash as a default
hashingAlg = NATIVE_HASH;
return (long) key.hashCode();
}
}
}


首先是调用getHash方法来计算key的hash值,里面使用到的Hash算法都在里面(默认使用Native_Hash算法),注意一下如果hashCode已经指定有值的话,key就不参与Hash运算了,这是为了方便某些特殊的对象,使用固定的Hash值。key的Hash值拿到以后,非一致性Hash算法的处理逻辑是直接将hash值对buckets的大小进行求模运算,得出使用的buckets索引,从而得到一个连接信息对象,最后根据这个连接信息从SocketPool中拉取一个GenericObjectPool对象,完成SchoonerSockIO的实例化。

3)判断value的类型,进行序列化操作

NativeHandle类,主要用于Java基本数据类的转换与解码,是memcache客户端支持基础数据类型的保证,比如把boolean/Boolean类型的true在set操作时转换为1,false转换为0,get操作时再转换回来。共支持14种,各有一个标志值,其他对象(如JavaBean对象)全换成byte数组,支持的14种类型编码如下:

/**
* values for cache flags
*/
public static final int MARKER_BYTE = 1;
public static final int MARKER_BOOLEAN = 8192;
public static final int MARKER_INTEGER = 4;
public static final int MARKER_LONG = 16384;
public static final int MARKER_CHARACTER = 16;
public static final int MARKER_STRING = 32;
public static final int MARKER_STRINGBUFFER = 64;
public static final int MARKER_FLOAT = 128;
public static final int MARKER_SHORT = 256;
public static final int MARKER_DOUBLE = 512;
public static final int MARKER_DATE = 1024;
public static final int MARKER_STRINGBUILDER = 2048;
public static final int MARKER_BYTEARR = 4096;
public static final int MARKER_OTHERS = 0x00;


4)拼接操作命令

其实就是拼接telnet命令,如set 1 32 0

5)网络传输部分

剩下的内容就属于网络编程相关部分了,可以留意一下对象的序列化操作,这里使用nio提升了客户端的通信效率。

最后获取命令的返回值,若为STORED,表示操作成功,结束一次set操作。

这里就是set操作的简单描述,由于这些代码中还穿插了memcache客户端其他特性的实现,比如失效转移,自动恢复等,这里我们先关注最基本的实现思路即可。

3、get操作

其实get/set作为一对操作,在连接信息的定位,连接池的获取,key的hash值计算等这些部分都是类似的,这样才可以保证set的缓存对象能够正确地被get操作获取到,区别在最后的网络通信部分,获取完数据时,将byte[]数组反序列化为Java对象。

返回值示例,正确响应:

VALUE 1 32 6

Hello!

获取失败时响应:END

正确响应的特征是3个空格一个”\r”,所以相关的Response解析代码如下:

while (!stop) {
/*
* Critical block to parse the response header.
*/
b = input.read();
if (b == ' ' || b == '\r') {
switch (index) {
case 0:
if (END.startsWith(sb.toString()))
return null;
case 1:
break;
case 2:
flag = Integer.parseInt(sb.toString());
break;
case 3:
// get the data size
dataSize = Integer.parseInt(sb.toString());
break;
}
index++;
sb = new StringBuffer();
if (b == '\r') {
input.read();
stop = true;
}
continue;
}
sb.append((char) b);
}


四、FAQ

Q1:如果用最简短的几句话,描述set等基本操作,该如何描述?

A1:三句话就可以了,第一:根据key的Hash值找到此次要操作的服务端实例;第二:拼接相关的telnet操作命令;第三:网络通信。

Q2:其他的操作,跟set都是类似的吗?

A2:是的,最关键的问题是相同的,只是拼接的命令和处理命令的响应略有差别而已。

Q3:set操作可以为key指定固定的hashcode值,有什么效果?

A3:getHash() 方法中直接返回该hashcode的值,能够影响getBuckets的结果,目的是可以指定固定的server节点,因为每次hash值都相同,映射算法出来的server节点都是同一个,delete操作也一样的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码 memcache javaclient