
我们在使用缓存的时候,不管Redis或者是Memcached,基本上都会遇到以下3个问题:缓存穿透、缓存并发、缓存集中失效。这篇文章主要针对【缓存并发】问题展开讨论,并给出具体的解决方案。
在高并发的访问下,当某个缓存处于过期失效的时间点时,极有可能出现多个进程同时查询该缓存(该缓存是业务场景中非常 "热点" 的数据,比如首页的缓存数据)。因为查询DB并重新缓存需要一定的时间,而瞬时并发非常高,如果此时缓存失效了,这些并发请求都会直接访问DB,从而导致DB服务器的CPU或者内存负载过高,服务能力下降甚至宕机,此问题即缓存并发问题。

缓存并发问题在微服务架构下凸显更加严重,比如某个基础服务A因为上述问题出现不可用,进而导致依赖A服务的B、C服务也不可用,而B服务的不可用又导致服务E、F不可用,不可用的服务就像滚雪球一样越滚越大,最终导致系统出现严重故障,此现象我们称之为雪崩效应。
注意缓存并发和缓存集中失效的区别在于:缓存并发指的是某一个热点key的失效,而缓存集中失效则是一批key同时失效,两者都可能导致雪崩问题。
针对该问题,存在以下三种解决方案:
 1public static boolean set(String key, String value, int seconds) {
 2    Jedis jedis = null;
 3    try {
 4        jedis = jedisPool.getResource();
 5        if (seconds > 0){
 6            // 添加数据缓存,缓存有效时间 = 真实时间 + 1 天
 7            jedis.set(key, seconds + 60 * 60 * 24, value);
 8
 9            // 添加过期时间缓存,缓存有效时间 = 真实时间
10            jedis.set("lock_" + key, seconds, System.currentTimeMillis() + "");
11        } else {
12            jedis.set(key, value);
13            jedis.set("lock_" + key, System.currentTimeMillis() + "");
14         }
15
16         return true;
17    } catch (JedisException e) {
18         if (jedis != null) {
19             returnBrokenResource(jedis);
20             jedis = null;
21         }
22         throw e;
23    } finally {
24         if (jedis != null) {
25             returnResource(jedis);
26         }
27    }
28} 1public static String get(String key) {
 2    Jedis jedis = null;
 3    try {
 4        jedis = jedisPool.getResource();
 5
 6        // 缓存过期 && 获取锁成功,setnx:原子操作
 7        if (jedis.setnx("lock_" + key, System.currentTimeMillis() + "") == 1) {
 8            /**
 9             * 将锁的失效时间设为60s,在60s内若查询数据库成功,则更新锁的失效时间=缓存时间
10             * 如果60s内出现异常,则60s后第一个请求又会去访问数据库
11             * 返回null表示没有查询到数据库,外层代码会通过数据库获取数据并设置缓存
12             */
13            jedis.expire("lock_" + key, 60);
14            return null;
15        } else{
16            // 缓存未过期或者缓存过期但获取锁失败, 则返回旧数据
17            return jedis.get(key);
18        }
19    } catch (JedisException e) {
20        if (jedis != null) {
21            returnBrokenResource(jedis);
22            jedis = null;
23        } 
24        throw e;
25    } finally {
26        if (jedis != null) {
27            returnResource(jedis);
28        }
29    }
30}