以下文章取自:http://jameswxx.iteye.com/blog/1168711

memcached的java客户端有好几种,http://code.google.com/p/memcached/wiki/Clients 罗列了以下几种

  1. spymemcached
  2. * http://www.couchbase.org/code/couchbase/java
  3. o An improved Java API maintained by Matt Ingenthron and others at Couchbase.
  4. o Aggressively optimised, ability to run async, supports binary protocol, support Membase and Couchbase features, etc. See site for details.
  5. Java memcached client
  6. * http://www.whalin.com/memcached
  7. o A Java API is maintained by Greg Whalin from Meetup.com.
  8. More Java memcached clients
  9. * http://code.google.com/p/javamemcachedclient
  10. * http://code.google.com/p/memcache-client-forjava
  11. * http://code.google.com/p/xmemcached
  12. Integrations
  13. * http://code.google.com/p/simple-spring-memcached
  14. * http://code.google.com/p/memcached-session-manager

我看的是第二个:Java memcached client源码,代码很简洁,一共只有9个类,最主要的有以下三个
MemcachedClient.java     客户端,负责提供外出程序接口,如get/set方法等等

SockIOPool.java          一个自平衡的连接池
NativeHandler.java       负责部分数据类型的序列化

它包含以下几个部分
1:key的服务端分布
2:数据序列化和压缩
3:连接池(连接方式和池的动态自动平衡)
4:failover和failback机制
5:和memcached服务器的通讯协议 
关于这几个点,我从key的set/get说起,会贯穿上面列举的4个部分。这个文章写下来,本来是作为一个笔记,思维比较跳跃,可能不是很连贯,如有疑问,欢迎站内交流。这个client的代码

很简洁明了,我也没有加过多注释,只是理了一个脉络。

从客户端自带的测试代码开始

  1. package com.meetup.memcached.test;
  2. import com.meetup.memcached.*;
  3. import org.apache.log4j.*;
  4. public class TestMemcached  {
  5. public static void main(String[] args) {
  6. BasicConfigurator.configure();
  7. String[] servers = { "127.0.0.1:12000"};
  8. SockIOPool pool = SockIOPool.getInstance();
  9. pool.setServers( servers );
  10. pool.setFailover( true );//故障转移
  11. pool.setInitConn( 10 ); //初始化连接为10
  12. pool.setMinConn( 5 );//最小连接为5
  13. pool.setMaxConn( 250 );//最大连接为250
  14. pool.setMaintSleep( 30 );//平衡线程休眠时间为30ms
  15. pool.setNagle( false );//Nagle标志为false
  16. pool.setSocketTO( 3000 );//响应超时时间为3000ms
  17. pool.setAliveCheck( true );//需要可用状态检查
  18. //初始化连接池,默认名称为"default"
  19. pool.initialize();
  20. //新建一个memcached客户端,如果没有给名字
  21. MemcachedClient mcc = new MemcachedClient();
  22. // turn off most memcached client logging:
  23. com.meetup.memcached.Logger.getLogger( MemcachedClient.class.getName() ).setLevel( com.meetup.memcached.Logger.LEVEL_WARN );
  24. for ( int i = 0; i < 10; i++ ) {
  25. boolean success = mcc.set( "" + i, "Hello!" );
  26. String result = (String)mcc.get( "" + i );
  27. System.out.println( String.format( "set( %d ): %s", i, success ) );
  28. System.out.println( String.format( "get( %d ): %s", i, result ) );
  29. }
  30. System.out.println( "\n\t -- sleeping --\n" );
  31. try { Thread.sleep( 10000 ); } catch ( Exception ex ) { }
  32. for ( int i = 0; i < 10; i++ ) {
  33. boolean success = mcc.set( "" + i, "Hello!" );
  34. String result = (String)mcc.get( "" + i );
  35. System.out.println( String.format( "set( %d ): %s", i, success ) );
  36. System.out.println( String.format( "get( %d ): %s", i, result ) );
  37. }
  38. }
  39. }

以上代码大概做了这几件事情:
初始化一个连接池
新建一个memcached客户端
set一个key/value
get一个key,并且打印出value
这是我们实际应用中很常见的场景。

连接池的创建和初始化 
        连接池SockIOPool是非常重要的部分,它的好坏直接决定了客户端的性能。SockIOPool用一个HashMap持有多个连接池对象,连接池以名称作为标识,默认为"default"。看看

SockIOPool的getInstance方法就知道了。

  1. public static SockIOPool getInstance() {
  2. return getInstance("default");
  3. }
  4. public static synchronized SockIOPool getInstance(String poolName) {
  5. if (pools.containsKey(poolName)) return pools.get(poolName);
  6. SockIOPool pool = new SockIOPool();
  7. pools.put(poolName, pool);
  8. return pool;
  9. }

