万字解析Redis的三大主流问题及解决方案(二)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 万字解析Redis的三大主流问题及解决方案

2.三种问题相应的解决方案


2.1-缓存穿透解决方案


了解完上述关于缓存穿透的概念之后我们就知道了只要问题就出在数据库无法将不存在的数据存储到Redis中,导致Redis中一直没有该数据,使得关于该数据的访问全部都是直接怼到数据库上,最后导致数据库崩溃.


既然这样,我们就将该数据存储到Redis里面,这样对于该数据的访问就又重新怼到Redis上面了,但是我们要注意这条数据既然不存在,那么我们就将该数据定义为空,并且要 给它设置过期时间,并且这种国旗时间不要设置的太长,20-30秒即可,否则这种无用的数据一致存储在Redis里面,也是浪费.

public PmsSkuInfo selectBySkuId(Integer skuId) {
        PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
        //连接缓存
        Jedis jedis=redisUtil.getJedis();
        //查询缓存
        String skuKey="sku:"+skuId+":info";
        String skuJson=jedis.get(skuKey);
        //缓存不为空
        if(StringUtils.isNotBlank(skuJson)){
            //通过fastjson将我们的字符串转化成我们对应的Sku对象
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }
        else{
            //如果缓存没有,查询mysql
            pmsSkuInfo=selectBySkuIdFromDB(skuId);
            //mysql查询结果存储到Redis
            if(pmsSkuInfo!=null){
                jedis.set("sku:"+skuId+":info", JSON.toJSONString(pmsSkuInfo));
            }
            else{
                //数据库中同样也不存在该数据
                //将空值设置给该Key,并且设置30秒的过期时间
                jedis.setex("sku:"+skuId+":info",30,JSON.toJSONString(""));
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }


2.2-缓存击穿解决方案


缓存击穿的解决方案就比较复杂了,不像缓存穿透和缓存雪崩一样,只需要设置相应的过期时间或者是将数据存进Redis即可解决.


缓存击穿的解决方案相应的就比较多,主要有两种:


Redis自身的分布式锁实现

通过redisson框架实现

接下来我们分别讲一下两者的实现方式:


Redis自身的分布式锁

缓存击穿的特殊性就在于是一个热点数据突然失效,导致大规模的请求直接怼到数据库上,这其中的重点就是一条热搜数据,大规模的请求在同一时间点怼到数据库.


所以分布式锁的思想就是, 每次向Redis请求数据的时候,都在Redis里面给该条数据上锁,一旦锁设置成功,那么就只有当前的进程可以进入到数据库中进行查询,并且查询完成之后就 将该数据重新存到Redis之中,数据存储成功之后 再释放掉该锁,在此之前其他锁没有设置成功的进程就 只能自旋等待锁被释放为止.


因为第一条请求结束之后,Redis中就已经重新有了该热点数据的缓存,所以之前自旋的进程就可以 直接从Redis中拿到该热点数据,不用再去访问数据库了,这样就极大的降低了数据库的压力.


我们也可以通过下面的思维导图来帮助大家理解:


20201202110626162.png


下面是一个小的Demo:


public PmsSkuInfo selectBySkuId(Integer skuId,String ip) {
        System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName());
        PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
        //连接缓存
        Jedis jedis=redisUtil.getJedis();
        //查询缓存
        String skuKey="sku:"+skuId+":info";
        String skuJson=jedis.get(skuKey);
        //缓存不为空
        if(StringUtils.isNotBlank(skuJson)){
            System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName()+"已经成功拿到缓存中的数据");
            //通过fastjson将我们的字符串转化成我们对应的Sku对象
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }
        else{
            System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName()+"开始申请分布式锁:"+"sku:"+skuId+":lock");
            //如果缓存没有,查询mysql
            //设置分布式锁,避免缓存击穿
            String OK=jedis.set("sku:"+skuId+":lock","1","nx","px",10*1000);
            if(StringUtils.isNotBlank(OK)&&OK.equals("OK")){
                System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName()+"已经申请到分布式锁:"+"sku:"+skuId+":lock"+"过期时间为10秒");
                pmsSkuInfo=selectBySkuIdFromDB(skuId);
                try {
                    Thread.sleep(1000*7);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //mysql查询结果存储到Redis
                if(pmsSkuInfo!=null){
                    //过期时间随机避免缓存雪崩
                    jedis.setex("sku:"+skuId+":info", (int) (10*Math.random()*10),JSON.toJSONString(pmsSkuInfo));
                }
                else{
                    //数据库中同样也不存在该数据,也传到Redis中,避免缓存穿透
                    jedis.setex("sku:"+skuId+":info",30,JSON.toJSONString(""));
                }
                System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName()+"使用完毕了,释放了分布式锁:"+"sku:"+skuId+":lock");
                //在访问mysql之后,需要将分布式锁释放掉
                jedis.del("sku:"+skuId+":lock");
            }
            else{
                System.out.println("ip:"+ip+"的机器进入访问,"+"进程名称为:"+Thread.currentThread().getName()+"没有申请到分布式锁:"+"sku:"+skuId+":lock"+"已经开始自旋");
                try {
                    //进程休眠几秒之后,开始自旋
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //开始自旋
                return selectBySkuId(skuId,ip);
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }


可以看到这是测试结果:


20201202110721835.png


  • redisson框架
    引入Redisson框架之后,我们就不用上面使用Redis的分布式锁那么繁琐.直接几行代码就能搞定.
    首先我们需要先引入Redisson框架所需要的依赖
<dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson</artifactId>
       <version>3.10.5</version>
</dependency>


之后我们就需要配置Redisson框架的配置信息

@Configuration
public class RedissonConfig {
    //读取配置文件中的redis的ip地址.端口号,数据库,密码
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.password}")
    private String password;
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        //链式编程
        config.useSingleServer().setAddress("redis://" + host + ":" + port)
                                .setPassword(password)
                                .setDatabase(database);
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}


