转自:www.open-open.com,非原创

memcached是一款高性能的分布式缓存系统,凭借其简单方便的操作,稳定可靠的性能广泛应用于互联网应用中,网上关于memcached介绍的资料也很多,最经典的资料就是《memcached全面剖析》这个文档,原文链接:http://gihyo.jp/dev/feature/01/memcached/0001,中文翻译网上很多:http://tech.idv2.com/2008/08/17/memcached-pdf/,这个文档写的很好,也很容易读懂。接下来我主要去总结一些常见应用场景问题以及解决办法。

1. 缓存的存储设计

按应用场景的不同一般有以下两种设计方案:

  • 方案一:把数据库的SQL查询结果缓存到memcached,读取数据的时候优先从memcached读取,挡住数据库查询请求。

    优点:我们可以在开发框架上做一些统一的缓存处理,对业务开发透明,减少对业务逻辑代码的侵入。这种情况下缓存的预热也比较方便,我们可以借助数据库的存储日志(eg:mysql的binlog)来预热缓存。

    缺点:这种方式有个隐患就是如果前端的一次请求需要涉及到多个SQL查询结果,这时候memcached需要取多次数据,在高并发的情况下网络io的开销和memcached的并发压力有可能成为瓶颈。

  • 方案二:把业务处理的最终结果进行缓存,客户端来请求时可以直接返回这个缓存的结果。

    优点:可以快速返回数据,只取一次memcache就可以了,减少了网络io消耗和运算处理消耗。

    缺点:需要在业务逻辑里显式处理缓存,同时存储的数据结构较复杂,当我们有数据更新时,重新生成缓存会比较麻烦。这种情况比较适用于计算密集型的高并发应用场景。

2. 缓存更新策略

两种常见的方案,也各有优缺点和应用场景:

  • 方案一:懒惰式加载,客户端先查询memcached,如果命中,返回结果,如果没命中(没有数据或已经过期),则从数据库中加载最新数据,并写回到memcached中,最后返回结果。

    优点:使用方便,简单;

    缺点:高并发的情况下如果缓存失效,将对后端数据库造成瞬时压力。当然,我们可以在应用里加锁来控制并发,但是这样也会对应用程序造成影响。

  • 方案二:主动更新策略,缓存里的数据永不失效,当有数据更新的时候,由单独程序来更新这个缓存。

    优点:缓存数据总是可靠的(没有LRU的情况下),前端可以快速响应,后端的数据库也没有并发查询的压力。

    缺点:程序结构变复杂了,需要维护单独的程序来完成更新,两套程序要共享一套缓存配置。(ps:其实有一些业务场景本来就是这样的,比如门户网站的内容发布系统和网站系统就需要共享一份数据,一个负责写数据,一个负责展示数据)

3. 批量删除(或更新)问题

在memcached中,我们的绝大部分操作都是基于单个key的add/set/del/get操作,用起来很方便,但是呢,有些时候我们会碰到批量删除(或更新)的问题。比如某手机App应用因为出现了敏感内容,网络监管部门要求删除所有跟这条内容有关的信息,这个时候因为手机机型、版本不同,这个内容在缓存里的key有多种多样。我们不能方便地拿到所有的key,或者可以枚举出所有的key,但是memcached并不支持批量删除操作,这就麻烦了,怎么解决这种问题呢?下面我以某门户网站删除敏感新闻来举例,我们假设每条新闻都有很多维度的内容,新闻以newsid标识,每个维度以prop 来老相识,再加一个通用前缀,这样,完整的key应该是这样的格式:key{newsid}{prop}

  • 方案一:

    用一个单独的集合(Set)把一类key维护起来。当需要批量删除(或更新)时只需要取出这个集合里的所有key进行相应的操作即可。这样做起来比较简单:

    首先,我们往memcached里面添加一个新的k,v时,就往那个set里加一个key,比如一条新闻在memcached里面有下面这些 对:

    key_{newsid}_{prop1}:value1
    key_{newsid}_{prop2}:value2
    key_{newsid}_{prop3}:value3
    ……
    key_{newsid}_{propn}:valuen

    在我们的集合里面,就要存放所有跟这条新闻有关key的集合:

    keyset_{newsid}:key_{newsid}_{prop1},key_{newsid}_{prop2},……,key_{newsid}_{propn})

    这样,当我们要清除这条新闻的缓存时,就可以取出这个key的集合,然后遍历这些key,到memcached里面逐个删除,这样就达到了批量删除的目的。

    在这里,我们提到的这个key set具体怎么存放和维护呢?

    一种方式是,在memcached里面把所有key用逗号拼接成一个大字符串构成keyset的value或者借助开发语言提供的集合结构(set)来组织数据,系列化到memcached中。

    另一种方式是,借助更方便的存储结构来保存这个key,比如redis的set结构,当然了,这种方式并不推荐,会给现有系统带来复杂度。

  • 方案二:

    通过动态更新key的方式来实现,这种方式是给每一个key都在原来key的基础上加一个版本号来组成,当需要批量删除或更新时只需升级版本号即可,具体怎么做呢?

    首先,我们在memcached给这条新闻维护一个版本号,这样:

    key_version_{newsid}:v1.0 (版本号可以用时间戳或其它任何有意义的内容代替)
    // 伪代码
    $memcacheClient->setVersion(key_version{newsid}, "v1.0");

    然后,当我们要保存或读取这条新闻相关的数据时,先取出这个版本号来生成新的key,如下:

    //伪代码
    $version = getVersion(key_version_{newsid});
    $key = "key_{newsid}_{prop}_" + $version;

    再用这个新的key来保存(或读取)真正的内容,这样在memcached里面保存的跟这条新闻有关的 对就是下面这样了:

    key_{newsid}_{prop1}_v1.0:value1
    key_{newsid}_{prop2}_v1.0:value2
    key_{newsid}_{prop3}_v1.0:value3
    ……
    key_{newsid}_{propn}_v1.0:valuen

    当我们需要删除(或更新)这条新闻相关的所有key时,只需要升级版本号即可,如下:

    //伪代码
    $memcacheClient->updateVersion(key_version_{newsid},"v2.0");

    这样的话,当我们下次访问这条新闻的缓存时,由于版本号升级,新的key下所有内容都为空,需要从数据库加载新的内容,或者是返回空的结果。而旧的key在过期时间到了以后也就可以回收利用了。这样就达到了我们批量删除或更新的目的。

