什么是cas
Memcached于1.2.4版本新增CAS协议,类同于Java并发包中CAS(Compare and Set)原子操作,用来处理同一item被多个线程更改过程的并发问题.
基本原理
基本原理非常简单,简而言之就是”版本号”.每个存储的数据对象都有一个版本号.在Memcached中,每个key关联有一个64bit长度的long型唯一数值,表示该key对应value的版本号.
这个数值由Memcached产生,从1开始,且同一Memcached不会重复,在两种情况下这个版本数值会加,即新增与更新,而删除item版本值不会减小.
未使用cas协议
- A取出数据对象x保存至本地
- B取出数据对象x保存至本地
- B修改x为数据对象y,并将其放入缓存
- A修改x为数据对象z,并将其放入缓存
- B想取回对象,发现不是y而是z,此处会发生数据写入冲突.
使用cas协议
- A取出数据对象x,并获取到CAS-ID
- B取出数据对象x,并获取到CAS-ID
- B修改数据对象y,在写入缓存前,发现CAS-ID与缓存空间中该数据的CAS-ID是一致,就将修改后的带有CAS-ID2的y写入到缓存.
- A修改数据对象z,在写入缓存前,发现CAS-ID与缓存空间中该数据的CAS-ID2不一致,则拒绝写入,返回存储失败.
示例代码
这里我们没有使用常用的spymemcached,而是使用更稳定,性能更好的java-memcached-client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public static void main(String[] args) { String[] serverlist = {"localhost:11211"}; // 初始化memcached SockIOPool pool = SockIOPool.getInstance("test"); pool.setServers(serverlist); pool.setInitConn(5); pool.setMinConn(5); pool.setMaxConn(50); pool.setNagle(false); pool.initialize(); // 获取实例 MemCachedClient mc = new MemCachedClient("test"); String key = "abc"; out.println("store:" + mc.set(key, "test string")); Bench b1 = new Bench(key, 2000); Bench b2 = new Bench(key, 5000); b1.start(); b2.start(); } private static class Bench extends Thread { String key; long sleep; public Bench(String key, long sleep) { this.key = key; this.sleep = sleep; } @Override public void run() { MemCachedClient mc = new MemCachedClient("test"); // 此方法不同于get方法 获取MemcachedItem对象 MemcachedItem item = mc.gets(key); out.println( Thread.currentThread().getName() + " value:" + item.getValue() + " cas:" + item.getCasUnique()); try { Thread.sleep(sleep); } catch (InterruptedException e) { e.printStackTrace(); } out.println(mc.cas(key, item.getValue() + String.valueOf(sleep), item.getCasUnique())); } } |
当然,默认情况下如果出现写入冲突,memcached是不会将最后的值写入,所以如果需要保证强一致性,还是需要使用do while循环,保证最后的值一定被更新至memcached中.