这样我们就已经将Redisson引入到我们的Spring容器之中了

之后我们便来编写代码进行测试:


@Controller
public class RedissonController {
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RedissonClient redissonClient;
    @RequestMapping("/testRedisson")
    @ResponseBody
    public String testRedisson(){
        Jedis jedis=redisUtil.getJedis();
        RLock lock=redissonClient.getLock("lock");//声明锁
        //上锁
        lock.lock();
        try {
            String v=jedis.get("k");
            if(StringUtils.isBlank(v)){
                v="1";
            }
            System.out.println("---->"+v);
            jedis.set("k",(Integer.parseInt(v)+1)+"");
            jedis.close();
        }
        finally {
            //解锁
            lock.unlock();
        }
        return "success";
    }
}


为了模拟高并发,我们通过Apache来进行压力测试(后续我会单独出一篇博客讲解压力测试,主要因为篇幅已经很长了)


202012022028227.png


之后我们再来分别看看三个程序打印的结果:

8071端口:


20201202202941319.png

8072端口:

20201202202956256.png

8073端口:

20201202203011197.png


可以很明显的看到数据没有重复,的确已经实现了安全性.


2.3-缓存雪崩解决方案


了解完上述的缓存雪崩的概念之后,解决办法就比较简单了,既然是因为数据的过期时间都是一样的才导致数据同时失效,那么我们就可以通过 将数据的过期时间设置成随机的 ,这样就会在极大程度上减少大量数据同时过期的情况.


举个例子,可以通过下面的方法来实现:


