redis核心面试题二(实战优化)

文章目录

  • 10. redis配置mysql实战优化[重要]
  • 11. redis之缓存击穿、缓存穿透、缓存雪崩
  • 12. redis实现分布式session

10. redis配置mysql实战优化[重要]

// 最初实现
		@Override
    @Transactional
    public Product createProduct(Product product) {
        productRepo.saveAndFlush(product);
        jedis.set(SystemConstants.REDIS_KEY_PREFIX + product.getProductId(), gson.toJson(product))
        return product;
    }

    @Override
    @Transactional
    public Product updateProduct(Product product) {
        productRepo.saveAndFlush(product);
        jedis.set(SystemConstants.REDIS_KEY_PREFIX + product.getProductId(), gson.toJson(product))
        return product;
    }

    @Override
    public Product getProduct(Long productId) {
        // 1. 先查redis
        String productRedis = jedis.get(SystemConstants.REDIS_KEY_PREFIX + productId);
        if (!StringUtil.isBlank(productRedis)) {
            return gson.fromJson(productRedis, Product.class);
        }
        // 2. redis没有,再查mysql数据库
        Product productMysql = productRepo.findByProductId(productId);
      	if (productMysql != null) {
            // 3. 数据库有,则更新redis数据
            jedis.set(SystemConstants.REDIS_KEY_PREFIX + productMysql.getProductId(), gson.toJson(productMysql));
        }
        // 4. 返回mysql数据库数据
        return productMysql;
    }
小公司并发量不大的情况下,问题不是很大,但是大公司高并发量,会出现大量问题,列举如下:
存在的问题:
	1. 缓存容量小问题:几百G的海量数据不可能一直都放到redis缓存中,大大降低redis(<10G)作为内存数据库的效率
		解决方案:设置固定过期时间,比如说一天,虽然一开始redis数据量很大,但是一天之后,会有大量数据失效,达到冷热数据的分离。
jedis.set(SystemConstants.REDIS_KEY_PREFIX + product.getProductId(), gson.toJson(product));
jedis.expire(SystemConstants.REDIS_KEY_PREFIX + product.getProductId(), SystemConstants.REDIS_KEY_EXPIRED_TIME);

	2. 缓存击穿问题:虽然设置了过期时间,仍然会出现缓存击穿问题, 即单个热点key失效的瞬间,持续的大并发请求就会击破缓存,直接请求到数据库,好像蛮力击穿一样(缓存无数据/数据库有数据)
    解决方案:设置随机过期时间
jedis.expire(SystemConstants.REDIS_KEY_PREFIX + productId, genRandomExpiredTime(5));
public Integer genRandomExpiredTime(Integer random) {
   return SystemConstants.REDIS_KEY_EXPIRED_TIME + new Random().nextInt(random) * 60 * 60;
}

	3. 缓存穿透问题:用户访问的数据既不在缓存当中,也不在数据库中,按道理说数据库都没有这个数据,就不能一直来查数据库了,防止黑客恶意攻击。
	解决方案一:缓存空值(null)或默认值 + 过期时间
	在数据库查询不存在时,将其缓存为空值(null)或默认值,缓存失效时间一般设置为5分钟之内,当数据库被写入或更新该key的新数据时,缓存必须同时被刷新,避免数据不一致。
		@Override
    public Product getProduct(Long productId) {
        String redisId = SystemConstants.REDIS_KEY_PREFIX + productId;

        // 1. 先查redis
        String productRedis = jedis.get(redisId);
        if (!StringUtil.isBlank(productRedis)) {
            // 判断缓存是否是默认值,避免缓存穿透
            if (productRedis.equals(SystemConstants.REDIS_DEFAULT_CACHE)) {
                jedis.expire(redisId, genRandomExpiredTime(3));
                return null;
            }
            jedis.expire(redisId, genRandomExpiredTime(5));
            return gson.fromJson(productRedis, Product.class);
        }

        // 2. redis没有,再查mysql数据库
        Product productMysql = productRepo.findByProductId(productId);
    		if (productMysql != null) {
            // 3. 数据库有,则更新redis数据
            jedis.set(redisId, gson.toJson(productMysql));
            jedis.expire(redisId, genRandomExpiredTime(5));
        } else {
            // 缓存空或默认值 + 过期时间,避免缓存穿透
            jedis.set(redisId, SystemConstants.REDIS_DEFAULT_CACHE);
            jedis.expire(redisId, genRandomExpiredTime(3));
        }
        return productMysql;
    }
        
	4. 突发性热点缓存重建导致数据库系统压力倍增:也就是说某一数据本来是冷数据,存储在数据库中,突然出现大量访问,redis还没缓存该数据,因此需要大量查询数据库并重建缓存,也就是以下代码重复执行,要是只执行一次就好了。
    		if (!StringUtil.isBlank(productRedis)) {
            // 3. 数据库有,则更新redis数据
            jedis.set(redisId, gson.toJson(productMysql));
            jedis.expire(redisId, genRandomExpiredTime(5));
        }
        
	解决方案一:DCL双端检锁机制
