Memcached旅终结

来源:互联网 发布:zookeeper paxos算法 编辑:程序博客网 时间:2024/05/16 16:01


1. 什么是CAS协议

很多中文的资料都不会告诉大家CAS的全称是什么,Google.com一下,CAS是什么?CAS是Check And Set的缩写。

2. CAS协议原文

http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

3. CAS的基本原理

基本原理非常简单,一言以蔽之,就是“版本号”。每个存储的数据对象,多有一个版本号。我们可以从下面的例子来理解:

如果不采用CAS,则有如下的情景:

第一步,A取出数据对象X;

第二步,B取出数据对象X;

第三步,B修改数据对象X,并将其放入缓存;

第四步,A修改数据对象X,并将其放入缓存。

如果采用CAS协议,则是如下的情景。

第一步,A取出数据对象X,并获取到CAS-ID1;

第二步,B取出数据对象X,并获取到CAS-ID2;

第三步,B修改数据对象X,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“一致”,就将修改后的带有CAS-ID2的X写入到缓存。

第四步,A修改数据对象Y,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“不一致”,则拒绝写入,返回存储失败。

这样CAS协议就用了“版本号”,解决了这个问题。

我们可以发现,第四步中会产生数据写入失败

1. 非CAS

首先看一个不是CAS的Memcached程序实例。

程序实例:

package com.sinosuperman.memcached; 
import java.io.IOException; 
import java.net.InetSocketAddress; 
import net.spy.memcached.MemcachedClient; 
public class Test { 
    public static void main(String[] args) throws IOException { 
        MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211)); 
         
        cache.set("x", 1800, "Love"); 
 
        String obj1 = (String) cache.get("x"); 
        String obj2 = (String) cache.get("x"); 
        obj2 = "Michael"; 
         
        cache.set("x", 1800, obj2); 
        System.out.println("Non-CAS 2:\t" + obj2); 
        System.out.println("Non-CAS 1:\t" + obj1); 
    } 

package com.sinosuperman.memcached;

import java.io.IOException;
import java.net.InetSocketAddress;

import net.spy.memcached.MemcachedClient;

public class Test {
 public static void main(String[] args) throws IOException {
  MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));
 
  cache.set("x", 1800, "Love");

  String obj1 = (String) cache.get("x");
  String obj2 = (String) cache.get("x");
  obj2 = "Michael";
 
  cache.set("x", 1800, obj2);
  System.out.println("Non-CAS 2:\t" + obj2);
  System.out.println("Non-CAS 1:\t" + obj1);
 }
}

运行结果:

2011-12-18 23:12:39.836 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/127.0.0.1:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 
2011-12-18 23:12:39.843 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for ***
Non-CAS 2:  Michael 
Non-CAS 1:  Love 
2011-12-18 23:12:39.836 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/127.0.0.1:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2011-12-18 23:12:39.843 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for
Non-CAS 2: Michael
Non-CAS 1: Love

 

可见在多个Client操作时,一定会引起写不一致性的问题。


2. CAS

程序实例:

package com.sinosuperman.memcached; 
 
import java.io.IOException; 
import java.net.InetSocketAddress; 
 
import net.spy.memcached.CASValue; 
import net.spy.memcached.MemcachedClient; 
 
public class Test { 
    @SuppressWarnings("unchecked") 
    public static void main(String[] args) throws IOException { 
        MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211)); 
         
        cache.set("y", 1800, "Love"); 
 
        CASValue casValue1 = cache.gets("y"); 
        CASValue casValue2 = cache.gets("y"); 
        cache.cas("y", casValue2.getCas(), casValue2.getValue()); 
         
        System.out.println("CAS 2:\t" + casValue2.getCas()); 
        System.out.println("Value 2:\t" + casValue2.getValue()); 
         
        System.out.println("CAS 1:\t" + casValue1.getCas()); 
        System.out.println("Value 1:\t" + casValue1.getValue()); 
    } 

package com.sinosuperman.memcached;

import java.io.IOException;
import java.net.InetSocketAddress;

import net.spy.memcached.CASValue;
import net.spy.memcached.MemcachedClient;

public class Test {
 @SuppressWarnings("unchecked")
 public static void main(String[] args) throws IOException {
  MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));
 
  cache.set("y", 1800, "Love");

  CASValue casValue1 = cache.gets("y");
  CASValue casValue2 = cache.gets("y");
  cache.cas("y", casValue2.getCas(), casValue2.getValue());
 
  System.out.println("CAS 2:\t" + casValue2.getCas());
  System.out.println("Value 2:\t" + casValue2.getValue());
 
  System.out.println("CAS 1:\t" + casValue1.getCas());
  System.out.println("Value 1:\t" + casValue1.getValue());
 }
}