public PmsSkuInfo selectBySkuId(Integer skuId) {
        PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
        //连接缓存
        Jedis jedis=redisUtil.getJedis();
        //查询缓存
        String skuKey="sku:"+skuId+":info";
        String skuJson=jedis.get(skuKey);
        //缓存不为空
        if(StringUtils.isNotBlank(skuJson)){
            //通过fastjson将我们的字符串转化成我们对应的Sku对象
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }
        else{
            //如果缓存没有,查询mysql
            pmsSkuInfo=selectBySkuIdFromDB(skuId);
            //mysql查询结果存储到Redis
            if(pmsSkuInfo!=null){
            //设置随机过期时间
                jedis.setex("sku:"+skuId+":info", (int) (10*Math.random()*10),JSON.toJSONString(pmsSkuInfo));
            }
            else{
                //数据库中同样也不存在该数据
                jedis.setex("sku:"+skuId+":info",30,JSON.toJSONString(""));
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
前端开发 安全 JavaScript
官网构建不再难:全方位解析高效解决方案,让企业形象在线上‘大放异彩’
【8月更文挑战第29天】企业门户网站是展示品牌和传递信息的重要窗口,其构建需综合考虑技术选型、内容管理和用户交互等。本文从内容管理系统(CMS)、前端框架、响应式设计、SEO优化及安全防护等方面,评估高效构建方案。WordPress适合快速搭建内容丰富的网站,而Drupal则适用于复杂内容管理和定制化需求;React和Vue提高前端开发效率,Bootstrap助力响应式布局;SEO技术和工具提升搜索引擎排名;SSL/TLS证书和Web应用防火墙保障安全。通过综合应用这些技术,企业可构建功能全面、体验优秀的门户网站。
30 1
|
2月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
60 0
|
4天前
|
存储 缓存 NoSQL
Redis 大 Key 对持久化的影响及解决方案
Redis 大 Key 对持久化的影响及解决方案
11 1
|
1月前
|
存储 缓存 NoSQL
Redis中大Key与热Key的解决方案
在工作中,Redis作为一款高性能缓存数据库被广泛应用,但常遇到“大key”和“热key”问题。“大key”指单个键包含大量数据,导致内存消耗高、性能下降及持久化效率降低;“热key”则是频繁访问的键,会引起CPU占用率高、请求阻塞等问题。本文详细分析了这些问题的定义、影响、原因,并提供了相应的解决方案,如合理设置缓存时间和数据结构、拆分大key、采用热点数据分片等方法。
Redis中大Key与热Key的解决方案
|
4天前
|
存储 缓存 NoSQL
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
12 0
|
2月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
53 0
|
2月前
|
Java Spring 监控
危机时刻,Spring框架如何拯救你的应用?深入探讨健康检查与自我修复功能
【8月更文挑战第31天】在现代软件架构中,应用的稳定性和可用性至关重要。本文介绍Spring框架中的健康检查与自我修复机制,通过Spring Boot Actuator的`/health`端点监控应用状态,并结合Spring Cloud Hystrix实现服务容错和断路器功能,提高应用健壮性。借助这些工具,开发者能轻松监控应用健康状况并在发现问题时自动采取措施,确保服务高可用性。要实现完善的机制,需根据具体应用架构和需求进行配置和扩展。
40 0
|
2月前
|
开发者 测试技术 Android开发
Xamarin 开发者的五大常见问题及解决方案:从环境搭建到性能优化,全面解析高效跨平台应用开发的技巧与代码实例
【8月更文挑战第31天】Xamarin 开发者常遇问题及解决方案覆盖环境搭建至应用发布全流程,助新手克服技术难关。首先需正确安装配置 Visual Studio 及 Xamarin 支持,设置 iOS/Android 测试环境。利用 Xamarin.Forms 和 XAML 实现高效跨平台开发,共享 UI 和业务逻辑代码。针对性能优化,采取减少 UI 更新、缓存计算结果等措施,复杂问题则借助 Xamarin Profiler 分析。
36 0
|
2月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
2月前
|
存储 缓存 NoSQL
Redis深度解析:部署模式、数据类型、存储模型与实战问题解决
Redis深度解析:部署模式、数据类型、存储模型与实战问题解决

推荐镜像

更多
下一篇
无影云桌面