但仍然存在以下问题,一方面synchronized锁住的是单个JVM,若是该web项目集群部署,则在每个JVM都需要锁一次,另一方面,假如productId=101是热点数据会被锁住,但是其他数据productId=202也需要排队等待,效率降低。
  解决方案二:分布式锁setnx
但仍然存在redis缓存和mysql数据库数据不一致问题
	解决方案三:锁优化-读写锁
    
	5. 缓存雪崩:在使用缓存时,通常会对缓存设置过期时间,一方面目的是保持缓存与数据库数据的一致性,另一方面是减少冷缓存占用过多的内存空间。但当缓存中大量热点缓存在某一个时刻同时实效,请求全部转发到数据库,从而导致数据库压力骤增,造成系统崩溃等情况,这就是缓存雪崩。
	解决方案:
    1. key均匀失效:   将key的过期时间后面加上一个随机数(比如随机1-5分钟),让key均匀的失效。
		2. 双key策略:		主key设置过期时间,备key不设置过期时间,当主key失效时,直接返回备key值。
		3. 构建缓存高可用集群
// 解决方案一:DCL双端检锁机制
@Override
    public Product getProduct(Long productId) {
        String redisId = SystemConstants.REDIS_KEY_PREFIX + productId;
        // 1. 先查redis
        String productRedis = jedis.get(redisId);
        if (!StringUtil.isBlank(productRedis)) {
            // 判断缓存默认值,避免缓存穿透
            if (productRedis.equals(SystemConstants.REDIS_DEFAULT_CACHE)) {
                jedis.expire(redisId, genRandomExpiredTime(3));
                return null;
            }
            jedis.expire(redisId, genRandomExpiredTime(5));
            return gson.fromJson(productRedis, Product.class);
        }

        Product productMysql = null;
        synchronized (this) {
            // 2. DCL再查redis,因为只要有一次查询数据库操作,redis就已经有缓存数据了
            productRedis = jedis.get(redisId);
            if (!StringUtil.isBlank(productRedis)) {
                if (productRedis.equals(SystemConstants.REDIS_DEFAULT_CACHE)) {
                    jedis.expire(redisId, genRandomExpiredTime(3));
                    return null;
                }
                jedis.expire(redisId, genRandomExpiredTime(5));
                return gson.fromJson(productRedis, Product.class);
            }

            // 3. redis还是没有,再查mysql数据库
            productMysql = productRepo.findByProductId(productId);
            if (productMysql != null) {
                // 4. 数据库有,则更新redis数据【可能出现突发性热点缓存重建导致数据库系统压力倍增】
                jedis.set(redisId, gson.toJson(productMysql));
                jedis.expire(redisId, genRandomExpiredTime(5));
            } else {
                // 缓存空或默认值 + 过期时间,避免缓存穿透
                jedis.set(redisId, SystemConstants.REDIS_DEFAULT_CACHE);
                jedis.expire(redisId, genRandomExpiredTime(3));
            }
        }
        return productMysql;
    }
