应对Memcached缓存失效,导致高并发查询DB的几种思路

简介: 原文地址: http://blog.csdn.net/hengyunabc/article/details/20735701 当Memcached缓存失效时,容易出现高并发的查询DB,导致DB压力骤然上升。

原文地址: 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,用户要实现的加载数据的回调接口:

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. public interface DataLoader {  
  2.     public <T> T load();  
  3. }  

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

 

[java]  view plain copy
 
  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秒都没有人访问,那么数据会过期。

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

目录
相关文章
|
10天前
|
缓存 数据库
高并发场景下,到底先更新缓存还是先更新数据库?
高并发场景下,到底先更新缓存还是先更新数据库?
|
1月前
|
存储 缓存 NoSQL
Redis是一种高性能的内存数据库,常用于高并发环境下的缓存解决方案
【6月更文挑战第18天】**Redis摘要:** 高性能内存数据库,擅长高并发缓存。数据存内存,访问迅速;支持字符串、列表等多元数据类型;具备持久化防止数据丢失;丰富命令集便于操作;通过节点集群实现数据分片与负载均衡,增强可用性和扩展性。理想的缓存解决方案。
34 1
|
28天前
|
缓存 Java Spring
SpringBoot配置第三方专业缓存技术Memcached 下载 安装 整合测试 2024年5000字详解
SpringBoot配置第三方专业缓存技术Memcached 下载 安装 整合测试 2024年5000字详解
19 0
|
1月前
|
缓存 NoSQL Java
高并发场景下缓存+数据库双写不一致问题分析与解决方案设计
高并发场景下缓存+数据库双写不一致问题分析与解决方案设计
|
2月前
|
存储 缓存 算法
高并发架构设计三大利器:缓存、限流和降级
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。
114 1
|
2月前
|
缓存 NoSQL 中间件
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?epoll、poll和select + Reactor模式
【5月更文挑战第19天】`epoll`、`poll`和`select`是Linux下多路复用IO的三种方式。`select`需要主动调用检查文件描述符,而`epoll`能实现回调,即使不调用`epoll_wait`也能处理就绪事件。`poll`与`select`类似,但支持更多文件描述符。面试时,重点讲解`epoll`的高效性和`Reactor`模式,该模式包括一个分发器和多个处理器,用于处理连接和读写事件。Redis采用单线程模型结合`epoll`的Reactor模式,确保高性能。在Redis 6.0后引入多线程,但基本原理保持不变。
47 2
|
1月前
|
canal 缓存 关系型数据库
高并发场景下,6种方案,保证缓存和数据库的最终一致性!
在解决缓存一致性的过程中,有多种途径可以保证缓存的最终一致性,应该根据场景来设计合适的方案,读多写少的场景下,可以选择采用“Cache-Aside结合消费数据库日志做补偿”的方案,写多的场景下,可以选择采用“Write-Through结合分布式锁”的方案,写多的极端场景下,可以选择采用“Write-Behind”的方案。
272 0
|
2月前
|
缓存 NoSQL Redis
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?-- Redis多线程
【5月更文挑战第21天】Redis启用多线程后,主线程负责接收事件和命令执行,IO线程处理读写数据。请求处理流程中,主线程接收客户端请求,IO线程读取并解析命令,主线程执行后写回响应。业界普遍认为,除非必要,否则不建议启用多线程模式,因单线程性能已能满足多数需求。公司实际场景中,启用多线程使QPS提升约50%,或选择使用Redis Cluster以提升性能和可用性。
37 0
|
2月前
|
NoSQL Redis 数据库
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?-- Memcache + Redis 多线程
【5月更文挑战第20天】Redis采用单线程模式以避免上下文切换和资源竞争,简化调试,且其性能瓶颈在于网络IO和内存,而非多线程。相比之下,Memcache使用多线程能更好地利用多核CPU,但伴随上下文切换和锁管理的开销。尽管Redis单线程性能不俗,6.0版本引入多线程以提升高并发下的IO处理能力。启用多线程后,Redis结合Reactor和epoll实现并发处理,提高系统性能。
50 0
|
2月前
|
消息中间件 Java Linux
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