• 缓存穿透 & 缓存雪崩 & 缓存击穿


    一 缓存穿透

    1. 行为

    查询一个一定不存在的数据。存储层(姑且认为是db,下面都用db指代)查不到数据则不写入缓存,那么下次请求这个不存在的数据同样会到db层查询,失去了缓存的意义。流量大或人为恶意攻击可能会使db宕掉。

    2. 解决方案

    (1) 布隆过滤器。将全量可能存在的数据哈希到一个足够大的bitmap中,布隆可能误报,但绝不会漏报,那么一定不存在的数据会被拦截掉,从而缓解了对db的压力

    (2) 空结果也进入缓存。如果查询返回的结果为空 (数据不存在 | 服务不可用), 仍将数据-空结果进行缓存,注意将其过期时间设置非常短(不超过5min)

    二 缓存雪崩

    1. 行为

    设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时实效,请求全部打到db,db瞬间压力过重雪崩。

    2. 解决方案

    (1) 加锁或采用队列保证缓存的单线程,避免失效时大量请求落到db存储系统

    (2) 缓存时间离散化。在原缓存的失效时间基础上增加一个随机值,降低同一时间集体失效概率

    三 缓存击穿

    1. 行为

    对于设置了过期时间的某些key,在过期的时间点,恰好对这个key有大量的并发请求过来,这些请求发现缓存过期同时请求db加载数据并回设到缓存,这个高并发的请求可能瞬间把后端db压垮。

    2.解决方案

    (1) 永不过期

    (2) 使用互斥锁。缓存失效的时候,不直接load db,而是使用缓存工具中带有成功返回标识的方法(比如redis的setnx,memcache的add)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存。否则重试整个get缓存的方法。

    在redis2.6.1之前版本未实现setnx的过期时间,所以给出两种版本代码参考

    a) setnx无过期时间版本:

     1 String get(String key) {    
     2    String value = redis.get(key);    
     3    if (value  == null) {    
     4     if (redis.setnx(key_mutex, "1")) {    
     5         // 3 min timeout to avoid mutex holder crash    
     6         redis.expire(key_mutex, 3 * 60)    
     7         value = db.get(key);    
     8         redis.set(key, value);    
     9         redis.delete(key_mutex);    
    10     } else {    
    11         //其他线程休息50毫秒后重试    
    12         Thread.sleep(50);    
    13         get(key);    
    14     }    
    15   }    
    16 }  

    b) redis2.6.1后, setnx有过期时间版本:

     1 public String get(key) {  
     2       String value = redis.get(key);  
     3       if (value == null) { //代表缓存值过期  
     4           //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db  
     5           if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功  
     6                value = db.get(key);  
     7                redis.set(key, value, expire_secs);  
     8                redis.del(key_mutex);  
     9               } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可  
    10                   sleep(50);  
    11                   get(key);  //重试  
    12               }  
    13           } else {  
    14               return value;        
    15           }  
    16  }  
  • 相关阅读:
    【转】Java并发编程:深入剖析ThreadLocal
    【转】关于Java的Daemon线程的理解
    【转】详细分析Java中断机制
    【转】Java并发编程注意事项
    【转】Java并发编程:volatile关键字解析
    【转】Java并发编程:Lock
    【转】JVM运行原理及JVM中的Stack和Heap的实现过程
    【转】Linux常用命令大全
    Linux 命令学习
    js中的prototype和__proto__
  • 原文地址:https://www.cnblogs.com/balfish/p/8270112.html
Copyright © 2020-2023  润新知