// 解决方案二:分布式锁setnx
    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.20.0</version>
    </dependency>
    
    @Configuration
		public class RedissonConfig {
    @Bean
    public Redisson redisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    	}
		}

 		// 集群部署:分布式锁
    public Product getProduct2(Long productId) {
        String redisId = SystemConstants.REDIS_KEY_PREFIX + productId;
        // 1. 先查redis缓存
        Product product = getProductFromRedis(redisId);
        if (product != null) {
            return product;
        }

        // 分布式锁RLock确保锁住特定的productId,不影响其他productId,解决所有问题
        RLock lock = redisson.getLock(SystemConstants.LOCK_HOT_CACHE_PREFIX + productId);
        lock.lock(); // 等价于setnx(SystemConstants.LOCK_HOT_CACHE_PREFIX + productId, value)
        
        // 2. DCL再查redis,因为只要有一次查询数据库操作,redis就已经有缓存数据了
        Product productMysql = null;
        try {
            product = getProductFromRedis(redisId);
            if (product != null) {
                return product;
            }
            // 3. redis还是没有,最后查mysql数据库
            productMysql = getProductFromMysql(productId);
        } finally {
            lock.unlock();
        }
        return productMysql;
    }

    private Product getProductFromRedis(String redisId) {
        Product product = null;
        String productRedis = jedis.get(redisId);
        if (!StringUtil.isBlank(productRedis)) {
            if (productRedis.equals(SystemConstants.REDIS_DEFAULT_CACHE)) {
                // 缓存中存在,却是缓存默认值,也就是数据库没有数据,设置过期时间,避免缓存穿透
                jedis.expire(redisId, genRandomExpiredTime(3));
                return new Product(); // 特殊情况
            }
            // 缓存中存在,也是正常值
            jedis.expire(redisId, genRandomExpiredTime(5));
            product = gson.fromJson(productRedis, Product.class);
        }
        return product;
    }

    private Product getProductFromMysql(Long productId) {
        String redisId = SystemConstants.REDIS_KEY_PREFIX + productId;
        Product productMysql = productRepo.findByProductId(productId);
        if (productMysql != null) {
            // 数据库有,则同步更新redis缓存数据【但是可能出现突发性热点缓存重建导致数据库系统压力倍增,也就是这段代码大量执行】
            jedis.set(redisId, gson.toJson(productMysql));
            jedis.expire(redisId, genRandomExpiredTime(5));
        } else {
            // 数据库没有,则设置默认值缓存 + 过期时间,避免缓存穿透
            jedis.set(redisId, SystemConstants.REDIS_DEFAULT_CACHE);
            jedis.expire(redisId, genRandomExpiredTime(3));
        }
        return productMysql;
    }
// 解决方案三:锁优化-读写锁
public Product getProductByReadWriteLock(Long productId) {
        String redisId = SystemConstants.REDIS_KEY_PREFIX + productId;
        // 1. 先查redis缓存
        Product product = getProductFromRedis(redisId);
        if (product != null) {
            return product;
        }
        // 加写锁
        ReadWriteLock readWriteLock = redisson.getReadWriteLock(SystemConstants.LOCK_HOT_UPDATE_PREFIX + productId);
        Lock writeLock = readWriteLock.writeLock();
        writeLock.lock();

        // 2. DCL再查redis,因为只要有一次查询数据库操作,redis就已经有缓存数据了
        Product productMysql;
        try {
            product = getProductFromRedis(redisId);
            if (product != null) {
                return product;
            }
            // 3. 加读锁 读数据库
            ReadWriteLock readWriteLock2 = redisson.getReadWriteLock(SystemConstants.LOCK_HOT_UPDATE_PREFIX + productId);
            Lock readLock = readWriteLock2.readLock();
            readLock.lock();
            productMysql = getProductFromMysql(productId);
            readLock.unlock();
        } finally {
            writeLock.unlock();
        }
        return productMysql;
    }  

