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

news2024/11/16 1:23:04

文章目录

  • 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,登录成功之后前端通过回调继续请求目标方法。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

变量命名的艺术:从蛇形到驼峰

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、蛇形命名法的魅力 二、类名和模块名的特殊规则 三、驼峰命名法的魅力与挑战 四、保持…

FFmpeg操作命令 - 精简版

PS&#xff1a;&#xff08;因为我只需要简单的操作&#xff0c;所以我整理出了这份笔记&#xff09; 原网址&#xff1a;30分钟带你入门&#xff0c;20个 FFmpeg操作命令&#xff0c;包你学会 - 知乎 大佬零声Github整理库整理的笔记非常的全面&#xff0c;想看完整版去上面…

MySQL主从复制(五):读写分离

一主多从架构主要应用场景&#xff1a;读写分离。读写分离的主要目标是分摊主库的压力。 读写分离架构 读写分离架构一 架构一结构图&#xff1a; 这种结构模式下&#xff0c;一般会把数据库的连接信息放在客户端的连接层&#xff0c;由客户端主动做负载均衡。也就是说由客户…

ROS | Gmapping进行Slam建图

launch文件 GMapping参数设置&#xff1a; 修改参数&#xff1a;

笔记 | 《css权威指南》

网络安全色 URL text-indent line-height & vertical-align 字体 font-weight 400 normal 700 bold background-attachment

【C++】c++入门(下 )

c入门 1.内联函数1.1 概念1.2 特性 2.auto关键字(C11)2.1 简介2.2 auto的使用2.3 auto不能推导的场景2.4 typedef取别名也能产生和auto的效果&#xff0c;为什么不使用&#xff1f; 3.基于范围的for循环(C11)3.1 9.1 范围for的语法3.2 范围for的使用条件 4.指针空值nullptr(C11…

寻找峰值 ---- 二分查找

题目链接 题目: 分析: 因为题目中要找的是任意一个峰值即可, 所以和<山脉数组的峰值索引>这道题差不多因为峰值左右都小于峰值, 所以具有"二段性", 可以使用二分查找算法如果nums[mid] < nums[mid 1], mid一定不是峰值, 所以left mid 1如果nums[mid] &…

【大模型部署】在C# Winform中使用文心一言ERNIE-3.5 4K 聊天模型

【大模型部署】在C# Winform中使用文心一言ERNIE-3.5 4K 聊天模型 前言 今天来写一个简单的ernie-c#的例子&#xff0c;主要参考了百度智能云的例子&#xff0c;然后自己改了改&#xff0c;学习了ERNIE模型的鉴权方式&#xff0c;数据流的格式和简单的数据解析&#xff0c;实…

电商平台的消费增值模式革新

在当今的电商市场&#xff0c;用户留存和粘性是各大平台竞相追求的目标。而消费增值模式&#xff0c;以其独特的激励机制&#xff0c;正逐渐成为电商平台吸引和留住用户的新策略。 一、消费即投资&#xff1a;创新的返利机制 在传统的电商消费中&#xff0c;消费者完成交易后&…

基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数

1 基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数 文章目录 1 基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数1.1 Print打印函数输出字符串1.2 Print打印函数其他用法程序开发我们以 edk2-stable202302版本为准。 1.1 Print打印函数输出字符串 我们把edk2/MdeModulePkg/App…

Upstream最新发布2024年汽车网络安全报告-百度网盘下载

Upstream最新发布2024年汽车网络安全报告-百度网盘下载 2024年2月7日&#xff0c;Upstream Security发布了2024年Upstream《GLOBAL AUTOMOTIVE CYBERSECURITY REPORT》。这份报告的第六版着重介绍了汽车网络安全的拐点&#xff1a;从实验性的黑客攻击发展到规模庞大的攻击&…

springboot 两个相同类型的Bean使用@Resouce加载

问题描述 有两个相同类型的Bean 使用Service等注解注入或者Bean注入启动以后报错&#xff1a; qualifying bean of type com.fasterxml.jackson.databind.ObjectMapper available: expected single matching bean but found 2提示有相同的类型两个。 解决 * 每个Bean Resour…

AI预测福彩3D采取888=3策略+杀断组+杀和尾缩水测试5月24日预测第1弹

哈喽&#xff0c;各位亲爱的小伙伴&#xff0c;在发布本期预测结果之前&#xff0c;先对最近的这套算法测试做一下总结。 最近的一套算法采用了88723的容差策略&#xff0c;关于容差策略相信大家都比较清楚&#xff1a;容差可以最大限度的保证初始大底中包含中奖号码&#xff0…

「网络流浅谈」网络流的概念

更好的阅读体验 通常做题思路&#xff1a;问题转化为流网络&#xff0c;再通过最大流 / 最小割 / 费用流与问题之间的数量关系&#xff0c;求解出原问题。 网络流于其他算法不同&#xff0c;概念定理需要熟记于心&#xff0c;否则后面做题会有很大的障碍。 1. 流网络 一个流…

鸿蒙开发ArkUI-X基础知识:【ArkUI代码工程及构建介绍】

代码工程及构建介绍 背景 ArkUI作为OpenHarmony的默认开发框架&#xff0c;在本项目&#xff08;ArkUI-X&#xff09;中需要做到一套代码同时支持多平台构建&#xff0c;所以会采取共仓开发的方式&#xff0c;部分仓直接指向OpenHarmony相关开源仓。 代码结构及仓库结构 代…

css左右滚动互不影响

想实现左右都可以滚动&#xff0c;且互不影响。 只需要再左边的css里面 .threedlist {cursor: pointer;width: 280px;position: fixed;height: 100vh; /* 定义父容器高度 */overflow-y: auto; /* 只有在内容超过父容器高度时才出现滚动条 */} 如果想取消滚动条样式 .threedli…

windows docker desktop 更换镜像存储目录

windows docker desktop 更换镜像存储目录 方法&#xff1a;如图&#xff0c;Browse浏览一个新的目录并选中&#xff0c;确定后&#xff0c;程序会开始stop&#xff0c;在stop完成前&#xff0c;会持续迁移原有镜像到新的位置&#xff0c;你会发现目标位置的磁盘占用空间越来越…

2024最新 Jenkins + Docker 实战教程(五)- 配置Gitee Webhooks实现自动构建部署

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Matlab:音频处理

用Matlab绘制一段音频信号在时域上的波形图&#xff0c;然后用低通滤波器滤掉噪音并再次绘制 1、导入音频文件 filename X:\1.mp3; % 替换为你的音频文件路径 [x, Fs] audioread(filename); 2、获取音频信号长度 len length(x); 3、计算时间轴 t (0:len-1) / Fs; 4、…

轻量音乐网站程序源码,在线音乐免费听歌

这是一个高品质的音乐共享和流媒体平台&#xff0c;用户可以在这个网站上免费在线听歌。这个轻量级的音乐网站程序源码&#xff0c;是您创建自己的音乐流媒体网站的最佳选择&#xff01;它还支持制作插件&#xff0c;并且在更新后&#xff0c;您可以保留您的自定义设置。 下 载…