连接池实例化完成后,还需要初始化,看看pool.initialize()做了什么:

  1. public void initialize() {
  2. //这里以自身作为同步锁,防止被多次初始化
  3. synchronized (this) {
  4. // 如果已经被初始化了则终止初始化过程
  5. if (initialized && (buckets != null || consistentBuckets != null) && (availPool != null)&& (busyPool != null)) {
  6. log.error("++++ trying to initialize an already initialized pool");
  7. return;
  8. }
  9. <span style="color: #ff0000;">  // 可用连接集合</span>
  10. availPool = new HashMap<String, Map<SockIO, Long>>(servers.length * initConn);
  11. //工作连接集合
  12. busyPool = new HashMap<String, Map<SockIO, Long>>(servers.length * initConn);
  13. // 不可用连接集合
  14. deadPool = new IdentityHashMap<SockIO, Integer>();
  15. hostDeadDur = new HashMap<String, Long>();
  16. hostDead = new HashMap<String, Date>();
  17. maxCreate = (poolMultiplier > minConn) ? minConn : minConn / poolMultiplier;
  18. if (log.isDebugEnabled()) {
  19. log.debug("++++ initializing pool with following settings:");
  20. log.debug("++++ initial size: " + initConn);
  21. log.debug("++++ min spare   : " + minConn);
  22. log.debug("++++ max spare   : " + maxConn);
  23. }
  24. if (servers == null || servers.length <= 0) {
  25. log.error("++++ trying to initialize with no servers");
  26. throw new IllegalStateException("++++ trying to initialize with no servers");
  27. }
  28. // initalize our internal hashing structures
  29. if (this.hashingAlg == CONSISTENT_HASH) populateConsistentBuckets();
  30. else populateBuckets();
  31. // mark pool as initialized
  32. this.initialized = true;
  33. // start maint thread
  34. if (this.maintSleep > 0) this.startMaintThread();
  35. }
  36. }

连接池的关闭

很简单,只是重置清空相关参数而已

  1. public void shutDown() {
  2. synchronized (this) {
  3. if (log.isDebugEnabled()) log.debug("++++ SockIOPool shutting down...");
  4. if (maintThread != null && maintThread.isRunning()) {
  5. // stop the main thread
  6. stopMaintThread();
  7. // wait for the thread to finish
  8. while (maintThread.isRunning()) {
  9. if (log.isDebugEnabled()) log.debug("++++ waiting for main thread to finish run +++");
  10. try {
  11. Thread.sleep(500);
  12. } catch (Exception ex) {
  13. }
  14. }
  15. }
  16. if (log.isDebugEnabled()) log.debug("++++ closing all internal pools.");
  17. closePool(availPool);
  18. closePool(busyPool);
  19. availPool = null;
  20. busyPool = null;
  21. buckets = null;
  22. consistentBuckets = null;
  23. hostDeadDur = null;
  24. hostDead = null;
  25. maintThread = null;
  26. initialized = false;
  27. if (log.isDebugEnabled()) log.debug("++++ SockIOPool finished shutting down.");
  28. }
  29. }

连接池的自动平衡
SockIOPool的initialize()方法最后有这么一行代码

// start maint thread
if (this.maintSleep > 0) this.startMaintThread();

这是在初始化完成后,启动线程池平衡线程

  1. protected void startMaintThread() {
  2. if (maintThread != null) {
  3. if (maintThread.isRunning()) {
  4. log.error("main thread already running");
  5. } else {
  6. maintThread.start();
  7. }
  8. } else {
  9. maintThread = new MaintThread(this);
  10. maintThread.setInterval(this.maintSleep);
  11. maintThread.start();
  12. }
  13. }