11. redis之缓存击穿、缓存穿透、缓存雪崩

  • 缓存击穿-缓存无数据/数据库有数据

单个热点key失效的瞬间,持续的大并发请求就会击破缓存,直接请求到数据库,好像蛮力击穿一样。这种情况就是缓存击穿(Cache Breakdown)。

在这里插入图片描述

1. 使用互斥锁(Mutex Key)
  只让一个线程构建缓存,其他线程等待构建缓存执行完毕,重新从缓存中获取数据。
2. 热点数据设置随机过期时间,后台异步更新缓存,适用于不严格要求缓存一致性的场景。
  • 缓存穿透-缓存无数据/数据库无数据

    缓存穿透(cache penetration)是用户访问的数据既不在缓存当中,也不在数据库中。但出于容错的考虑,如果从数据库查询不到数据,则无法写入缓存。这就导致每次请求都会到底层数据库进行查询,缓存也失去了意义。当高并发或有人利用不存在的Key频繁攻击时,数据库的压力骤增,甚至崩溃,这就是缓存穿透问题。

在这里插入图片描述

解决方案:
方案一:缓存空值(null)或默认值 + 过期时间
	在数据库查询不存在时,将其缓存为空值(null)或默认值,缓存失效时间一般设置为5分钟之内,当数据库被写入或更新该key的新数据时,缓存必须同时被刷新,避免数据不一致。

方案二:业务逻辑前置校验
	在业务请求的入口处进行数据合法性校验,检查请求参数是否合理、是否包含非法值、是否恶意请求等,提前有效阻断非法请求。比如,根据年龄查询时,请求的年龄为-10岁,这显然是不合法的请求参数,直接在参数校验时进行判断返回。

方案三:使用布隆过滤器请求白名单
	写入数据时,使用布隆过滤器进行标记(相当于设置白名单),业务请求发现缓存中无对应数据时,可先通过查询布隆过滤器判断数据是否在白名单内,如果不在白名单内,则直接返回空或失败。

方案四:用户黑名单限制
	当发生异常情况时,实时监控访问的对象和数据,分析用户行为,针对故意请求、爬虫或攻击者,进行特定用户的限制;

在这里插入图片描述

  • 缓存雪崩-缓存无数据/数据库有数据

    在使用缓存时,通常会对缓存设置过期时间,一方面目的是保持缓存与数据库数据的一致性,另一方面是减少冷缓存占用过多的内存空间。但当缓存中大量热点缓存在某一个时刻同时实效,请求全部转发到数据库,从而导致数据库压力骤增,造成系统崩溃等情况,这就是缓存雪崩(Cache Avalanche)。

在这里插入图片描述

解决方案:
1. key均匀失效:   将key的过期时间后面加上一个随机数(比如随机1-5分钟),让key均匀的失效。
2. 双key策略:		主key设置过期时间,备key不设置过期时间,当主key失效时,直接返回备key值。
3. 构建缓存高可用集群(针对缓存服务故障情况)

12. redis实现分布式session

基于redis的分布式session实现,依赖于前台请求中携带的cookie和后台生成的token。大致原理可以分为以下步骤:

1,前端请求目标方法,拦截器判断请求头中是否携带cookie。
2,如果请求头中携带cookie,则取出cookie并查询redis中该cookie是否过期。
	如果没有过期,则放行让该请求去请求目标方法;
	如果已经过期,重新登陆
3,如果请求头中,没有携带cookie,则跳转到登录方法(同时携带当前请求的链接作为登录后的回调地址)
4,进行登录,登录完毕生成指定的token存入redis中,生成cookie设置到response中。
5,登录成功之后前端通过回调继续请求目标方法。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/654074.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ProxySQL路由策略实现读写分离

目的&#xff1a;配置proxysql路由策略后将不同用户的不同请求路由到不同的节点&#xff0c;实现读写分离 前提条件&#xff1a; 配置表mysql_replication_hostgroups&#xff0c;10为写组&#xff0c;20为读组 mysql_users表中已添加用户writer用户加入10写组&#xff0c;rea…