运行结果:

2011-12-18 23:07:14.528 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/127.0.0.1:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue 
2011-12-18 23:07:14.541 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for
CAS 2:  11 
Value 2:    Love 
CAS 1:  11 
Value 1:    Love

 

1. 源程序


package com.sinosuperman.memcached; 
 
import java.io.IOException; 
import java.net.InetSocketAddress; 
 
import net.spy.memcached.CASResponse; 
import net.spy.memcached.CASValue; 
import net.spy.memcached.MemcachedClient; 
 
 
public class Test { 
     
    private static MemcachedClient client = null; 
     
    static { 
        try { 
            client = new MemcachedClient(new InetSocketAddress("localhost", 11211)); 
        } catch (IOException o) { 
        } 
    } 
 
    public static void main(String[] args) throws Exception { 
         
        client.set("numberss", 1800, 1); 
         
        Test testObj = new Test(); 
        for (int i = 0; i < 10; i++) { 
            testObj.new ThreadTest("Thread-" + (i + 1)).start(); 
        } 
    } 
     
    private class ThreadTest extends Thread { 
         
        private  MemcachedClient client = null; 
        ThreadTest(String name) throws IOException { 
            super(name); 
            client = new MemcachedClient(new InetSocketAddress("localhost", 11211)); 
        } 
         
        public void run() { 
            int i = 0; 
            int success = 0; 
            while (i < 10) { 
                i++; 
                CASValue<Object> uniqueValue =client.gets("numberss"); 
                CASResponse response = client.cas("numberss",    
                 uniqueValue.getCas(), (Integer)uniqueValue.getValue() + 1); 
 
                if (response.toString().equals("OK")) { 
                    success++; 
                } 
                 
                if (i == 10) 
                System.out.println(Thread.currentThread().getName() + " " +  i  
                  + " time " + " update oldValue : " + uniqueValue.getValue()  
                  +  " , result : " + response); 
            } 
             
            if (success < 10) { 
                Count.incr(10 - success); 
                System.out.println("Test counter: " + Count.get()); 
            } 
        } 
    } 
     
    public static class Count { 
        private static int counter = 0; 
        public static void incr(int x) { 
            counter += x; 
        } 
        public static int get() { 
            return counter; 
        } 
    } 

package com.sinosuperman.memcached;

import java.io.IOException;
import java.net.InetSocketAddress;

import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;
import net.spy.memcached.MemcachedClient;


public class Test {
 
private static MemcachedClient client = null;
 
static {
  try {
   client = new MemcachedClient(new InetSocketAddress("localhost", 11211));
  } catch (IOException o) {
  }
 }

 public static void main(String[] args) throws Exception {
 
  client.set("numberss", 1800, 1);
 
  Test testObj = new Test();
  for (int i = 0; i < 10; i++) {
   testObj.new ThreadTest("Thread-" + (i + 1)).start();
  }
 }
 
private class ThreadTest extends Thread {
 
  private  MemcachedClient client = null;
  ThreadTest(String name) throws IOException {
   super(name);
   client = new MemcachedClient(new InetSocketAddress("localhost", 11211));
  }
 