上面提到的两种方案其实都比较简单和实用,当然也各有缺点,方案一的key set维护需要额外的消耗,方案二的老版本数据不能及时清理,造成缓存垃圾。我们在实际应用场景中可以灵活选择,两者在效果上其实不会有太大区别。

4. 故障转移和扩容的问题

memcached它不是一个分布式的系统,严格来说是个单点系统,所谓的分布式只是借助客户端来实现的。所以它没有那些开源分布式系统那样的高可用性,我们这里来讨论一下memcached怎么去避免单点故障,以及在线扩容的问题。(ps:memcached做得真省事儿,最大的特点就是简单,好多辅助功能都要依赖于客户端自己去实现)。

  • 一致性哈希:好吧,这应该算是最简单常见的一种机制了,依赖于一致性哈希的特点,节点故障或扩容加节点时对集群影响较小,基本上可以满足大部分应用场景了。但是要注意:节点调整的最初一段时间内,会有一部分缓存丢失,穿透到后端的数据库上,在高并发的应用里,要做好并发控制,以免对数据库造成压力。
  • 双写机制:客户端维护两个集群,每次更新数据的时候同时更新两份,读取的时候随机(或固定)读取一份,这种情况下集群的可用性和稳定性是很高的,可以无痛变更,节点故障或扩容对缓存和后端数据库都没有影响。当然,这样做也是有代价的:一是两份数据的一致性问题,不过对缓存来说,这种极少数的不一致情况是可以容忍的;另一个是内存浪费的问题,通过冗余一份数据来减少故障率,代价还是挺大的,并不适合大型的互联网应用。
  • Twemproxy:这是twitter开源的一个代理程序,可以给redis和memcached作代理,有了这个东西可以减少好多维护成本(主要是客户端的)。对于故障转移和在线扩容也很方便。具体可以参考:https://github.com/twitter/twemproxy

5. 与优化有关的一些小细节

  • 批量读取(multiget):有些较复杂的业务请求可能一次请求要进行多次memcached操作,其中的网络往返的消耗以及对memcached节点施加的并发压力还是比较可观的,这种情况下我们可以考虑进行批量读取来减少网络io往返的次数,一次把数据返回,同时还能减轻客户端的业务处理逻辑。

    这里有一个著名的multiget无底洞问题,在facebook的应用中发现了这个问题,请参考:http://highscalability.com/blog/2009/10/26/facebooks-memcached-multiget-hole-more-machines-more-capacit.html,这篇文章中已经提出了解决方案。但其实我们也可以考虑把multiget的key分布到一个节点上,来避免这个问题,这样就需要自己定制memcache 的客户端,按一定的规则(比如:相同的前缀)把一类key分布到同一个节点上,来避免这个问题,同时这样也可以提高性能,不用在多个节点之间等待数据。

  • 改变系列化方式:不使用java的对象序列化方式(哈哈,我这里只针对java来说),自己实现序列化,把要缓存的对象序列化成字节数组或者string进行保存。这样在内存节省和网络传输上都有不错的效果。

  • 数据预热:一些场景下我们需要为应用预热缓存数据(比如节点扩容需要重新分布数据),在前面说缓存设计的时候提出过,可以借助数据库的更新日志来预热缓存,这主要依赖于缓存的内容是跟数据库存储一致。其它情况下我们可以考虑在现有缓存前面挡一层空内容的集群节点,逐步把旧缓存读取到新缓存集群中来达到数据预热的目的,这样做就是有一点麻烦,需要应用端配合。
  • 增长因子:合理调整memcached的增长因子,可以有效控制内存的浪费。
  • 空结果的处理:有些场景下我们数据库里没有查到数据,缓存里也是空的,这时候需要在缓存里存放一个短时效的空结果来挡住前端的频繁请求,以免对数据库造成压力。