linux开发之设备树基本语法二

设备树特殊节点,对节点定义别名,chosen节点用来uboot给内核传参 上面的mmc0就是sdmmc0节点的别名 device_type属性 只对cpu节点和memory节点进行描述 自定义属性 这部分自定义,比如定义管脚标号,初始数值等 为什么我们可以在设备树上自己定义属性呢?设备树文件描述的是硬…

AI手语研究数据集;视频转视频翻译和风格化功能如黏土动画;AI检测猫咪行为;开放源码的AI驱动搜索引擎Perplexica

✨ 1: Prompt2Sign 多语言手语数据集&#xff0c;便捷高效用于手语研究。 Prompt2Sign 是一个全面的多语言手语数据集&#xff0c;旨在通过工具自动获取和处理网络上的手语视频。该数据集具有高效、轻量的特点&#xff0c;旨在减少先前手语数据集的不足之处。该数据集目前包含…

Python---Matplotlib(2万字总结)【从入门到掌握】

数据可视化 在完成了对数据的透视之后&#xff0c;可以将数据透视的结果通过可视化的方式呈现出来&#xff0c;简单的说&#xff0c;就是将数据变成漂亮的图表&#xff0c;因为人类对颜色和形状会更加敏感&#xff0c;然后再进一步解读数据背后隐藏的价值。在之前的文章中已经…

gitlab push 代码,密码正确,仍然提示HTTP Basic: Access denied. The provided password

HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password gitlab 登录账户密码确认正确&#xff0c;登录获取代码仍然提示以上问题&#xff0c;解决方案 …

10、QT—SQLite使用小记1

开发平台&#xff1a;Win10 64位 开发环境&#xff1a;Qt Creator 13.0.0 构建环境&#xff1a;Qt 5.15.2 MSVC2019 64位 构建工具&#xff1a;qmake 在上一篇文章中笔者介绍了sqlite的使用&#xff0c;并提供了一个封装好的文件&#xff0c;这篇文章就针对封装好的文件进行测试…

在table中获取每一行scope的值

目的 当前有一份如下数据需要展示在表格中&#xff0c;表格的页面元素套了一个折叠面板&#xff0c;需要循环page_elements中的数据展示出来 错误实践 将template放在了折叠面板中&#xff0c;获取到的scope是空数组 <el-table-column label"页面元素" show-o…

3D Slicer:从入门到精通——数据模块之DICOM

DICOM 文章目录 DICOM概述DICOM简介Slicer DICOM数据库DICOM插件 如何操作创建DICOM数据库将DICOM文件读入场景DICOM导入DICOM加载 从DICOM数据库中删除数据将数据从场景导出到DICOM数据库将数据从场景导出到DICOM文件DICOM网络传输DICOMweb网络传输 查看DICOM元数据 面板及其用…

初识STM32单片机-TIM定时器

