原文地址: http://blog.csdn.net/hengyunabc/article/details/20735701

当Memcached缓存失效时,容易出现高并发的查询DB,导致DB压力骤然上升。

这篇blog主要是探讨如何在缓存将要失效时,及时地更新缓存,而不是如何在缓存失效之后,如何防止高并发的DB查询。

个人认为,当缓存将要失效时,及时地把新的数据刷到memcached里,这个是解决缓存失效瞬间高并发查DB的最好方法。那么如何及时地知道缓存将要失效?

解决这个问题有几种思路:

比如一个key是aaa,失效时间是30s。

1.定期从DB里查询数据,再刷到memcached里

这种方法有个缺点是,有些业务的key可能是变化的,不确定的。

而且不好界定哪些数据是应该查询出来放到缓存中的,难以区分冷热数据。

2.当缓存取到为null时,加锁去查询DB,只允许一个线程去查询DB

这种方式不太靠谱,不多讨论。而且如果是多个web服务器的话,还是有可能有并发的操作。

3.在向memcached写入value时,同时写入当前机器在时间作为过期时间

当get得到数据时,如果当前时间 - 过期时间 > 5s,则后台启动一个任务去查询DB,更新缓存。

当然,这里的后台任务必须保证同一个key,只有一个线程在执行查询DB的任务,不然这个还是高并发查询DB。

缺点是要把过期时间和value合在一起序列化,取出数据后,还要反序列化。很不方便。

网上大部分文章提到的都是前面两种方式,有少数文章提到第3种方式。下面提出一种基于两个key的方法:

4.两个key,一个key用来存放数据,另一个用来标记失效时间

比如key是aaa,设置失效时间为30s,则另一个key为expire_aaa,失效时间为25s。

在取数据时,用multiget,同时取出aaa和expire_aaa,如果expire_aaa的value == null,则后台启动一个任务去查询DB,更新缓存。和上面类似。

对于后台启动一个任务去查询DB,更新缓存,要保证一个key只有一个线程在执行,这个如何实现?

对于同一个进程,简单加锁即可。拿到锁的就去更新DB,没拿到锁的直接返回。

对于集群式的部署的,如何实现只允许一个任务执行?

这里就要用到memcached的add命令了。

add命令是如果不存在key,则设置成功,返回true,如果已存在key,则不存储,返回false。

当get expired_aaa是null时,则add expired_aaa 过期时间由自己灵活处理。比如设置为3秒。

如果成功了,再去查询DB,查到数据后,再set expired_aaa为25秒。set aaa 为30秒。

综上所述,来梳理下流程:

比如一个key是aaa,失效时间是30s。查询DB在1s内。

  • put数据时,设置aaa过期时间30s,设置expire_aaa过期时间25s;
  • get数据时,multiget  aaa 和 expire_aaa,如果expired_aaa对应的value != null,则直接返回aaa对应的数据给用户。如果expire_aaa返回value == null,则后台启动一个任务,尝试add expire_aaa,并设置超时过间为3s。这里设置为3s是为了防止后台任务失败或者阻塞,如果这个任务执行失败,那么3秒后,如果有另外的用户访问,那么可以再次尝试查询DB。如果add执行成功,则查询DB,再更新aaa的缓存,并设置expire_aaa的超时时间为25s。

5. 时间存到Value里,再结合add命令来保证只有一个线程去刷新数据

update:2014-06-29

最近重新思考了下这个问题。发现第4种两个key的办法比较耗memcached的内存,因为key数翻倍了。结合第3种方式,重新设计了下,思路如下:

  • 仍然使用两个key的方案:

key

__load_{key}

其中,__load_{key} 这个key相当于一个锁,只允许add成功的线程去更新数据,而这个key的超时时间是比较短的,不会一直占用memcached的内存

  • 在set 到Memcached的value中,加上一个时间,(time, value),time是memcached上的key未来会过期的时间,并不是当前系统时间。
  • 当get到数据时,检查时间是否快要超时: time - now < 5 * 1000,假定设置了快要超时的时间是5秒。

* 如果是,则后台启动一个新的线程:
 *     尝试 add __load_{key},
 *     如果成功,则去加载新的数据,并set到memcached中。

*  原来的线程直接返回value给调用者。

按上面的思路,用xmemcached封装了下:

DataLoader,用户要实现的加载数据的回调接口:

  1. public interface DataLoader {
  2. public <T> T load();
  3. }

RefreshCacheManager,用户只需要关心这这两个接口函数:

  1. public class RefreshCacheManager {
  2. static public <T> T tryGet(MemcachedClient memcachedClient, final String key, final int expire, final DataLoader dataLoader);
  3. static public <T> T autoRetryGet(MemcachedClient memcachedClient, final String key, final int expire, final DataLoader dataLoader);
  4. }

其中autoRetryGet函数如果get到是null,内部会自动重试4次,每次间隔500ms。

RefreshCacheManager内部自动处理数据快过期,重新刷新到memcached的逻辑。

详细的封装代码在这里:https://gist.github.com/hengyunabc/cc57478bfcb4cd0553c2

总结:

我个人是倾向于第5种方式的,因为很简单,直观。比第4种方式要节省内存,而且不用mget,在使用memcached集群时不用担心出麻烦事。