  public void run() {
   int i = 0;
   int success = 0;
   while (i < 10) {
    i++;
    CASValue<Object> uniqueValue =client.gets("numberss");
    CASResponse response = client.cas("numberss",  
                 uniqueValue.getCas(), (Integer)uniqueValue.getValue() + 1);

    if (response.toString().equals("OK")) {
     success++;
    }
   
    if (i == 10)
    System.out.println(Thread.currentThread().getName() + " " +  i
                  + " time " + " update oldValue : " + uniqueValue.getValue()
                  +  " , result : " + response);
   }
  
   if (success < 10) {
    Count.incr(10 - success);
    System.out.println("Test counter: " + Count.get());
   }
  }
 }
 
public static class Count {
  private static int counter = 0;
  public static void incr(int x) {
   counter += x;
  }
  public static int get() {
   return counter;
  }
 }
}

 

2. 输出结果:

Thread-1 10 time  update oldValue : 14 , result : EXISTS 
Test counter: 6 
Thread-2 10 time  update oldValue : 14 , result : EXISTS 
Test counter: 15 
Thread-3 10 time  update oldValue : 17 , result : EXISTS 
Test counter: 22 
Thread-5 10 time  update oldValue : 17 , result : EXISTS 
Test counter: 27 
Thread-4 10 time  update oldValue : 20 , result : OK 
Test counter: 33 
Thread-8 10 time  update oldValue : 27 , result : OK 
Test counter: 36 
Thread-6 10 time  update oldValue : 28 , result : EXISTS 
Test counter: 43 
Thread-10 10 time  update oldValue : 31 , result : EXISTS 
Test counter: 52 
Thread-9 10 time  update oldValue : 31 , result : OK 
Test counter: 60 
Thread-7 10 time  update oldValue : 35 , result : OK 
Test counter: 65 
Thread-1 10 time  update oldValue : 14 , result : EXISTS
Test counter: 6
Thread-2 10 time  update oldValue : 14 , result : EXISTS
Test counter: 15
Thread-3 10 time  update oldValue : 17 , result : EXISTS
Test counter: 22
Thread-5 10 time  update oldValue : 17 , result : EXISTS
Test counter: 27
Thread-4 10 time  update oldValue : 20 , result : OK
Test counter: 33
Thread-8 10 time  update oldValue : 27 , result : OK
Test counter: 36
Thread-6 10 time  update oldValue : 28 , result : EXISTS
Test counter: 43
Thread-10 10 time  update oldValue : 31 , result : EXISTS
Test counter: 52
Thread-9 10 time  update oldValue : 31 , result : OK
Test counter: 60
Thread-7 10 time  update oldValue : 35 , result : OK
Test counter: 65

3. 分析

我们可以看到,未成功的次数最终为65,数据值最终为35,两者的和刚好是100,正好符合我们的实验结果预期。

1. Memcached是什么?

Memcached是分布式的内存对象缓存系统。


2. Memcached的基本数据结构是什么?
Memcached是基于Key/Value对的HashMap。每一对,都可以设定过期时间。


3. Memcached用什么实现?
服务端程序由C语言编写,客户端可以用任何语言编写。客户端通过Memcached协议与服务端通信。


4. Memcached特点
(1)无备份/冗余:
各Memcached节点的数据之间没有互相备份,一旦某个节点挂掉,缓存中的数据就会丢失。


5. 开发团队
Memcached由Danga Interactive开发。


6. 相关下载
(1)Memcached服务端程序
http://memcached.org/


(2)Memcached的客户端程序
http://code.google.com/p/memcached/wiki/Clients


(3)Memcached可视化管理系统(PHP和jQuery编写)


http://www.junopen.com/memadmin


7. 如何理解Memcached的分布式特点?

Memcached Server并不具有分布式特征,每个Server都是独立运行的,各Server之间不存在通信获知其他节点状态和数据备份的功能。那么Memcached为什么还是分布式的缓存系统呢?其实说到Memcached的分布式,是将Memcached Client结合在一起考虑的。具体的分布式策略,由Client实现。也就是说Memcached的分布式,不是系统层的,而是应用层的。

异步实时读写

在使用Memcached时,一般实时读写的场景并不多见。但多是Memcached写入后,在一定时间后才会有读操作。但是如果应用场景,是写入后瞬间即会有读操作呢?似乎没有什么特别之处,我们依然可以这样写:


注:此处使用的是spymemcached客户端。

MemcachedClient cache = new MemcachedClient(cacheServerAddr); 
cache.set("key", 3600, bigData); 
return cache.get("key"); 
MemcachedClient cache = new MemcachedClient(cacheServerAddr);
cache.set("key", 3600, bigData);
return cache.get("key");

如此写入缓存后,如果立刻就有其他客户端进行读操作,则会读取失败,因为set是异步操作(async),很可能仍还没有写入完。

一种可行的方法,是采用同步写操作。常用的set方法没有这种方式,需要采用遵守Memcached的CAS(Check And Set)协议的写操作。而这种写操作,一般是基于读取后得到CAS ID(类似于SVN中的版本ID),根据这个CAS ID来保证写入时,没有和其他写入操作产生“写重复”Conflict。因此,在我们现在所讨论的场景中,可以如下使用CAS协议:

(1)初始写入:写一个简单的初始值(set,异步操作);

(2)获取版本:使用异步方式获取CAS ID;

(3)同步写入:以同步方式写入数据,保证在读取前,已经写入结束。

MemcachedClient cache = new MemcachedClient(cacheServerAddr); 
cache.set(“key”, 3600, ""); 
long casId = cache.asyncGets("key").get().getCas(); 
cache.cas("key", casid, bigData); 
return cache.get("key"); 
MemcachedClient cache = new MemcachedClient(cacheServerAddr);
cache.set(“key”, 3600, "");
long casId = cache.asyncGets("key").get().getCas();
cache.cas("key", casid, bigData);
return cache.get("key");


 

 

 

 

 

0 0
原创粉丝点击