初识STM32单片机-TIM定时器 一、定时器概述二、定时器类型2.1 基本定时器(TIM6和TIM7)2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)2.3 高级定时器(TIM1和TIM8) 三、定时中断基本结构和时基单元工作时序3.1 定时器基本结构3.2 预分频器时序3.3 计数器时序3.3.1 计数器有无预装时序(…

广告界的奥斯卡:揭秘成功广告策划的核心要素

在这个品牌林立、竞争激烈的市场大潮中&#xff0c;想要让自己的品牌声音被听见&#xff0c;一个成功的广告策划无疑是你的超级扩音器。 一个成功的广告策划一般来说需要以下十大要素&#xff1a; 1. 明确的目标和受众定位&#xff1a;你的指南针 首先&#xff0c;咱们得有个…

aws eks节点的初始化引导和鉴权逻辑

kubernetes集群的kubelet的启动引导eks集群的kubelet启动引导 参考资料 https://juejin.cn/post/7016472622246395934eks安全最佳实践&#xff0c;https://aws.github.io/aws-eks-best-practices/security/docs/iam/ kubernetes集群的kubelet的启动引导 按照官方文档和相关…

灵动微SPI LCD彩屏参考方案

LCD显示能够提供均匀的、流畅的、色彩鲜艳的动态或静态的图像&#xff0c;尤其在家电应用、智能家居应用、消费电子等产品中&#xff0c;受到了广大消费者的青睐&#xff0c;同时也受到了市场的广泛关注&#xff0c;为此&#xff0c;官方代理英尚微介绍搭载MM32系列MCU的SPI LC…

【SpringBoot】单元测试实战演示及心得分享

目录 1.指定测试标准 2.设计测试用例 3.测试集示例 4.跑测试集 1.指定测试标准 单元测试会用到mock和junit的内容&#xff0c;作者前文有详解&#xff0c;可移步&#xff1a; Spring Boot单元测试-CSDN博客 mockito的详细使用-CSDN博客 1.1.测哪一层&#xff1f; 以当…

solidworks 3D草图案例2-方块异形切

单位mm 单位mm 长方体 底面是48mm*48mm&#xff0c;高为60mm 3D草图 点击线&#xff0c;根据三视图&#xff0c;绘制角度线&#xff0c; 由于三点确定一个面&#xff0c;因此确定三点就可以了 基准面 点击参考几何体-基准面&#xff0c;依次点击3个点 曲面切除 完成后点击插…

海外仓ERP系统:赋能海外仓,实现标准化管理

随着业务规模的不断发展和业务类型的复杂度逐渐提升&#xff0c;传统的海外仓管理模式已经很难适应现在的情况了。对海外仓企业来说&#xff0c;一套合适的海外仓管理erp系统可以起到很大的辅助作用。 不过很多小型海外仓企业会纠结于是同时选择企业erp系统和海外仓管理系统&a…

重庆耶非凡科技有限公司的选品师项目靠谱吗?

在跨境电商和零售市场日益繁荣的今天&#xff0c;选品师的角色愈发凸显出其重要性。重庆耶非凡科技有限公司作为一家致力于多元化服务的科技公司&#xff0c;其选品师项目备受关注。那么&#xff0c;重庆耶非凡科技有限公司的选品师项目靠谱吗?接下来&#xff0c;我们将从多个…

网上3d全景虚拟交互展馆沉浸式体验让客户和使用者都满意

在数字化浪潮席卷而来的今天&#xff0c;3D场景网站已成为众多行业展现创意与实力的新舞台。然而&#xff0c;传统的3D建模软件往往因其复杂性和高门槛&#xff0c;让许多渴望创建逼真3D场景的用户望而却步。 幸运的是&#xff0c;华锐视点推出了搭建3D场景网站的编辑器——一款…

双机多网口配置同网段地址,可以通过目的IP确定接收数据的网卡吗?

环境 两台机器两网卡同网段接入同一个二层交换机。 机器A ens38 00:0c:29:a4:8b:fb 10.0.0.11/24 ens39 00:0c:29:a4:8b:05 10.0.0.12/24 机器B ens38 00:0c:29:4f:a6:c4 10.0.0.21/24 ens39 00:0c:29:4f:a6:ce 10.0.0.22/24 初始ARP表 只有管理口接口的ARP表项&#xff0c…

Vue2+Element 封装评论+表情功能

有需要的小伙伴直接拿代码即可&#xff0c;不需要下载依赖。 评论组件如下&#xff1a; 创建 comment.vue 文件。 表情组件 VueEmoji.vue 在评论组件中使用。 <template><div class"comment"><div class"flex_box"><h2>评论 {…

Amazon云计算AWS(一)

目录 一、基础存储架构Dynamo&#xff08;一&#xff09;Dynamo概况&#xff08;二&#xff09;Dynamo架构的主要技术 二、弹性计算云EC2&#xff08;一&#xff09;EC2的基本架构&#xff08;二&#xff09;EC2的关键技术&#xff08;三&#xff09;EC2的安全及容错机制 提供的…