这种两个key的方式,还有一个好处,就是数据是自然冷热适应的。如果是冷数据,30秒都没有人访问,那么数据会过期。

如果是热门数据,一直有大流量访问,那么数据就是一直热的,而且数据一直不会过期。

应对Memcached缓存失效,导致高并发查询DB的几种思路的更多相关文章

  1. 应对Memcached缓存失效,导致高并发查询DB的四种思路(l转)

    当Memcached缓存失效时,容易出现高并发的查询DB,导致DB压力骤然上升. 这篇blog主要是探讨如何在缓存将要失效时,及时地更新缓存,而不是如何在缓存失效之后,如何防止高并发的DB查询. 解决 ...

  2. memcached缓存失效时的高并发访问问题解决

    memcached一般用于在访问一些性能相对低下的数据接口时(如数据库),为了保证这些数据接口的稳定性,加上memcached以减少访问次数,保证这些数据接口的健壮性.一般memcached的数据都是 ...

  3. PHP解决抢购、秒杀、抢楼、抽奖等阻塞式高并发库存防控超量的思路方法

    如今在电商行业里,秒杀抢购活动已经是商家常用促销手段.但是库存数量有限,而同时下单人数超过了库存量,就会导致商品超卖甚至库存变负数的问题. 又比如:抢购火车票.论坛抢楼.抽奖乃至爆红微博评论等也会引发 ...

  4. 解决EFCore缓存机制导致的数据查询错误问题

    如题,在对同一个Context连续进行相同条件的查询时,会触发EFCore的缓存机制,如果这个过程中数据发生了变化,则会出现错误. 例如:有两个Context实例,一个负责查询,一个负责增删改, A_ ...

  5. 解决new Thread().Start导致高并发CPU 100%的问题

    背景 之前接手一个项目的时候,发现到处是 new Thread(()=>{ //do something }).Start(); 这么做的目的,无非是为了减少页面等待时间提高用户体验,把一些浪费 ...

  6. [转]高并发访问下避免对象缓存失效引发Dogpile效应

    避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并 ...

  7. 【高并发】高并发环境下构建缓存服务需要注意哪些问题?我和阿里P9聊了很久!

    写在前面 周末,跟阿里的一个朋友(去年晋升为P9了)聊了很久,聊的内容几乎全是技术,当然了,两个技术男聊得最多的话题当然就是技术了.从基础到架构,从算法到AI,无所不谈.中间又穿插着不少天马行空的想象 ...

  8. java亿级流量电商详情页系统的大型高并发与高可用缓存架构实战视频教程

    亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 完整高清含源码,需要课程的联系QQ:2608609000 1[免费观看]课程介绍以及高并发高可用复杂系统中的缓存架构有哪些东西2[免费观看]基于 ...

  9. Java高并发--缓存

    Java高并发--缓存 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 在下图中每一个部分都可以使用缓存的技术. 缓存的特征 缓存命中:直接通过缓存获取到数据 命中率: ...

随机推荐

  1. 网络管理 SNMP基础知识

    SNMP Agent快速开发   网友:SmileWolf 发布于: 2007.08.02 16:06 (共有条评论) 查看评论 | 我要评论                   摘自  http:/ ...

  2. csu 1803(余数分类)

    1803: 2016 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 565  Solved: 364[Submit][Status][Web Board ...

  3. scala学习笔记4--scala的函数一

    默认值: def sayMyName(name : String = "Jack"){ println(name) } 可变参数: def sumMoreParameters(el ...

  4. linux java环境变量设置

    下载JRE或者JDK后解压,设置以下环境变量 JAVA_HOME=/home/zm/jdk1.8.0_181JRE_HOME=/home/zm/jdk1.8.0_181/jreCLASSPATH=.: ...

  5. Java 8 对 List<List<String>> 排序

    Java 8 对 List<List> 排序 import java.util.ArrayList; import java.util.List; import java.util.str ...

  6. 【WPF】奇怪的INotifyPropertyChanged的实现

    MSDN是这样解释的: INotifyPropertyChanged 接口用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知. 例如,考虑一个带有名为 FirstName 属性的 Per ...

  7. 长沙理工大学第十二届ACM大赛-重现赛 G - 跑路ing

    题目描述 vigoss18 辞职成功终于逃出了公司,但是没过太久,公司就发现vigoss18 的所作所为,于是派人来把他抓 回去. vigoss18 必须一直跑路,躲避公司的围捕.可以抽象的看成一个有 ...

  8. 转载:tar命令批量解压方法总结

    由于linux的tar命令不支持批量解压,所以很多网友编写了好多支持批量解压的shell命令,收集了一下,供大家分享: 第一:for tar in *.tar.gz;  do tar xvf $tar ...

  9. linux——(7)了解shell

    概念一:什么是shell 用户用来和内核交互的东西,他相当一个接口,我们通过这个接口调用别的程序,然后他调用内核来帮我们完成程序功能.shell有很多种类,他们之间有部分区别,Linux默认使用bas ...

  10. javascript的优缺点和内置对象

    1)优点:简单易用,与Java有类似的语法,可以使用任何文本编辑工具编写,只需要浏览器就可执行程序,并且事先不用编译,逐行执行,无需进行严格的变量声明,而且内置大量现成对象,编写少量程序可以完成目标: ...