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");