以下文章取自: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. hdu 1728 逃离迷宫(dFS+优先队列)

    求转弯最少的走路方式!!!! #include<stdio.h> #include<string.h> #include<queue> using namespac ...

  2. 执行update操作的话,就会报“Connection is read-only. Queries leading to data modification are not allowed”的异常。

    我用的是 spring + springmvc + mybatis +mysql. <tx:advice id="txAdvice" transaction-manager= ...

  3. SQL Server 存储过程之基础知识(转)

    什么是存储过程呢?存储过程就是作为可执行对象存放在数据库中的一个或多个SQL命令. 通俗来讲:存储过程其实就是能完成一定操作的一组SQL语句. 那为什么要用存储过程呢?1.存储过程只在创造时进行编译, ...

  4. sqlserver 执行远程数据库代码

    1.启用Ad Hoc Distributed Queries: exec sp_configure 'show advanced options',1reconfigureexec sp_config ...

  5. .C .h 和 .CCP的区别

    1.*.H:C语言规定使用一个变量或调用一个函数前必须声明,为了使用方便,经常把常用函数,例如Windows API的函数,MFC类写入头文件.h,这样每次需要引用时只要使用#include加入就可以 ...

  6. JSON的parse()方法

    JSON方法也可以接受另外的一个参数,作为还原函数. 实例: var book = { title:"JavaScript Learn", author:["wang&q ...

  7. [C++程序设计]字符数组的赋值与引用

    只能对字符数组的元素赋值,而不能用赋值语句对整个数组赋值. char c[5]; c={′C′,′h′,′i′,′n′,′a′}; //错误,不能对整个数组一次赋值 c[0]=′C′; c[1]=′h ...

  8. python中__init__.py文件的作用

    问题 在执行models.py时,报ImportError:No module named transwarp.db的错误,但明明transwarp下就有db.py文件,路径也没有错误.真是想不通.后 ...

  9. 流媒体(RTMP,RTSP,HLS)

    流媒体(RTMP,RTSP,HLS) 前言 最近项目需要流媒体的播放,后端一共提供了 三种流数据(RTSP,RTMP,HLS),在不同的场景可能会使用到不同方式播放,就需要做到适配, 支持所有的流数据 ...

  10. mysqlbackup

    mysqlbackup 使用学习 1.设置数据库用户的相关权限 '; grant reload,replication client,super,process on *.* to backupuse ...