万字解析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月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
126 3
|
20天前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
64 3
|
23天前
|
存储 人工智能 自然语言处理
高效档案管理案例介绍:文档内容批量结构化解决方案解析
档案文件内容丰富多样,传统人工管理耗时低效。思通数科AI平台通过自动布局分析、段落与标题检测、表格结构识别、嵌套内容还原及元数据生成等功能,实现档案的高精度分块处理和结构化存储,大幅提升管理和检索效率。某历史档案馆通过该平台完成了500万页档案的数字化,信息检索效率提升60%。
|
22天前
|
存储
文件太大不能拷贝到U盘怎么办?实用解决方案全解析
当我们试图将一个大文件拷贝到U盘时,却突然跳出提示“对于目标文件系统目标文件过大”。这种情况让人感到迷茫,尤其是在急需备份或传输数据的时候。那么,文件太大为什么会无法拷贝到U盘?又该如何解决?本文将详细分析这背后的原因,并提供几个实用的方法,帮助你顺利将文件传输到U盘。
|
29天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
43 3
|
1月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
2月前
|
机器学习/深度学习 算法 Python
深度解析机器学习中过拟合与欠拟合现象:理解模型偏差背后的原因及其解决方案,附带Python示例代码助你轻松掌握平衡技巧
【10月更文挑战第10天】机器学习模型旨在从数据中学习规律并预测新数据。训练过程中常遇过拟合和欠拟合问题。过拟合指模型在训练集上表现优异但泛化能力差,欠拟合则指模型未能充分学习数据规律,两者均影响模型效果。解决方法包括正则化、增加训练数据和特征选择等。示例代码展示了如何使用Python和Scikit-learn进行线性回归建模,并观察不同情况下的表现。
347 3
|
2月前
|
SQL 安全 Windows
SQL安装程序规则错误解析与解决方案
在安装SQL Server时,用户可能会遇到安装程序规则错误的问题,这些错误通常与系统配置、权限设置、依赖项缺失或版本不兼容等因素有关
|
1月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
JavaScript 前端开发 索引
Vue3 + Vite项目实战:常见问题与解决方案全解析
Vue3 + Vite项目实战:常见问题与解决方案全解析
85 0

推荐镜像

更多