memcached的使用其实非常简单,性能也很出色,上面这些就是我们在实际业务开发中会碰到的一些场景,根据实际场景去选择合适的解决方案,可以给以后的开发维护带来不少便利。

memcached使用总结篇一相关推荐

  1. memcached客户端_分布式算法真是吊炸天 – memcached - 第287篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列 ) 色谈Java序列化:女孩子慎入 - 第280篇 烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇 双向链表,比西天还远? ...

  2. 【软件开发】Memcached(理论篇)

    Memcached(理论篇) 1.Memcached 简介 Memcached 是一个开源的,支持高性能,高并发的分布式内存缓存系统,由 C 语言编写,总共 2000 多行代码.从软件名称上看,前 3 ...

  3. 分布式算法真是吊炸天 – memcached - 第287篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列

  4. 内存泄漏的原因及解决办法_探索内存碎片化 - 第288篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列 ) 色谈Java序列化:女孩子慎入 - 第280篇 烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇 双向链表,比西天还远? ...

  5. 分布式缓存应用(转载的)

    前言 Asp.Net中使用Couchbase--Memcached缓存入门篇 见http://www.cnblogs.com/aehyok/p/3436721.html 主要讲解Couchbase服务 ...

  6. 网站架构之缓存应用(摘录)

    网站缓存这个话题并不新颖,但是能否将它用好,可是一门学问,同一件工具在不同人的手中会做出不同的事情来.这里我来分享总结下我对于网站架构中缓存应用的一些看法和经验,大家有好的想法可以补充      第一 ...

  7. 大数据的关键技术与综述

    在大数据时代,传统的数据处理方法还适用吗? 大数据环境下的数据处理需求 大数据环境下数据来源非常丰富且数据类型多样,存储和分析挖掘的数据量庞大,对数据展现的要求较高,并且很看重数据处理的高效性和可用性 ...

  8. 国内最全的Spring Boot系列之三

    历史文章 <国内最全的Spring Boot系列之一> <国内最全的Spring Boot系列之二> 马上要过年了,作者要回家好好休息一下了,吃饱喝足,明年继续.在此和大家拜个 ...

  9. 浏览器console的用法

    Leo_wlCnBlogs 自由.创新.研究.探索 Linux/Windows Mono/DotNet [ Open Source .NET Development/ 使用开源工具进行DotNet软件 ...

最新文章

  1. 终端bash美化(FC)
  2. 社会生活中常用的14条著名法则
  3. CVPR2021 | 重新思考BiSeNet让语义分割模型速度起飞
  4. Spring AOP源码分析(六)Spring AOP配置的背后
  5. linux用两种方法找到cuond,很基础的Linuxshell脚本学习.doc
  6. 线程的调度、优先级和亲缘性——Windows核心编程学习手札系列之七
  7. Php错误邮件提示linux,linux下phpmailer发送邮件出现SMTP ERROR: Failed to connect to server: (0)错误...
  8. 查看oracle的块大小,查看操作系统块大小
  9. c语言验证24点,C语言解24点游戏程序
  10. zookeeper安装_【Zookeeper】zookeeper的安装与调试
  11. Valgrind的使用方法
  12. aws rds监控慢sql_AWS RDS SQL Server –启动新的数据库实例
  13. mysql管理员_mysql怎么获得管理员权限??麻烦告诉我
  14. js日期减一个月_正正正国庆!折上再减!三亚/香格里拉/稻城/拈花湾,最低499元…...
  15. java 泛型参数具体类型获取、泛型返回具体类型获取
  16. android gamed,先游DGame
  17. China Joy 还没看够?2020 谷歌游戏出海峰会带来更多精彩!
  18. office2016安装部分组件教程
  19. 打印机设备与计算机连接类型,电脑打印机的连接方式 打印机的种类有哪些
  20. 洋码头API接口:item_search - 根据关键词取商品列表

热门文章

  1. diy 单片机 自动浇花_基于单片机的自动浇花系统
  2. Linux下解决qtcreator中不能输入中文的问题
  3. 【css】图片的内容阴影处理
  4. 软件测试自学毛笔字纹身,ps纹身教程_photoshop给人物添加纹身效果实例制作教程...
  5. 北京将超前布局6G未来网络!这场发布会,信息量很大
  6. 【opencv】19.图像边缘检测算子数学原理、像素一二阶导数的意义
  7. 短租APP开发定制快速搭建
  8. android p正式版一加6,国内首家!一加氢OS Android P正式版更新
  9. Dism 错误 32
  10. 【408】计算机组成原理第一轮强化笔记