一、主要内容
本章节的主要内容是介绍Memcache 的初始化过程。
二、准备工作
1、memcache服务端搭建,至少启动两个memcache实例。
2、将下载的memcache-java-client-release的源码导入到Eclipse工程里。
3、memcache-client代码,可以从源码包中的测试类抽取一小部分,如下:
public static void main(String[] args) {
String[] servers = { "192.168.0.106:11211","192.168.0.106:11212" };
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(serverlist);
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(50);
pool.setNagle(false);
pool.initialize();
}
SockIOPool 的参数尽可能少一些,这样有利于排除干扰,pool.initialize();语句执行表示正式进入客户端初始化。
三、源码阅读
1、SockIOPool的实例化方法
主要是调用SchoonerSockIOPool的getInstance(“default”)方法,使用了
ConcurrentMap集合,并且方法内对该集合进行同步锁控制,连接池的实现类是SchoonerSockIOPool ,默认使用TCP方式,如代码所示:
/**
* Factory to create/retrieve new pools given a unique poolName.
*
* @param poolName
* unique name of the pool
* @return instance of SockIOPool
*/
public static SchoonerSockIOPool getInstance(String poolName) {
SchoonerSockIOPool pool;
synchronized (pools) {
if (!pools.containsKey(poolName)) {
pool = new SchoonerSockIOPool(true);
pools.putIfAbsent(poolName, pool);
}
}
return pools.get(poolName);
}
其实也可以创建多个连接池对象,同步安全可以放心的创建,可以根据实际的场景需求创建多个,但一般场景下创建一个就够用了。
2、初始化方法
接下来我们看最核心的initialize()方法,同样进行了锁控制,请注意这点,hash算法会影响Buckets的创建逻辑:一致性Hash(CONSISTENT_HASH )和另外三个Hash算法(NATIVE_HASH、OLD_COMPAT_HASH、NEW_COMPAT_HASH)是不一样的,代码如下:
/**
* Initializes the pool.
*/
public void initialize() {
initDeadLock.lock();
try {
// if servers is not set, or it empty, then
// throw a runtime exception
if (servers == null || servers.length <= 0) {
log.error("++++ trying to initialize with no servers");
throw new IllegalStateException("++++ trying to initialize with no servers");
}
// pools
socketPool = new HashMap<String, GenericObjectPool>(servers.length);
hostDead = new ConcurrentHashMap<String, Date>();
hostDeadDur = new ConcurrentHashMap<String, Long>();
// only create up to maxCreate connections at once
// initalize our internal hashing structures
if (this.hashingAlg == CONSISTENT_HASH)
populateConsistentBuckets();
else
populateBuckets();
// mark pool as initialized
this.initialized = true;
} finally {
initDeadLock.unlock();
}
}
3、Buckets的创建逻辑
这里我们先讨论其他三个Hash算法的情况,一致性Hash算法的Buckets创建逻辑我们会在后面的章节单独讨论。
获取配置信息中的servers配置项(或是前面main方法中的servers数组)生成实例节点,注意这里权重(weights)的用法,权重会增加Buckets集合实例节点的个数,以调整该节点的命中概述。对象池使用的是commons-pool的GenericObjectPool类。如下代码所示:
private void populateBuckets() {
// store buckets in tree map
buckets = new ArrayList<String>();
// servers表示配置文件中的memcache实例服务节点
for (int i = 0; i < servers.length; i++) {
if (this.weights != null && this.weights.length > i) {
for (int k = 0; k < this.weights[i].intValue(); k++) {
// 这里会根据权重的数量来生成虚拟节点
buckets.add(servers[i]);
}
} else {
buckets.add(servers[i]);
}
// Create a socket pool for each host
// Create an object pool to contain our active connections
GenericObjectPool gop;
SchoonerSockIOFactory factory;
if (authInfo != null) {
factory = new AuthSchoonerSockIOFactory(servers[i], isTcp, bufferSize, socketTO, socketConnectTO,
nagle, authInfo);
} else {
factory = new SchoonerSockIOFactory(servers[i], isTcp, bufferSize, socketTO, socketConnectTO, nagle);
}
gop = new GenericObjectPool(factory, maxConn, GenericObjectPool.WHEN_EXHAUSTED_BLOCK, maxIdle, maxConn);
factory.setSockets(gop);
socketPool.put(servers[i], gop);
}
}
这样最基本的创建过程就完成了。
四、FAQ
以下是在阅读代码过程中提的疑问,阅读完之后自行作的解答,仅供参考。
Q1、初始化:权重是如何利用的?
A1:初始化时根据权的数量来填充buckets的数量,权重的关键在于比例,基本上权重越大,在获取服务节点的命中率越高,最后就是各权重累加不一定要是100%。
Q2、SockIOPool的实例化时默认使用TCP协议,如果服务端启动时选择UDP会怎么样?
A2:启动时不会报错,在调用set/get操作时会报错,会报java.io.IOException异常。
Q3、SockIOPool的初始连接数是在什么时候完成的?
A3:本来是应该在初始化时就会创建连接的,但代码中维持连接池连接数据的线程类MaintThread没有找到启动的执行语句,所以刚初始化完是不会有连接的,要等到发起set/get操作时,才会创建连接。查看连接可以在服务端用lsof -i:11211命令或netstat -anp|grep 11211等命令查看。
Q4、memcache客户端的最大连接数是如何实现的?
A4:在创建GenericObjectPool时,就指定了最大连接数maxConn,这部分逻辑是交给commons-pool的GenericObjectPool类来实现的。