美文网首页
通过双key解决缓存并发问题

通过双key解决缓存并发问题

作者: IT人的职场进阶 | 来源:发表于2020-01-05 22:24 被阅读0次

​我们在使用缓存的时候,不管Redis或者是Memcached,基本上都会遇到以下3个问题:缓存穿透、缓存并发、缓存集中失效。这篇文章主要针对【缓存并发】问题展开讨论,并给出具体的解决方案。

1.什么是缓存并发?

在高并发的访问下,当某个缓存处于过期失效的时间点时,极有可能出现多个进程同时查询该缓存(该缓存是业务场景中非常 "热点" 的数据,比如首页的缓存数据)。因为查询DB并重新缓存需要一定的时间,而瞬时并发非常高,如果此时缓存失效了,这些并发请求都会直接访问DB,从而导致DB服务器的CPU或者内存负载过高,服务能力下降甚至宕机,此问题即缓存并发问题。

缓存并发问题在微服务架构下凸显更加严重,比如某个基础服务A因为上述问题出现不可用,进而导致依赖A服务的B、C服务也不可用,而B服务的不可用又导致服务E、F不可用,不可用的服务就像滚雪球一样越滚越大,最终导致系统出现严重故障,此现象我们称之为雪崩效应。

注意缓存并发和缓存集中失效的区别在于:缓存并发指的是某一个热点key的失效,而缓存集中失效则是一批key同时失效,两者都可能导致雪崩问题。

2.如何解决?

针对该问题,存在以下三种解决方案:

1、加锁:在缓存失效后,通过加锁的方式只允许一个线程查询数据和写缓存,其他线程如果发现有锁就等待,等解锁后再返回数据。该方案会造成部分请求等待。

2、二级缓存:A1为原始缓存,A2为拷贝缓存。A1失效时,可以访问A2,其中A1的缓存失效时间设置为短期(比如5min),A2的缓存失效时间设置为长期(比如1天)。如果缓存value很大,此方案的缓存空间利用率低。

3、双key:思路和方案2类似,不同的是双key分别缓存过期时间(key-time)和缓存数据(key-data),其中(key-time)的缓存失效时间设置为短期(比如5min),(key-data)的缓存失效时间设置为长期(比如1天)。当第一个线程发现 key-time 过期不存在时,则先更新key-time,然后去查询数据库并更新key-data 的值;当其他线程来获取数据时,虽然第一个线程还没有从数据库查询完毕并更新缓存,但发现key-time存在,会直接读取缓存的旧数据返回。和二级缓存的方案对比,该方案的缓存空间利用率高。

3.双key方案的示例代码

1. 写缓存的示例代码

public static boolean set(String key, String value, int seconds) {
    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();
        if (seconds > 0){
            // 添加数据缓存,缓存有效时间 = 真实时间 + 1 天
            jedis.set(key, seconds + 60 * 60 * 24, value);

            // 添加过期时间缓存,缓存有效时间 = 真实时间
            jedis.set("lock_" + key, seconds, System.currentTimeMillis() + "");
        } else {
            jedis.set(key, value);
            jedis.set("lock_" + key, System.currentTimeMillis() + "");
        }
        return true;
    } catch (JedisException e) {
        if (jedis != null) {
            returnBrokenResource(jedis);
            jedis = null;
        }
        throw e;
    } finally {
        if (jedis != null) {
            returnResource(jedis);
        }
    }
}

2. 读缓存的示例代码

public static String get(String key) {
    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();

        // 缓存过期 && 获取锁成功,setnx:原子操作
        if (jedis.setnx("lock_" + key, System.currentTimeMillis() + "") == 1) {
            /**
             * 将锁的失效时间设为60s,在60s内若查询数据库成功,则更新锁的失效时间=缓存时间
             * 如果60s内出现异常,则60s后第一个请求又会去访问数据库
             * 返回null表示没有查询到数据库,外层代码会通过数据库获取数据并设置缓存
             */
             jedis.expire("lock_" + key, 60);
             return null;
         } else{
             // 缓存未过期或者缓存过期但获取锁失败, 则返回旧数据
             return jedis.get(key);
         }
    } catch (JedisException e) {
            if (jedis != null) {
                returnBrokenResource(jedis);
                jedis = null;
            } 
            throw e;
    } finally {
            if (jedis != null) {
                returnResource(jedis);
            }
    }
}

其他推荐:
1、线程池运用不当的一次线上事故
2、工程师如何从技术转型做管理?

作者简介:程序员,985硕士,前亚马逊Java工程师,现58转转技术总监。持续分享技术和管理方向的文章。如果感兴趣,可微信扫描下面的二维码关注我的公众号:『IT人的职场进阶』

相关文章

  • 通过双key解决缓存并发问题

    ​我们在使用缓存的时候,不管Redis或者是Memcached,基本上都会遇到以下3个问题:缓存穿透、缓存并发、缓...

  • 缓存并发神技,如何通过双 key 来解决缓存并发问题?

    我们在使用缓存的时候,不管Redis或者是Memcached,基本上都会遇到以下3个问题:缓存穿透、缓存并发、缓存...

  • 面试Redis——缓存并发 缓存雪崩 缓存穿透

    本文主要叙述缓存并发,缓存雪崩,缓存穿透的问题以及解决方案。 缓存并发 什么是缓存并发 场景:在你每天刷抖音,看微...

  • 浅谈缓存架构

    缓存集群 缓存集群存在的问题 1.热key缓存集群中的某个key瞬间被数万甚至十万的并发请求打爆。2.大value...

  • 缓存穿透、并发和雪崩那些事

    0 题记 缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本文讲解其产生原因和解决方案。 缓存穿...

  • 缓存穿透、并发和雪崩那些事

    0 题记 缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本文讲解其产生原因和解决方案。 缓存穿...

  • 缓存穿透、并发和雪崩那些事

    0 题记 缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本文讲解其产生原因和解决方案。 缓存穿...

  • 什么是缓存击穿、缓存并发和缓存雪崩?

    缓存击穿、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本节讲解其产生原因和解决方案。 缓存击穿通常是由...

  • Redis-3大常见问题

    缓存常见问题 by shihang.mai 雪崩 大量的key同时失效,高并发请求导致压力全部到库。叫缓存雪崩。 ...

  • 缓存问题记录

    缓存击穿问题 概念:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存...

网友评论

      本文标题:通过双key解决缓存并发问题

      本文链接:https://www.haomeiwen.com/subject/nyacactx.html