`

使用memcached进行并发控制(转)

    博客分类:
  • java
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。

引子

一个使用缓存进行并发控制的讨论,让我学习到成本与收益间的平衡,以及何为真正的可用性......

防止并发有多种方式,本文只涉及使用缓存memcached控制。

并发场景:

     用例:SNS系统中具有高级会员资格的人发起活动。

     业务规则:1.一个人同时只能创建一个活动。2.具有高级会员资格。

     基本流程如下:









这个流程中存在明显的并发问题,当进程A校验过会员M有资格,并且为创建过活动,但为开始执行创建操作,此时另一个进程B也进行了规则判断,顺利通过,并完成创建操作,此时A继续执行,则会产生两条M的活动。(这个并发场景很简单,很普遍)

最初的解决方案:

     计划利用memcached的add操作的原子性来控制并发,具体方式如下:

     1.申请锁:在校验是否创建过活动前,执行add操作key为memberId,如果add操作失败,则表示有另外的进程在并发的为该memberId创建活动,返回创建失败。否则表示无并发

     2.执行创建活动

     3.释放锁:创建活动完成后,执行delete操作,删除该memberId。

问题:

     如此实现存在一些问题:

     1.memcached中存放的值有有效期,即过期后自动失效,如add过M1后,M1失效,可以在此add成功

     2.即使通过配置,可以使memcached永久有效,即不设有效期,memcached有容量限制,当容量不够后会进行自动替换,即有可能add过M1后,M1被其他key值置换掉,则再次add可以成功。

     3.此外,memcached是基于内存的,掉电后数据会全部丢失,导致重启后所有memberId均可重新add。

应对问题:

     针对上述的几个问题,根本原因是add操作有时效性,过期,被替换,重启,都会是原来的add操作失效。解决该问题有两个方法:

     1.采用持久化的缓存解决方法,如TT(Tokyo Tyrant:http://fallabs.com/tokyotyrant/)

     2.减轻时效性的影响,使用memcached CAS(check and set)方式。

     第一种不必解释了,很简单,原来的所有问题都是时效性惹得祸,时效性源于memcached是基于内存的,那么采用持久话存储的TT可以彻底根治这个问题。

     第二种方式需要简单介绍下:

     memcached中除了add操作是原子的,还有另外两个操作也是原子的:incr和decr,使用CAS模式即:

     1.预先在memcached中设置一个key值,假设为CREATKEY=1

     2.每次创建活动时,在规则校验前先get出CREATEKEY=x;

     3.进行规则校验

     4.执行incr CREATEKEY操作,检验返回值是否为所期望的x+1,如果不是,则说明在此期间有另外的进程执行了incr操作,即存在并发,放弃更新。否则

     5.执行创建活动

     对比这两种方法,从效果上看可以发现第一种时100%可靠的,不存在问题;第二种,可能存在误判,即本来不存在并发,却被判为并发,如缓存重启,或key值失效后,incr值可能不同于期望值,导致误判。

     但是从成本上考虑,TT是持久化的缓存解决方案,完美意味着高成本,我们必须维护持久化数据,而使用memcached的CAS方式,可以以几乎0成本的方式解决时效性问题,尽管存在一点小缺陷,但这种缺陷可以通过简单的重试即可解决。考虑实际的产出比,采用memcached的CAS方式更适合实际情况。

     成本与收益间的平衡,做科学与做工程的区别~】




应用场景分析:

       如原来MEMCACHED中的KES的内容为A,客户端C1和客户端C2都把A取了出来,C1往准备往其中加B,C2准备往其中加C,这就会造成C1和C2执行后的CACHE KEYS要么是AB要么是AC,而不会出现我们期望的ABC。这种情况,如果不是在集群环境中,而只是单机服务器,可以通过在写CACHE KEYS时增加同步锁,就可以解决问题,可是在集群环境中,同步锁是显然解决不了问题的。

memcached是原子的吗?宏观
         所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模式,所有的命令都是原子的;命令序列不是原子的。如果您通过get命令获取了一个item,修改了它,然后想把它set回memcached,我们不保证这个item没有被其他进程(process,未必是操作系统中的进程)操作过。在并发的情况下,您也可能覆写了一个被其他进程set的item。
memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果您使用gets命令查询某个key的item,memcached会 给您返回该item当前值的唯一标识。如果您覆写了这个item并想把它写回到memcached中,您可以通过cas命令把那个唯一标识一起发送给 memcached。如果该item存放在memcached中的唯一标识与您提供的一致,您的写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该item存放在memcached中的唯一标识将会改变,您的写操作就会失败。

微观分析

memcache为了避免一些竞争,加入了一些特殊原子操作:add cas incr decr

"add" means "store this data, but only if the server *doesn't* already;

“cas” is a check and set operation which means “store this data but only if no one else has updated since I last fetched it.” ;

它们的实现原理基于CAS(check and save)模式:

下面是一个全新的CAS模式实现:

     1.预先在memcached中设置一个key值,假设为CREATKEY=1

     2.每次创建活动时,在规则校验前先get出CREATEKEY=x;

     3.进行规则校验

     4.执行incr CREATEKEY操作,检验返回值是否为所期望的x+1,如果不是,则说明在此期间有另外的进程执行了incr操作,即存在并发,放弃更新。否则

     5.执行创建活动

memcached保存的key value都有一个唯一标识casUnique,在进行incr decr操作时,首先获取casUnique,执行incr,检验返回值是否casUnique+1,如果是,则更新,否则,失败不更新!

尽管这种设计在处理并发时还存在缺陷,但可以通过简单的重试来解决问题!

接口分析:

返回MemcachedItem对象:

public MemcachedItem gets(String key) {
               return client.gets(key);
          }

public MemcachedItem gets(String key, Integer hashCode) {
                   return gets(OPCODE_GET, key, hashCode, false);
         }

普通的get方法,返回Value对象

public Object get(String key) {
              return client.get(key);
         }

casUnique:是唯一标识
        public boolean cas(String key, Object value, long casUnique) {
             return client.cas(key, value, casUnique);
        }

public boolean cas(String key, Object value, long casUnique) {
               return set(OPCODE_SET, key, value, null, null, casUnique, primitiveAsString);
          }

MemcachedItem类结构:

public final class MemcachedItem {
              public long casUnique;
               public Object value;

}

其它约束:

32位无符号整数

下面一片文章是对memcache add原子性实际应用的使用:

原文出自:http://blog.csdn.net/jiangbo_hit/article/details/6211704

引子

一个使用缓存进行并发控制的讨论,让我学习到成本与收益间的平衡,以及何为真正的可用性......

防止并发有多种方式,本文只涉及使用缓存memcached控制。

并发场景:

     用例:SNS系统中具有高级会员资格的人发起活动。

     业务规则:1.一个人同时只能创建一个活动。2.具有高级会员资格。

     基本流程如下:






这个流程中存在明显的并发问题,当进程A校验过会员M有资格,并且为创建过活动,但为开始执行创建操作,此时另一个进程B也进行了规则判断,顺利通过,并完成创建操作,此时A继续执行,则会产生两条M的活动。(这个并发场景很简单,很普遍)

最初的解决方案:

     计划利用memcached的add操作的原子性来控制并发,具体方式如下:

     1.申请锁:在校验是否创建过活动前,执行add操作key为memberId,如果add操作失败,则表示有另外的进程在并发的为该memberId创建活动,返回创建失败。否则表示无并发

     2.执行创建活动

     3.释放锁:创建活动完成后,执行delete操作,删除该memberId。

问题:

     如此实现存在一些问题:

     1.memcached中存放的值有有效期,即过期后自动失效,如add过M1后,M1失效,可以在此add成功

     2.即使通过配置,可以使memcached永久有效,即不设有效期,memcached有容量限制,当容量不够后会进行自动替换,即有可能add过M1后,M1被其他key值置换掉,则再次add可以成功。

     3.此外,memcached是基于内存的,掉电后数据会全部丢失,导致重启后所有memberId均可重新add。

应对问题:

     针对上述的几个问题,根本原因是add操作有时效性,过期,被替换,重启,都会是原来的add操作失效。解决该问题有两个方法:

     1.采用持久化的缓存解决方法,如TT(Tokyo Tyrant:http://fallabs.com/tokyotyrant/)

     2.减轻时效性的影响,使用memcached CAS(check and set)方式。

     第一种不必解释了,很简单,原来的所有问题都是时效性惹得祸,时效性源于memcached是基于内存的,那么采用持久话存储的TT可以彻底根治这个问题。

     第二种方式需要简单介绍下:

     memcached中除了add操作是原子的,还有另外两个操作也是原子的:incr和decr,使用CAS模式即:

     1.预先在memcached中设置一个key值,假设为CREATKEY=1

     2.每次创建活动时,在规则校验前先get出CREATEKEY=x;

     3.进行规则校验

     4.执行incr CREATEKEY操作,检验返回值是否为所期望的x+1,如果不是,则说明在此期间有另外的进程执行了incr操作,即存在并发,放弃更新。否则

     5.执行创建活动

     对比这两种方法,从效果上看可以发现第一种时100%可靠的,不存在问题;第二种,可能存在误判,即本来不存在并发,却被判为并发,如缓存重启,或key值失效后,incr值可能不同于期望值,导致误判。

     但是从成本上考虑,TT是持久化的缓存解决方案,完美意味着高成本,我们必须维护持久化数据,而使用memcached的CAS方式,可以以几乎0成本的方式解决时效性问题,尽管存在一点小缺陷,但这种缺陷可以通过简单的重试即可解决。考虑实际的产出比,采用memcached的CAS方式更适合实际情况。

     成本与收益间的平衡,做科学与做工程的区别~

http://blog.csdn.net/jiangbo_hit/article/details/6211704
http://zhengjunwei2007.blog.163.com/blog/static/3529794220117325112464/
分享到:
评论

相关推荐

    Memcached 分布式缓存实现原理简介

    在高并发环境下,大量的读、写请求涌向数据库,此时磁盘IO将成为瓶颈,从而导致过高的响应延迟,因此缓存应运而生。无论是单机缓存还是分布式缓存都有其适应场景和优缺点,当今存在的缓存产品也是数不胜数,最常见的...

    goRubu:一个用Go编写的URL缩短器,具有基于Mongo的后端,基于Prometheus和Grafana的监视,基于Memcached的直写式缓存。 整个应用程序也被docker化

    使用乐观并发控制和悲观并发控制,查看是否可以提高性能。 包含Api验证和日志记录中间件,以及基于Swagger的文档 为什么选择goRubu? :dog_face: 想通过构建项目来学习Go和系统设计。 因此,goRubu。 基准测试 ...

    后端开发开发技巧总结与入门常用的技巧总结.docx

    并发控制:理解并合理应用锁机制(如乐观锁、悲观锁),防止并发问题。 缓存策略:在合适的地方引入缓存机制(如Redis、Memcached)提高系统性能。 分页与批量处理:对于大数据量的处理,采用分页或流式处理,减轻...

    网站架构技术

    使用应用服务器集群改善网站的并发处理能力 问题: 负载均衡情况下session状态的保持? 解决方案: 基于DNS的负载均衡 反向代理 ngix JK2 数据库的读写分离 问题: 读库与写库的数据同步...

    lock:锁库提供PHP代码的序列化执行

    如果将ext-redis用于锁定并配置为使用igbinary进行序列化或使用lzf进行压缩,则还必须安装ext-igbinary和/或ext-lzf 。 安装 作曲家 要通过composer使用此库,请在存储库的根文件夹中运行以下终端命令。 ...

    决战Nginx系统卷:高性能Web服务器详解与运维第二部分(保证能用)

    第12章 限制用户并发连接数 第13章 修改或隐藏Nginx的版本号 第14章 配置FLV服务器 第15章 Nginx的访问控制 第16章 提供FTP下载 第17章 Nginx与编码 第18章 网页压缩传输 第19章 控制Nginx如何记录日志 第20...

    决战Nginx: 系统卷 - 高性能Web服务器详解与运维第三部分(保证能用)

    第12章 限制用户并发连接数 第13章 修改或隐藏Nginx的版本号 第14章 配置FLV服务器 第15章 Nginx的访问控制 第16章 提供FTP下载 第17章 Nginx与编码 第18章 网页压缩传输 第19章 控制Nginx如何记录日志 第20...

    决战Nginx系统卷:高性能Web服务器详解与运维第一部分(保证能用)

    第12章 限制用户并发连接数 第13章 修改或隐藏Nginx的版本号 第14章 配置FLV服务器 第15章 Nginx的访问控制 第16章 提供FTP下载 第17章 Nginx与编码 第18章 网页压缩传输 第19章 控制Nginx如何记录日志 第20...

    java开源包8

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    什么是NoSQL数据库?

    它需要对程序进行变更,但由于使用廉价的服务器,可以控制成本。另外,以后只要依葫芦画瓢增加廉价服务器的数量就可以了。 不对大量数据进行处理的话就没有使用的必要吗? NoSQL数据库基本上来说为了“使大量数据...

    dubbo技术介绍

    并发控制 连接控制 延迟连接 粘滞连接 令牌验证 路由规则 配置规则 服务降级 优雅停机 主机绑定 日志适配 访问日志 服务容器 Reference Config缓存 分布式事务13-1-13 U serG uide-zh -D ubbo -A libaba O pen S ...

    java开源包1

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包11

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包2

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包3

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包6

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包5

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包10

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包4

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    java开源包7

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (<jcaptcha:image label="Type the text "/> ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

Global site tag (gtag.js) - Google Analytics