MaintThread的run方法

  1. public void run() {
  2. this.running = true;
  3. while (!this.stopThread) {
  4. try {
  5. Thread.sleep(interval);
  6. // if pool is initialized, then
  7. // run the maintenance method on itself
  8. if (pool.isInitialized()) pool.selfMaint();
  9. } catch (Exception e) {
  10. break;
  11. }
  12. }
  13. this.running = false;

 其实最终的平衡方法是SockIOPool.selfMaint()

  1. protected void selfMaint() {
  2. if (log.isDebugEnabled()) log.debug("++++ Starting self maintenance....");
  3. // go through avail sockets and create sockets
  4. // as needed to maintain pool settings
  5. Map<String, Integer> needSockets = new HashMap<String, Integer>();
  6. synchronized (this) {
  7. // 先统计每个服务器实例的可用连接是否小于最小可用连接数
  8. for (Iterator<String> i = availPool.keySet().iterator(); i.hasNext();) {
  9. String host = i.next();
  10. Map<SockIO, Long> sockets = availPool.get(host);
  11. if (log.isDebugEnabled()) log.debug("++++ Size of avail pool for host (" + host + ") = "
  12. + sockets.size());
  13. // if pool is too small (n < minSpare)
  14. if (sockets.size() < minConn) {
  15. // need to create new sockets
  16. int need = minConn - sockets.size();
  17. needSockets.put(host, need);
  18. }
  19. }
  20. }
  21. // 如果小于最小可用连接数,则要新建增加可用连接
  22. Map<String, Set<SockIO>> newSockets = new HashMap<String, Set<SockIO>>();
  23. for (String host : needSockets.keySet()) {
  24. Integer need = needSockets.get(host);
  25. if (log.isDebugEnabled()) log.debug("++++ Need to create " + need + " new sockets for pool for host: "
  26. + host);
  27. Set<SockIO> newSock = new HashSet<SockIO>(need);
  28. for (int j = 0; j < need; j++) {
  29. SockIO socket = createSocket(host);
  30. if (socket == null) break;
  31. newSock.add(socket);
  32. }
  33. newSockets.put(host, newSock);
  34. }
  35. // synchronize to add and remove to/from avail pool
  36. // as well as clean up the busy pool (no point in releasing
  37. // lock here as should be quick to pool adjust and no
  38. // blocking ops here)
  39. synchronized (this) {
  40. //将新建的连接添加到可用连接集合里
  41. for (String host : newSockets.keySet()) {
  42. Set<SockIO> sockets = newSockets.get(host);
  43. for (SockIO socket : sockets)
  44. addSocketToPool(availPool, host, socket);
  45. }
  46. for (Iterator<String> i = availPool.keySet().iterator(); i.hasNext();) {
  47. String host = i.next();
  48. Map<SockIO, Long> sockets = availPool.get(host);
  49. if (log.isDebugEnabled()) log.debug("++++ Size of avail pool for host (" + host + ") = "
  50. + sockets.size());
  51. //如果可用连接超过了最大连接数,则要关闭一些
  52. if (sockets.size() > maxConn) {
  53. // need to close down some sockets
  54. int diff = sockets.size() - maxConn;
  55. int needToClose = (diff <= poolMultiplier) ? diff : (diff) / poolMultiplier;
  56. if (log.isDebugEnabled()) log.debug("++++ need to remove " + needToClose
  57. + " spare sockets for pool for host: " + host);
  58. for (Iterator<SockIO> j = sockets.keySet().iterator(); j.hasNext();) {
  59. if (needToClose <= 0) break;
  60. // remove stale entries
  61. SockIO socket = j.next();
  62. long expire = sockets.get(socket).longValue();
  63. // 这里回收可用连接池的闲置连接,连接设置到可用连接池里时,expire设置为当前时间。如果 (expire + maxIdle) < System.currentTimeMillis()为true,则表
  64. 明,该连接在可用连接池呆得太久了,需要回收
  65. if ((expire + maxIdle) < System.currentTimeMillis()) {
  66. if (log.isDebugEnabled()) log.debug("+++ removing stale entry from pool as it is past its idle timeout and pool is over max spare");
  67. // remove from the availPool
  68. deadPool.put(socket, ZERO);
  69. j.remove();
  70. needToClose--;
  71. }
  72. }
  73. }
  74. }
  75. //清理正在工作的连接集合
  76. for (Iterator<String> i = busyPool.keySet().iterator(); i.hasNext();) {
  77. String host = i.next();
  78. Map<SockIO, Long> sockets = busyPool.get(host);
  79. if (log.isDebugEnabled()) log.debug("++++ Size of busy pool for host (" + host + ")  = "
  80. + sockets.size());
  81. // loop through all connections and check to see if we have any hung connections
  82. for (Iterator<SockIO> j = sockets.keySet().iterator(); j.hasNext();) {
  83. // remove stale entries
  84. SockIO socket = j.next();
  85. long hungTime = sockets.get(socket).longValue();
  86. //如果工作时间超过maxBusyTime,则也要回收掉,超过maxBusyTime,可能是服务器响应时间过长
  87. if ((hungTime + maxBusyTime) < System.currentTimeMillis()) {
  88. log.error("+++ removing potentially hung connection from busy pool ... socket in pool for "
  89. + (System.currentTimeMillis() - hungTime) + "ms");
  90. // remove from the busy pool
  91. deadPool.put(socket, ZERO);
  92. j.remove();
  93. }
  94. }
  95. }
  96. }
  97. // 最后清理不可用连接集合
  98. Set<SockIO> toClose;
  99. synchronized (deadPool) {
  100. toClose = deadPool.keySet();
  101. deadPool = new IdentityHashMap<SockIO, Integer>();
  102. }
  103. for (SockIO socket : toClose) {
  104. try {
  105. socket.trueClose(false);
  106. } catch (Exception ex) {
  107. log.error("++++ failed to close SockIO obj from deadPool");
  108. log.error(ex.getMessage(), ex);
  109. }
  110. socket = null;
  111. }
  112. if (log.isDebugEnabled()) log.debug("+++ ending self maintenance.");
  113. }

key的服务器端分布

初始化方法其实就是根据每个服务器的权重,建立一个服务器地址集合,如果选择了一致性哈希,则对服务器地址进行一致性哈希分布,一致性哈希算法比较简单,如果不了解的同学,可以

自行google一下,initialize() 方法里有这段代码:

//一致性哈希

  1. if (this.hashingAlg == CONSISTENT_HASH){
  2. populateConsistentBuckets();
  3. }else populateBuckets();

看看populateConsistentBuckets()方法

// 用一致性哈希算法将服务器分布在一个2的32次方的环里,服务器的分布位置<=servers.length*40*4

  1. private void populateConsistentBuckets() {
  2. if (log.isDebugEnabled()) log.debug("++++ initializing internal hashing structure for consistent hashing");
  3. // store buckets in tree map
  4. this.consistentBuckets = new TreeMap<Long, String>();
  5. MessageDigest md5 = MD5.get();
  6. if (this.totalWeight <= 0 && this.weights != null) {
  7. for (int i = 0; i < this.weights.length; i++)
  8. this.totalWeight += (this.weights[i] == null) ? 1 : this.weights[i];
  9. } else if (this.weights == null) {
  10. this.totalWeight = this.servers.length;
  11. }
  12. for (int i = 0; i < servers.length; i++) {
  13. int thisWeight = 1;
  14. if (this.weights != null && this.weights[i] != null) thisWeight = this.weights[i];
  15. //这个值永远小于40 * this.servers.length,因为thisWeight/totalWeight永远小于1
  1. double factor = Math.floor(((double) (40 * this.servers.length * thisWeight)) / (double) this.totalWeight);
  2. //服务器的分布位置为factor*4,factor<=40*this.servers.length,所以服务器的分布位置& lt;=40*this.servers.length*4。
  3. for (long j = 0; j < factor; j++) {
  4. //md5值的二进制数组为16位
  5. byte[] d = md5.digest((servers[i] + "-" + j).getBytes());
  6. //16位二进制数组每4位为一组,每组第4个值左移24位,第三个值左移16位,第二个值左移8位,第一个值不移位。进行或运算,得到一个小于2的32 次方的long值。
  7. for (int h = 0; h < 4; h++) {
  8. Long k = ((long) (d[3 + h * 4] & 0xFF) << 24) | ((long) (d[2 + h * 4] & 0xFF) << 16)
  9. | ((long) (d[1 + h * 4] & 0xFF) << 8) | ((long) (d[0 + h * 4] & 0xFF));
  10. consistentBuckets.put(k, servers[i]);
  11. if (log.isDebugEnabled()) log.debug("++++ added " + servers[i] + " to server bucket");
  12. }
  13. }
  14. // create initial connections
  15. if (log.isDebugEnabled()) log.debug("+++ creating initial connections (" + initConn + ") for host: "
  16. + servers[i]);
  17. //创建连接
  18. for (int j = 0; j < initConn; j++) {
  19. SockIO socket = createSocket(servers[i]);
  20. if (socket == null) {
  21. log.error("++++ failed to create connection to: " + servers[i] + " -- only " + j + " created.");
  22. break;
  23. }
  24. //添加到可用连接池
  25. addSocketToPool(availPool, servers[i], socket);
  26. if (log.isDebugEnabled()) log.debug("++++ created and added socket: " + socket.toString()
  27. + " for host " + servers[i]);
  28. }
  29. }

如果不是一致性哈希,则只是普通分布,很简单,只是根据权重将服务器地址放入buckets这个List里

  1. private void populateBuckets() {
  2. if (log.isDebugEnabled()) log.debug("++++ initializing internal hashing structure for consistent hashing");
  3. // store buckets in tree map
  4. this.buckets = new ArrayList<String>();
  5. for (int i = 0; i < servers.length; i++) {
  6. if (this.weights != null && this.weights.length > i) {
  7. for (int k = 0; k < this.weights[i].intValue(); k++) {
  8. this.buckets.add(servers[i]);
  9. if (log.isDebugEnabled()) log.debug("++++ added " + servers[i] + " to server bucket");
  10. }
  11. } else {
  12. this.buckets.add(servers[i]);
  13. if (log.isDebugEnabled()) log.debug("++++ added " + servers[i] + " to server bucket");
  14. }
  15. // create initial connections
  16. if (log.isDebugEnabled()) log.debug("+++ creating initial connections (" + initConn + ") for host: "
  17. + servers[i]);
  18. for (int j = 0; j < initConn; j++) {
  19. SockIO socket = createSocket(servers[i]);
  20. if (socket == null) {
  21. log.error("++++ failed to create connection to: " + servers[i] + " -- only " + j + " created.");
  22. break;
  23. }
  24. //新建连接后,加入到可用连接集合里
  25. addSocketToPool(availPool, servers[i], socket);
  26. if (log.isDebugEnabled()) log.debug("++++ created and added socket: " + socket.toString()
  27. + " for host " + servers[i]);
  28. }
  29. }
  30. }

如何创建socket连接

在上面的private void populateBuckets()方法里,createSocket(servers[i])是创建到服务器的连接,看看这个方法

  1. protected SockIO createSocket(String host) {
  2. SockIO socket = null;
  3. //hostDeadLock是一个可重入锁,它的变量声明为
  4. private final ReentrantLock             hostDeadLock    = new ReentrantLock();
  5. hostDeadLock.lock();
  6. try {
  7. //hostDead.containsKey(host)为true表示曾经连接过该服务器,但没有成功。
  8. //hostDead是一个HashMap,key为服务器地址,value为当时连接不成功的时间
  9. //hostDeadDur是一个HashMap,key为服务器地址,value为设置的重试间隔时间
  10. if (failover && failback && hostDead.containsKey(host) && hostDeadDur.containsKey(host)) {
  11. Date store = hostDead.get(host);
  12. long expire = hostDeadDur.get(host).longValue();
  13. if ((store.getTime() + expire) > System.currentTimeMillis()) return null;
  14. }
  15. } finally {
  16. hostDeadLock.unlock();
  17. }
  18. try {
  19. socket = new SockIO(this, host, this.socketTO, this.socketConnectTO, this.nagle);
  20. if (!socket.isConnected()) {
  21. log.error("++++ failed to get SockIO obj for: " + host + " -- new socket is not connected");
  22. deadPool.put(socket, ZERO);
  23. socket = null;
  24. }
  25. } catch (Exception ex) {
  26. log.error("++++ failed to get SockIO obj for: " + host);
  27. log.error(ex.getMessage(), ex);
  28. socket = null;
  29. }
  30. // if we failed to get socket, then mark
  31. // host dead for a duration which falls off
  32. hostDeadLock.lock();
  33. try {
  34. //到了这里,socket仍然为null,说明这个server悲剧了,无法和它创建连接,则要把该server丢到不可用的主机集合里
  35. if (socket == null) {
  36. Date now = new Date();
  37. hostDead.put(host, now);
  38. //如果上次就不可用了,到期了仍然不可用,就要这次的不可用时间设为上次的2倍,否则初始时长为1000ms
  39. long expire = (hostDeadDur.containsKey(host)) ? (((Long) hostDeadDur.get(host)).longValue() * 2) : 1000;
  40. if (expire > MAX_RETRY_DELAY) expire = MAX_RETRY_DELAY;
  41. hostDeadDur.put(host, new Long(expire));
  42. if (log.isDebugEnabled()) log.debug("++++ ignoring dead host: " + host + " for " + expire + " ms");
  43. // 既然这个host都不可用了,那与它的所有连接当然要从可用连接集合"availPool"里删除掉
  44. clearHostFromPool(availPool, host);
  45. } else {
  46. if (log.isDebugEnabled()) log.debug("++++ created socket (" + socket.toString() + ") for host: " + host);
  47. //连接创建成功,如果上次不成功,那么这次要把该host从不可用主机集合里删除掉
  48. if (hostDead.containsKey(host) || hostDeadDur.containsKey(host)) {
  49. hostDead.remove(host);
  50. hostDeadDur.remove(host);
  51. }
  52. }
  53. } finally {
  54. hostDeadLock.unlock();
  55. }
  56. return socket;
  57. }

  SockIO构造函数

  1. public SockIO(SockIOPool pool, String host, int timeout, int connectTimeout, boolean noDelay)
  2. throws IOException,
  3. UnknownHostException {
  4. this.pool = pool;
  5. String[] ip = host.split(":");
  6. // get socket: default is to use non-blocking connect
  7. sock = getSocket(ip[0], Integer.parseInt(ip[1]), connectTimeout);
  8. if (timeout >= 0) this.sock.setSoTimeout(timeout);
  9. // testing only
  10. sock.setTcpNoDelay(noDelay);
  11. // wrap streams
  12. in = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
  13. out = new BufferedOutputStream(sock.getOutputStream());
  14. this.host = host;
  15. }

getSocket方法

  1. protected static Socket getSocket(String host, int port, int timeout) throws IOException {
  2. SocketChannel sock = SocketChannel.open();
  3. sock.socket().connect(new InetSocketAddress(host, port), timeout);
  4. return sock.socket();
  5. }

可以看到,socket连接是用nio方式创建的。

新建MemcachedClient 
MemcachedClient mcc = new MemcachedClient();新建了一个memcached客户端,看看构造函数,没作什么,只是设置参数而已。

  1. /**
  2. * Creates a new instance of MemCachedClient.
  3. */
  4. public MemcachedClient() {
  5. init();
  6. }
  7. private void init() {
  8. this.sanitizeKeys       = true;
  9. this.primitiveAsString  = false;
  10. this.compressEnable     = true;
  11. this.compressThreshold  = COMPRESS_THRESH;
  12. this.defaultEncoding    = "UTF-8";
  13. this.poolName           = ( this.poolName == null ) ? "default" : this.poolName;
  14. // get a pool instance to work with for the life of this instance
  15. this.pool               = SockIOPool.getInstance( poolName );
  16. }

set方法如何工作

到此memcached客户端初始化工作完成。再回到测试类TestMemcached,看看for循环里的

boolean success = mcc.set( ""  + i, "Hello!" );
 String result = (String)mcc.get( "" + i );
 初始化后,就可以set,get了。看看set是怎么工作的。

  1. /**
  2. * Stores data on the server; only the key and the value are specified.
  3. *
  4. * @param key key to store data under
  5. * @param value value to store
  6. * @return true, if the data was successfully stored
  7. */
  8. public boolean set( String key, Object value ) {
  9. return set( "set", key, value, null, null, primitiveAsString );
  10. }
  11. //这个set方法比较长
  12. private boolean set( String cmdname, String key, Object value, Date expiry, Integer hashCode, boolean asString ) {
  13. if ( cmdname == null || cmdname.trim().equals( "" ) || key == null ) {
  14. log.error( "key is null or cmd is null/empty for set()" );
  15. return false;
  16. }
  17. try {
  18. key = sanitizeKey( key );
  19. }
  20. catch ( UnsupportedEncodingException e ) {
  21. // if we have an errorHandler, use its hook
  22. if ( errorHandler != null )
  23. errorHandler.handleErrorOnSet( this, e, key );
  24. log.error( "failed to sanitize your key!", e );
  25. return false;
  26. }
  27. if ( value == null ) {
  28. log.error( "trying to store a null value to cache" );
  29. return false;
  30. }
  31. // get SockIO obj
  32. SockIOPool.SockIO sock = pool.getSock( key, hashCode );
  33. if ( sock == null ) {
  34. if ( errorHandler != null )
  35. errorHandler.handleErrorOnSet( this, new IOException( "no socket to server available" ), key );
  36. return false;
  37. }
  38. if ( expiry == null )
  39. expiry = new Date(0);
  40. // store flags
  41. int flags = 0;
  42. // byte array to hold data
  43. byte[] val;
  44. //这些类型自己序列化,否则由java序列化处理
  45. if ( NativeHandler.isHandled( value ) ) {
  46. if ( asString ) {
  47. //如果是字符串,则直接getBytes
  48. try {
  49. if ( log.isInfoEnabled() )
  50. log.info( "++++ storing data as a string for key: " + key + " for class: " + value.getClass().getName() );
  51. val = value.toString().getBytes( defaultEncoding );
  52. }
  53. catch ( UnsupportedEncodingException ue ) {
  54. // if we have an errorHandler, use its hook
  55. if ( errorHandler != null )
  56. errorHandler.handleErrorOnSet( this, ue, key );
  57. log.error( "invalid encoding type used: " + defaultEncoding, ue );
  58. sock.close();
  59. sock = null;
  60. return false;
  61. }
  62. }
  63. else {
  64. try {
  65. if ( log.isInfoEnabled() )
  66. log.info( "Storing with native handler..." );
  67. flags |= NativeHandler.getMarkerFlag( value );
  68. val    = NativeHandler.encode( value );
  69. }
  70. catch ( Exception e ) {
  71. // if we have an errorHandler, use its hook
  72. if ( errorHandler != null )
  73. errorHandler.handleErrorOnSet( this, e, key );
  74. log.error( "Failed to native handle obj", e );
  75. sock.close();
  76. sock = null;
  77. return false;
  78. }
  79. }
  80. }
  81. else {
  82. // 否则用java的序列化
  83. try {
  84. if ( log.isInfoEnabled() )
  85. log.info( "++++ serializing for key: " + key + " for class: " + value.getClass().getName() );
  86. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  87. (new ObjectOutputStream( bos )).writeObject( value );
  88. val = bos.toByteArray();
  89. flags |= F_SERIALIZED;
  90. }
  91. catch ( IOException e ) {
  92. // if we have an errorHandler, use its hook
  93. if ( errorHandler != null )
  94. errorHandler.handleErrorOnSet( this, e, key );
  95. // if we fail to serialize, then
  96. // we bail
  97. log.error( "failed to serialize obj", e );
  98. log.error( value.toString() );
  99. // return socket to pool and bail
  100. sock.close();
  101. sock = null;
  102. return false;
  103. }
  104. }
  105. //压缩内容
  106. if ( compressEnable && val.length > compressThreshold ) {
  107. try {
  108. if ( log.isInfoEnabled() ) {
  109. log.info( "++++ trying to compress data" );
  110. log.info( "++++ size prior to compression: " + val.length );
  111. }
  112. ByteArrayOutputStream bos = new ByteArrayOutputStream( val.length );
  113. GZIPOutputStream gos = new GZIPOutputStream( bos );
  114. gos.write( val, 0, val.length );
  115. gos.finish();
  116. gos.close();
  117. // store it and set compression flag
  118. val = bos.toByteArray();
  119. flags |= F_COMPRESSED;
  120. if ( log.isInfoEnabled() )
  121. log.info( "++++ compression succeeded, size after: " + val.length );
  122. }
  123. catch ( IOException e ) {
  124. // if we have an errorHandler, use its hook
  125. if ( errorHandler != null )
  126. errorHandler.handleErrorOnSet( this, e, key );
  127. log.error( "IOException while compressing stream: " + e.getMessage() );
  128. log.error( "storing data uncompressed" );
  129. }
  130. }
  131. // now write the data to the cache server
  132. try {
  133. //按照memcached协议组装命令
  134. String cmd = String.format( "%s %s %d %d %d\r\n", cmdname, key, flags, (expiry.getTime() / 1000), val.length );
  135. sock.write( cmd.getBytes() );
  136. sock.write( val );
  137. sock.write( "\r\n".getBytes() );
  138. sock.flush();
  139. // get result code
  140. String line = sock.readLine();
  141. if ( log.isInfoEnabled() )
  142. log.info( "++++ memcache cmd (result code): " + cmd + " (" + line + ")" );
  143. if ( STORED.equals( line ) ) {
  144. if ( log.isInfoEnabled() )
  145. log.info("++++ data successfully stored for key: " + key );
  146. sock.close();
  147. sock = null;
  148. return true;
  149. }
  150. else if ( NOTSTORED.equals( line ) ) {
  151. if ( log.isInfoEnabled() )
  152. log.info( "++++ data not stored in cache for key: " + key );
  153. }
  154. else {
  155. log.error( "++++ error storing data in cache for key: " + key + " -- length: " + val.length );
  156. log.error( "++++ server response: " + line );
  157. }
  158. }
  159. catch ( IOException e ) {
  160. // if we have an errorHandler, use its hook
  161. if ( errorHandler != null )
  162. errorHandler.handleErrorOnSet( this, e, key );
  163. // exception thrown
  164. log.error( "++++ exception thrown while writing bytes to server on set" );
  165. log.error( e.getMessage(), e );
  166. try {
  167. sock.trueClose();
  168. }
  169. catch ( IOException ioe ) {
  170. log.error( "++++ failed to close socket : " + sock.toString() );
  171. }
  172. sock = null;
  173. }
  174. //用完了,就要回收哦,sock.close()不是真正的关闭,只是放入到可用连接集合里。
  175. if ( sock != null ) {
  176. sock.close();
  177. sock = null;
  178. }
  179. return false;
  180. }

通过set方法向服务器设置key和value,涉及到以下几个点
数据的压缩和序列化 (如果是get方法,则和set方法基本是相反的)
为key分配服务器 对于一些常用类型,采用自定义的序列化,具体要看NativeHander.java,这个类比较简单,有兴趣可以自己看看

  1. public static boolean isHandled( Object value ) {
  2. return (
  3. value instanceof Byte            ||
  4. value instanceof Boolean         ||
  5. value instanceof Integer         ||
  6. value instanceof Long            ||
  7. value instanceof Character       ||
  8. value instanceof String          ||
  9. value instanceof StringBuffer    ||
  10. value instanceof Float           ||
  11. value instanceof Short           ||
  12. value instanceof Double          ||
  13. value instanceof Date            ||
  14. value instanceof StringBuilder   ||
  15. value instanceof byte[]
  16. )
  17. ? true
  18. : false;
  19. }

其他类型则用java的默认序列化

为key选择服务器 
SockIOPool.SockIO sock = pool.getSock( key, hashCode );就是为key选择服务器

  1. public SockIO getSock(String key, Integer hashCode) {
  2. if (log.isDebugEnabled()) log.debug("cache socket pick " + key + " " + hashCode);
  3. if (!this.initialized) {
  4. log.error("attempting to get SockIO from uninitialized pool!");
  5. return null;
  6. }
  7. // if no servers return null
  8. if ((this.hashingAlg == CONSISTENT_HASH && consistentBuckets.size() == 0)
  9. || (buckets != null && buckets.size() == 0)) return null;
  10. // if only one server, return it
  11. if ((this.hashingAlg == CONSISTENT_HASH && consistentBuckets.size() == 1)
  12. || (buckets != null && buckets.size() == 1)) {
  13. SockIO sock = (this.hashingAlg == CONSISTENT_HASH) ? getConnection(consistentBuckets.get(consistentBuckets.firstKey())) : getConnection(buckets.get(0));
  14. if (sock != null && sock.isConnected()) {
  15. if (aliveCheck) {//健康状态检查
  16. if (!sock.isAlive()) {
  17. sock.close();
  18. try {
  19. sock.trueClose();//有问题,真的关闭socket
  20. } catch (IOException ioe) {
  21. log.error("failed to close dead socket");
  22. }
  23. sock = null;
  24. }
  25. }
  26. } else {//连接不正常,放入不可用连接集合里
  27. if (sock != null) {
  28. deadPool.put(sock, ZERO);
  29. sock = null;
  30. }
  31. }
  32. return sock;
  33. }
  34. Set<String> tryServers = new HashSet<String>(Arrays.asList(servers));
  35. // get initial bucket
  36. long bucket = getBucket(key, hashCode);
  37. String server = (this.hashingAlg == CONSISTENT_HASH) ? consistentBuckets.get(bucket) : buckets.get((int) bucket);
  38. while (!tryServers.isEmpty()) {
  39. // try to get socket from bucket
  40. SockIO sock = getConnection(server);
  41. if (log.isDebugEnabled()) log.debug("cache choose " + server + " for " + key);
  42. if (sock != null && sock.isConnected()) {
  43. if (aliveCheck) {
  44. if (sock.isAlive()) {
  45. return sock;
  46. } else {
  47. sock.close();
  48. try {
  49. sock.trueClose();
  50. } catch (IOException ioe) {
  51. log.error("failed to close dead socket");
  52. }
  53. sock = null;
  54. }
  55. } else {
  56. return sock;
  57. }
  58. } else {
  59. if (sock != null) {
  60. deadPool.put(sock, ZERO);
  61. sock = null;
  62. }
  63. }
  64. // if we do not want to failover, then bail here
  65. if (!failover) return null;
  66. // log that we tried
  67. tryServers.remove(server);
  68. if (tryServers.isEmpty()) break;
  69. //注意哦,下面是failover机制
  70. int rehashTries = 0;
  71. while (!tryServers.contains(server)) {
  72. String newKey = String.format("%s%s", rehashTries, key);
  73. if (log.isDebugEnabled()) log.debug("rehashing with: " + newKey);
  74. bucket = getBucket(newKey, null);
  75. server = (this.hashingAlg == CONSISTENT_HASH) ? consistentBuckets.get(bucket) : buckets.get((int) bucket);
  76. rehashTries++;
  77. }
  78. }
  79. return null;
  80. }

下面这个方法是真正的从服务器获取连接

  1. public SockIO getConnection(String host) {
  2. if (!this.initialized) {
  3. log.error("attempting to get SockIO from uninitialized pool!");
  4. return null;
  5. }
  6. if (host == null) return null;
  7. synchronized (this) {
  8. // if we have items in the pool
  9. // then we can return it
  10. if (availPool != null && !availPool.isEmpty()) {
  11. // take first connected socket
  12. Map<SockIO, Long> aSockets = availPool.get(host);
  13. if (aSockets != null && !aSockets.isEmpty()) {
  14. for (Iterator<SockIO> i = aSockets.keySet().iterator(); i.hasNext();) {
  15. SockIO socket = i.next();
  16. if (socket.isConnected()) {
  17. if (log.isDebugEnabled()) log.debug("++++ moving socket for host (" + host
  18. + ") to busy pool ... socket: " + socket);
  19. // remove from avail pool
  20. i.remove();
  21. // add to busy pool
  22. addSocketToPool(busyPool, host, socket);
  23. // return socket
  24. return socket;
  25. } else {
  26. // add to deadpool for later reaping
  27. deadPool.put(socket, ZERO);
  28. // remove from avail pool
  29. i.remove();
  30. }
  31. }
  32. }
  33. }
  34. }
  35. // create one socket -- let the maint thread take care of creating more
  36. SockIO socket = createSocket(host);
  37. if (socket != null) {
  38. synchronized (this) {
  39. addSocketToPool(busyPool, host, socket);
  40. }
  41. }
  42. return socket;
  43. }

failover和failback

这两者都是发生在获取可用连接这个环节。

failover,如果为key选择的服务器不可用,则对key重新哈希选择下一个服务器,详见getSock方法的末尾。

failback,用一个hashmap存储连接失败的服务器和对应的失效持续时间,每次获取连接时,都探测是否到了重试时间。

代码中实际运用memcached——java的更多相关文章

  1. 代码中实际运用memcached——mycode

    1.下载安装64位memcached.exe  下载地址:http://blog.couchbase.com/memcached-windows-64-bit-pre-release-availabl ...

  2. 代码中实际运用memcached——.NET

    本文取自:http://blog.csdn.net/dyllove98/article/details/9115947 memcached安装:============================ ...

  3. 删除 java代码中所有的注释

    删除 java代码中所有的注释.java public class CleanCommons { private static Pattern pattern = Pattern.compile(&q ...

  4. Memcached学习笔记 — 第四部分:Memcached Java 客户端-gwhalin(1)-介绍及使用

     介绍 Memcached java client是官方推荐的最早的memcached java客户端.最新版本:java_memcached-release_2.6.1. 官方下载地址:http ...

  5. Kivy A to Z -- 怎样从python代码中直接訪问Android的Service

    在Kivy中,通过pyjnius扩展能够间接调用Java代码,而pyjnius利用的是Java的反射机制.可是在Python对象和Java对象中转来转去总让人感觉到十分别扭.好在android提供了b ...

  6. 入门: 使用JNI 从C++代码中调用Java的静态方法

    开发环境: 操作系统: (uname -a output)  Linux ubuntu 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:16:28 UTC ...

  7. 如何解救在异步Java代码中已检测的异常

    Java语言通过已检测异常语法所提供的静态异常检测功能非常实用,通过它程序开发人员可以用很便捷的方式表达复杂的程序流程. 实际上,如果某个函数预期将返回某种类型的数据,通过已检测异常,很容易就可以扩展 ...

  8. 本地C代码中创建Java对象

    作者:唐老师,华清远见嵌入式学院讲师. 创建Java域的对象就是创建Java类的实例,再调用Java类的构造方法. 以Bitmap的构建为例,Bitmap中并没有Java对象创建的代码及外部能访问的构 ...

  9. Spring MVC框架下在java代码中访问applicationContext.xml文件中配置的文件(可以用于读取配置文件内容)

    <bean id="propertyConfigurer" class="com.****.framework.core.SpringPropertiesUtil& ...

随机推荐

  1. 详细解读Jquery各Ajax函数:$.get(),$.post(),$.ajax(),$.getJSON() —(转)

    一,$.get(url,[data],[callback]) 说明:url为请求地址,data为请求数据的列表(是可选的,也可以将要传的参数写在url里面),callback为请求成功后的回调函数,该 ...

  2. bash的元字符(下)

    ` "Esc"键正下方键,替换命令 PS1=`command` {} 在当前shell中运行命令 {command1;command2} | 创建命令间的管道 command1 | ...

  3. Git 多人协作的工作模式

    多人协作 148次阅读 当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin. 要查看远程库的信息,用git rem ...

  4. textarea 的最大高度以及最小高度

    <script type="text/javascript"> $(function(){ $("#textarea3").textareaAuto ...

  5. UIDeviceOrientation UIInterfaceOrientation 区别

    UIDeviceOrientation      是机器硬件的当前旋转方向   这个你只能取值 不能设置 UIInterfaceOrientation   是你程序界面的当前旋转方向   这个可以设置 ...

  6. MagicalRecord(简化CoreData操作)

    1.新建项目不勾选coredata 2.pod 'MagicalRecord' 3.新建模型文件 4.添加实体和属性 5.Create NSManagedObject subclass 6.增 Per ...

  7. Linux系统的组成和内核的组成

    关于linux的组成的宏观认识. 组成图: 内核组成: 一个完整的Linux内核一般由5部分组成,它们分别是内存管理.进程管理.进程间通信.虚拟文件系统和网络接口.

  8. (原)调用jpeglib对图像进行压缩

    网址:http://www.cnblogs.com/darkknightzh/p/4973828.html.未经允许,严禁转载. 参考网站: http://dev.w3.org/Amaya/libjp ...

  9. [原]此程序专用来说明C++模板的用法

    #include using namespace std; //此程序专用来说明模版的使用 template void swap1(T& a,T& b){     T temp=a; ...

  10. linux的一点一滴---open

    open函数用于打开和创建一个文件. 所需头文件: #include<sys/types.h> #include <sys/stat.h> #include <fcntl ...