memcached(十八)并发原语CAS与GETS操作
2017-03-12 11:13
246 查看
最近笔者自己的项目中,遇到了乐观锁的需求。但是redis没有这个操作,无奈,看了memcache天然的支持这种并发原语,即:GETS和CAS操作。因此准备打算继续使用REDIS,业务没有那么强的时序执行要求,因此可以使用没有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是否一致。结果是“不一致”,则拒绝写入,返回存储失败。
我们可以通过重试,或者其他业务逻辑解决第四步设置失败的问题。
SETNX key value
将key 的值设为value ,当且仅当key 不存在。
返回值:
设置成功,返回1。
设置失败,返回0。
最初的解决方案:
利用memcached的add操作的原子性来控制并发,具体方式如下:
1.申请锁:在校验是否创建过活动前,执行add操作key为key,如果add操作失败,则表示有另外的进程在并发的为该key创建活动,返回创建失败。否则表示无并发
2.执行创建活动
3.释放锁:创建活动完成后,执行delete操作,删除该key。
问题:
1.memcached中存放的值有有效期,即过期后自动失效,如add过M1后,M1失效,可以在此add成功
2.即使通过配置,可以使memcached永久有效,即不设有效期,memcached有容量限制,当容量不够后会进行自动替换,即有可能add过M1后,M1被其他key值置换掉,则再次add可以成功。
3.此外,memcached是基于内存的,掉电后数据会全部丢失,导致重启后所有memberId均可重新add。
解决方案
针对上述的几个问题,根本原因是add操作有时效性,过期,被替换,重启,都会使原来的add操作失效。解决该问题有方法
基本原理非常简单,一言以蔽之,就是“版本号”。每个存储的数据对象,多有一个版本号。我们可以从下面的例子来理解:
我们为什么要使用这种并发原语呢?如果是单机版的,我们可以通过通过加锁同步就可以解决执行时序的问题。但是我们的应用是分布式的,无状态的应用服务器通过负载均衡,部署到了多台。加锁也解决不了多台服务器的时序执行。
如果不采用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的方案
一般使用redis用这个算法,可以从某种程度上保证时序性。SETNX key value
将key 的值设为value ,当且仅当key 不存在。
返回值:
设置成功,返回1。
设置失败,返回0。
最初的解决方案:
利用memcached的add操作的原子性来控制并发,具体方式如下:
1.申请锁:在校验是否创建过活动前,执行add操作key为key,如果add操作失败,则表示有另外的进程在并发的为该key创建活动,返回创建失败。否则表示无并发
2.执行创建活动
3.释放锁:创建活动完成后,执行delete操作,删除该key。
if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); } }
问题:
1.memcached中存放的值有有效期,即过期后自动失效,如add过M1后,M1失效,可以在此add成功
2.即使通过配置,可以使memcached永久有效,即不设有效期,memcached有容量限制,当容量不够后会进行自动替换,即有可能add过M1后,M1被其他key值置换掉,则再次add可以成功。
3.此外,memcached是基于内存的,掉电后数据会全部丢失,导致重启后所有memberId均可重新add。
解决方案
针对上述的几个问题,根本原因是add操作有时效性,过期,被替换,重启,都会使原来的add操作失效。解决该问题有方法
1.减轻时效性的影响,使用memcached CAS(check and set)方式。
CAS的基本原理基本原理非常简单,一言以蔽之,就是“版本号”。每个存储的数据对象,多有一个版本号。我们可以从下面的例子来理解:
final GetsResponse<Object> response = memcachedClient.gets("key"); boolean flag = memcachedClient.cas("key", new CASOperation<Object>() { @Override public int getMaxTries() { //重试次数 return 1; } @Override public Object getNewValue(long currentCAS, Object currentValue) { oldCAS = response.getCas(); if (checkCAS()) {//比较CAS值 value = db.get(key); return value; }else{ //抛出异常,或者其他业务逻辑 } } }); }
相关文章推荐
- memcached(十八)并发原语CAS与GETS操作
- memcached 并发原语CAS与GETS操作
- memcached 并发原语CAS与GETS操作
- CoreData 从入门到精通(四)并发操作
- memcached-常用操作命令
- c#多线程并发执行一个操作函数
- 【Java并发编程】之十七:深入Java内存模型—内存操作规则总结
- [C++11 并发编程] 05 - Mutex 基本操作
- 《并发操作一致性问题》已全部完成
- ASP编程入门进阶(十八):FSO组件之文件操作(中)
- 一起写RPC框架(十八)RPC注册中心五--注册中心之持久化的操作
- PHP操作memcached
- 使用Simple-Spring-Memcached注解做缓存操作
- mysql并发操作引发的一些思考
- Java多线程并发锁和原子操作,你真的了解吗?
- Linux并发与同步(一)原子操作/spinlock/mutex
- 高并发 数据库操作比如插入,修改等解决办法
- 数据库大并发操作要考虑死锁和锁的性能问题
- 【实战Java高并发程序设计】5:让普通变量也享受原子操作
- ASP编程入门进阶(十八):FSO组件之文件操作(中)