Redis缓存问题解决方案

Redis缓存问题解决方案

为什么使用Redis缓存:

1.在高并发的情况下,大量查询进入数据库,会大量占用数据库的连接(默认数据库连接数151),数据库压力过大就会出现connection refuse(数据库连接拒绝)问题,
2.Redis缓存数据存在内存中,读取速度比从磁盘大大提升,提高用户的体验;

Redis缓存使用场景:

1.先查后放最常见(先去Redis缓存中查询,没有再查数据库,再存入缓存中),
2.缓存预热/数据预热可解决缓存穿透问题(提前将热点数据放入Redis缓存中):比如李X琦直播间的火爆商品
3.定时器 + Redis缓存可定时(定时加载缓存,这样服务不需要重启:5月1号就提前把11月11日的热门商品数据准备好,通过定时器在11月11日把数据加载入Redis缓存中)

缓存一致性问题的解决方案:

必须知道读写都存在的情况下才会出现 缓存一致性问题、操作缓存使用删除而不是修改(修改逻辑复杂且消耗性能);
在这里插入图片描述

解决方案

  • 先更新数据库后删除缓存(删除重试)(无法数据强一致性,存在脏读问题在这里插入图片描述
    1.存在数据脏读情况1(无法避免):线程1在修改数据库的时候,线程2读取数据并把旧数据放入到redis缓存中——导致数据的脏读,
    2.存在数据脏读情况2(删除缓存失败,使用mq或者cannal解决):线程1在修改数据库后,删除redis缓存中的旧数据失败,导致其他线程永远获取到的是redis中的旧数据——导致数据的脏读
  • 先删除缓存再修改数据库(延迟双删)(无法数据强一致性,存在脏读问题
    在这里插入图片描述
    1.存在数据脏读情况(无法避免):线程1在修改数据库的时候,线程2访问了数据库并将数据写入到了redis缓存中,导致线程2和缓存中的数据是脏数据,所以需要线程1进行延迟双删将线程2存入redis中的缓存进行删除

  • 异步写(缺点:代码耦合度高):使用mq(rocketmq,rabbitmq) 完成数据同步。它会有延迟 99.999999%(无法数据强一致性,存在脏读问题
    在这里插入图片描述

  • Cannal:Cannal监听MySQL的binlog发送变化——通知Cannal客户端——Cannal客户端监听到变化后更新Redis(无法数据强一致性,存在脏读问题
    在这里插入图片描述

  • MySQL和redis加锁才能实现(数据强一致性,不存在脏读问题),但是效率会变低(适用于金融、支付等不能出任何差错的业务情况)

  • 拆表:把数据库业务需要变动字段拆出来,然后把不需要变动进行缓存

缓存穿透解决方案:

缓存穿透的原因大量的/恶意的查询语句在Redis缓存中没有查询到,直接访问数据库中也没有,导致数据库负载过高,造成了缓存穿透问题(缓存看做访问数据库DB的马甲);

  • 设置null缓存:数据库中没有查到的数据,在Redis缓存中设置key-null-过期时间
  • 缓存预热/数据预热缺点:不合理的查询依旧会打到数据库中,大量查询一个表中没有的数据):提起将数据放入缓存中,防止大量查询直接打在数据库,
  • 布隆过滤器(举例:提前将合法的数据id放入布隆过滤器中):布隆过滤器是访问Redis缓存的缓存(马甲),先访问布隆过滤器,如果布隆过滤器中没有这条数据的id就去数据库DB查询(数据库DB没有的话设置Redis空缓存,有的话返回这条数据),布隆过滤器中(直接访问Redis缓存数据,如果Redis缓存数据是空缓存则直接返回这空缓存——因为这个空缓存是查询数据库DB没有而设置的Redis空缓存

设置null缓存:

前置准备:Redis依赖、Redis的yml配置、Redis初始化配置类(解决Redis键的乱码问题:key用String序列化方式,value用jackjson进行处理)

<!--SpringBoot-Redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
server:port: 6380spring:# redis配置redis:# Redis数据库索引(默认为0)database: 7# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password:# 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0
/*** @Description: 覆盖官方start配置机制,防止官方的redis的键乱码问题* (key用String序列化方式,value用jackjson进行处理),* */
@Configuration
public class RedisConfiguration {/*** @Description 改写redistemplate序列化规则**/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 1: 开始创建一个redistemplateRedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 2:开始redis连接工厂跪安了redisTemplate.setConnectionFactory(redisConnectionFactory);// 创建一个json的序列化方式GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// 设置key用string序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer());// 设置value用jackjson进行处理redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash也要进行修改redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// 默认调用redisTemplate.afterPropertiesSet();return redisTemplate;}
}
@Service
public class CourseService {// 1: 使用 springboot自带的redis@Autowiredprivate RedisTemplate redisTemplate;/*** 查询课程需要放入缓存中** @param courseId*/public Course getCourse(Long courseId) {String key = "redis:course:" + courseId;// 根据课程key = redis:course:1去缓存中查询,是否存在,Course course = (Course) redisTemplate.opsForValue().get(key);System.out.println("course = " + course);// 如果缓存是否为空if (course == null) {// 获取数据库DB的数据course = getCourseDb(courseId);// 如果数据库也没有查询到if (course == null) {                course = new Course();// 这里为什么要设置时间? 因为redis无关不设置时间,就是永久的?// 这种数据一般设置一个时间,然后过期到,腾出内存空间。这个时间具体写多久。你可以考虑建议写大一点。redisTemplate.opsForValue().set(key, course, 600, TimeUnit.SECONDS);} else {// 数据库的数据开始写入到缓存中redisTemplate.opsForValue().set(key, course);}}return course;}/*** 伪造数据库的记录** @param courseId* @return*/public Course getCourseDb(Long courseId) {Map<Long, Course> map = new HashMap<>();map.put(1L, new Course(1L, "GO系列", new BigDecimal(2999)));map.put(2L, new Course(2L, "Java系列", new BigDecimal(1999)));return map.get(courseId);}
}

布隆过滤器解决数据穿透问题:

前置准备:redis数据库对布隆过滤器服务进行了配置绑定:参考文章https://blog.csdn.net/qq_58326706/article/details/135662671、布隆过滤器依赖、布隆过滤器所在的Redis服务器yml配置、布隆过滤器配置类

<!--布隆过滤器-->
<dependency><groupId>com.redislabs</groupId><artifactId>jrebloom</artifactId><version>2.1.0</version>
</dependency>
<!--jedis客户端(可对布隆过滤器操作)-->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.4.1</version>
</dependency><!--redisson客户端(可对布隆过滤器操作)-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.6</version>
</dependency>
# 远程redis的布隆过滤器配置
redis:bloom:host: 192.XXX.XXX.200port: 6379# 布隆过滤器中存放的数据(容量capacity)capacity: 100password: redis密码# 布隆过滤器的误差比例(100个出一次差错1%==0.01)rate: 0.01# 布隆过滤器存在Redis数据库的那个库中db: 7
/*** Redis的布隆过滤器配置类*/
@Configuration
@Slf4j
public class BloomFilterConfiguration {// 布隆过滤器服务关联的Redis的host地址@Value("${redis.bloom.host}")private String host;// 布隆过滤器服务关联的Redis的password@Value("${redis.bloom.password}")private String password;// 布隆过滤器服务关联的Redis的端口@Value("${redis.bloom.port}")private Integer port;// 布隆过滤器存放在关联的Redis的那个库@Value("${redis.bloom.db}")private int db;// 布隆过滤器的(容量capacity)@Value("${redis.bloom.capacity}")private Integer capacity;// 布隆过滤器的错误率@Value("${redis.bloom.rate}")private Double rate;/*** JedisPool连接池*/@Beanpublic JedisPool jedisPool() {JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxIdle(8);poolConfig.setMaxTotal(8);poolConfig.setMaxWaitMillis(10L);JedisPool jp = new JedisPool(poolConfig, host, port,10 * 1000, password, db);return jp;}/*** 布隆过滤器的Client的初始化:设置布隆过滤器的key名称(可设置多个布隆过滤器的key)** @param pool JedisPool连接池* @return*/@Beanpublic Client rebloomClient(JedisPool pool) {Client client = new Client(pool);try {// 布隆过滤器1:课程过滤 底层代码(bf.reserve redis:bloom:courses 100 0.01 )client.createFilter("redis:bloom:course:list", capacity, rate);// 布隆过滤器2:黑白名单 bf.reserve redis:bloom:userblack:list 0.01 100client.createFilter("redis:bloom:userblack:list", capacity, rate);// 布隆过滤器3:手机号码过滤 bf.reserve redis:bloom:urls 0.01 100client.createFilter("redis:bloom:phone:list", capacity, rate);} catch (Exception ex) {log.info("bloom过滤器已经存在,异常信息是:{}", ex.getMessage());}return client;}
}
@Service
public class CourseService {// 1: 使用 springboot自带的redis@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate Client bloomFilter;// bloomFilter客户端// 布隆过滤器keypublic static String courseBloomKey = "redis:bloom:course:list";public static String courseRedisKey = "redis:course:"; // 提前添加数据/*** 布隆过滤器解决缓存穿透问题:查询课程需要放入缓存中** @param courseId 课程id*/public Course getCourse2(Long courseId) {// 1、判断布隆过滤器中是否存在boolean exists = bloomFilter.exists(courseBloomKey, String.valueOf(courseId));Course course = null;// 定义null对象// 2、布隆过滤器中存在,在去Redis缓存中查找if (exists) {course = (Course) redisTemplate.opsForValue().get(courseRedisKey + courseId);// 3、如果Redis缓存中也为null,直接返回空对象if (course == null) {course = new Course();}// 3、!防止布隆过滤器误删} else {if (course == null) {course = getCourseDb(courseId);// 数据库也没有查到if (course == null) {course = new Course();// 设置Redis空缓存redisTemplate.opsForValue().set(courseRedisKey + courseId, course, 600, TimeUnit.SECONDS);} else {// 数据库的数据开始写入到缓存中redisTemplate.opsForValue().set(courseRedisKey + courseId, course);}}}return course;}/*** 伪造数据库的记录** @param courseId* @return*/public Course getCourseDb(Long courseId) {Map<Long, Course> map = new HashMap<>();map.put(1L, new Course(1L, "GO系列", new BigDecimal(2999)));map.put(2L, new Course(2L, "Java系列", new BigDecimal(1999)));return map.get(courseId);}
}

缓存预热实现:

缓存预热/数据预热的原理监听Spring容器启动前做缓存预热(提前将热点数据放入redis缓存中),
实现方式

  • @PostConstruct注解(适用于Spring、SpringBoot项目): 使用 @PostConstruct 注解标记一个方法,该方法将在对象被构造后自动调用
    参数和返回值: @PostConstruct 方法没有参数,并且不能有返回值。它通常被设计为执行一些初始化任务,而不关心调用者传递的参数,
    异常处理: 如果 @PostConstruct 方法抛出异常,对象的初始化将被终止,并且该异常将被抛出给调用者。
@Slf4j
@Component// 加入Spring容器管理
public class TestPostConstruct{@Autowiredprivate ShopService shopService;// 模拟预热的数据private static String hot_Data;/*** @PostConstruct注解的方法在Spring容器启动前执行,在依赖注入后执行*/@PostConstructpublic void construct(){log.info("〓〓〓〓〓〓〓〓〓〓 Autowired加载完成");hot_Data= ShopService.demo5();log.info("〓〓〓〓〓〓〓〓〓〓 hot_Data= " + hot_Data);}
}
@Slf4j
@Service
public class ShopServiceImpl implements ShopService {/*** 模拟从数据库查询数据的操作*/public String getHotData() {log.info("〓〓〓〓〓〓〓〓〓〓 getHotData():执行!!");return "Hot Data";}
}

在这里插入图片描述

  • 类实现InitializingBean接口(没有返回值,适用于Spring、SpringBoot项目),
@Slf4j
@Service
public class TestInitializingBean implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {log.info("我在Spring容器加载前,我的任务完成了!");}
}

在这里插入图片描述

  • 类实现 CommandLineRunner接口(没有返回值,适用于SpringBoot项目),
  • 类实现 ApplicationRunner接口(没有返回值,适用于SpringBoot项目),
  • 类实现 ApplicationListener接口(没有返回值,适用于Spring、SpringBoot项目),

什么所有方式的执行顺序:构造函数>initmethod>InitializingBean >@PostConstruct>ApplicationListener>CommandLineRunner/ApplicationRunner

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 895 (Div. 3)补题

Two Vessels&#xff08;Problem - A - Codeforces&#xff09; 题目大意&#xff1a;有两个无限容器&#xff0c;目前一个容器中有a克水&#xff0c;另一个容器中有b克水&#xff0c;现有一个大小为cg的容器&#xff0c;我们每次可以从一个无限容器中取任意不大于c克的水&…

android中.9图如何制作

在工作中我们需要做界面往往需要设计师给我们提供图标&#xff0c;我们才能把界面友好的显示出来&#xff0c;普通的图标用png、jpg等都可以显示出来&#xff0c;为了让界面更与众不同&#xff0c;设计师们通常会设计一些弯角、弯钩如果我们要作为背景使用的话就不能设置高度&a…

【 Qt 快速上手】-①- Qt 背景介绍与发展前景

文章目录 1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt的应用场景1.7 Qt的成功案例1.8 Qt的发展前景及就业分析行业发展方向就业方面的发展前景 1.1 什么是 Qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供了建立…

349. 两个数组的交集(力扣)(OJ题)

题目链接&#xff1a;349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 个人博客主页&#xff1a;https://blog.csdn.net/2301_79293429?typeblog 专栏&#xff1a;https://blog.csdn.net/2301_79293429/category_12545690.html 给定两个数组 nums1 和 nums2 &a…

协议网关BL110轻松实现多种协议转MQTT、OPC UA,支持8种主流工业协议转换

随着工业4.0的迅猛发展&#xff0c;人们深刻认识到在工业生产和生活中&#xff0c;实时、可靠、安全的数据传输至关重要。而在工厂自动化领域&#xff0c;工业设备之间的联动控制离不开各种工业通信协议。在此背景下&#xff0c;高性能的工业自动化数据传输解决方案——协议转换…

mac查看maven版本报错:The JAVA_HOME environment variable is not defined correctly

终端输入mvn -version报错: The JAVA_HOME environment variable is not defined correctly, this environment variable is needed to run this program. Java环境变量的问题,打开bash_profile查看 open ~/.bash_profile export JAVA_8_HOME/Library/Java/JavaVirtualMachine…

心跳检测与服务剔除

社保中心的忧桑 今天社保中心来了一位钉子户&#xff0c;90多岁的王大爷又兴高采烈的来给自己快120岁的老父亲领社保了! 工作人员这一-想&#xff0c;好像哪里不对啊&#xff0c;这老父亲120岁的年纪都可以上吉尼斯世界纪录了&#xff0c;要不咱帮老爷子去申请一下?王大爷一听…

vue3-侦听器

侦听器 计算属性允许我们声明性地计算衍生值。 需求在状态变化时进行一些操作&#xff0c;比如更改 Dom,根据异步操作结果去修改另外的数据状态。 watch 监听异步请求结果 <script lang"ts" setup> import { ref, watch } from "vue"const ques…

开发实践8_REST

一、Django REST Framework, Django View & APIView MTV模式实现前后端分离。Representational State Transfer 表现层状态转化。Representation 资源&#xff08;Resource a specific info. on net.&#xff09;具体呈现形式。ST 修改服务端的数据。修改数据 POST请求。…

用VSCode玩STM32的烧录工具 CooCox Cortex Flash Programmer

一、下载软件 经热心兄弟推荐的版本&#xff0c;不知道有没有版权&#xff0c;如有版权问题&#xff0c;请通知删除。 CSDN - 0积分下载&#xff1a;https://download.csdn.net/download/qq_49053936/88744187 二、生成bin文件 插件不同&#xff0c;方法有所不同&#xff0c;各…

云轴科技ZStack位列IDC云系统软件市场教育行业TOP2

近日&#xff0c;全球IT市场研究和咨询公司IDC发布 《中国云系统软件市场跟踪报告2023H1》 ZStack作为产品化的云基础软件提供商 位居云系统软件市场第一梯队 市场份额位列独立云厂商*第一 增速最快 教育行业TOP2 在教育行业&#xff0c;云计算已成为教育行业信息化的重要基础…

C++PythonC# 三语言OpenCV从零开发(3):图像读取和显示

文章目录 相关链接前言Mat是什么读取图片CC#Python 灰度处理CCSharpPython 打印图像信息CCsharpPython 总结 相关链接 C&Python&Csharp in OpenCV 专栏 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程&#xff08;附带课程课件资料课件笔记&#xff09; …