当前位置: 首页 > news >正文

redis与springBoot整合

前提

要实现,使用Redis存储登录状态

需要一个完整的前端后端的项目

前端项目搭建

  • 解压脚手架

  • 安装依赖

  • 配置请求代理

  • 选做: 禁用EsLint语法检查 Vue Admin Template关闭eslint校验,lintOnSave:false设置无效解决办法_lintonsave: false-CSDN博客

后端项目搭建

  • 创建springboot项目

  • 从其他项目中拷贝需要的依赖

  • 从其他项目拷贝所需的yml配置

  • 创建所需的entity,Controller,service,mapper,util

  • 写一个登录测试即可

七、与SpringBoot整合

7.1 RedisTemplate了解

spring-data-redis的jar中,提供在srping应用中通过简单的配置访问redis服务的功能,它对reids底层开发包进行了高度封装。

针对reids的操作,包中提供了RedisTemplate类和StringRedisTemplate类,其中StringRedisTemplate是RedisTemplate的子类,该类只支持key和value为String的操作

RedisTemplate针对不同数据类型的操作进行封装,将同一类型操作封装为Operation接口

  • ValueOperations:简单K-V操作,获取方式 redisTemplate.opsForValue();

  • SetOperations:set类型数据操作,获取方式 redisTemplate.opsForSet();

  • ZSetOperations:zset类型数据操作,获取方式 redisTemplate.opsForZSet();

  • HashOperations:针对hash类型的数据操作, 获取方式 redisTemplate.opsForHash();

  • ListOperations:针对list类型的数据操作,获取方式 redisTemplate.opsForList();


序列化策略

StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

  • RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

  • GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化

  • Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的

  • JacksonJsonRedisSerializer: 序列化object对象为json字符串

  • JdkSerializationRedisSerializer: 序列化java对象(被序列化的对象必须实现Serializable接口)

  • StringRedisSerializer: 简单的字符串序列化

  • GenericToStringSerializer:类似StringRedisSerializer的字符串序列化

  • GenericJackson2JsonRedisSerializer:类似Jackson2JsonRedisSerializer,但使用时构造函数不用特定的类

7.2 整合

7.2.1 依赖

        <!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
​<!-- pool 对象池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>

7.2.2 yml配置

spring:datasource:# 这里是之前mysql的....redis:# 地址host: 127.0.0.1# 端口,默认为6379port: 6379# 数据库索引database: 0# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms

7.2.3 redis配置类

SpringBoot自动在容器中创建了RedisTemplate对象和StringRedisTemplate对象。但是,RedisTemplate的泛型是<Object,Object>,进行数据处理时比价麻烦,我们需要自定义一个RedisTemplate对象


ps: [了解]在SpringBoot 1.5.x版本默认的Redis客户端是Jedis实现的,SpringBoot 2.x版本默认客户端是用lettuce实现的

package com.qf.config;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
​
/*** --- 天道酬勤 ---** @author QiuShiju* @desc* 针对redis的配置类* 主要目的,设置RedisTemplate的序列化策略*/
@Configuration
public class RedisConfig {
​@Autowiredprivate LettuceConnectionFactory lettuceConnectionFactory;
​// 容器中默认的对象名是方法名@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
​//key采用String的序列化方式redisTemplate.setKeySerializer(stringRedisSerializer);// value序列化方式采用jacksonredisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的key也采用String的序列化方式redisTemplate.setHashKeySerializer(stringRedisSerializer);// hash的value序列化方式采用jacksonredisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setConnectionFactory(lettuceConnectionFactory);return redisTemplate;}
}

7.3 测试

记得测试依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>

测试代码

package com.qf.test;
​
import com.qf.entity.StudentTb;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
​
/*** 所有springboot 相关单元测试类 都必须在启动类所在包及其子包下*/
@SpringBootTest // 作用就是标记当前类 一个springboot测试 ,可以启动springboot应用 并从容器中获取 容器中的bean
public class RedisTest {
​/*** 从容器中获取 redisTemplate*   redisTemplate 使用了模板设计模式,作用提供了统一的api 操作*/@Autowiredprivate RedisTemplate redisTemplate ;
​/*** @Test 表示当前方法是一个测试方法* 测试方法要求:  1.必须是public void*               2.无参* 测试value 为String 类型*/@Testpublic void stringTest(){
​// valueOperations 就是一个专门用于操作 值为String 类型的redis工具// 相当于 redis 命令的 set   getValueOperations valueOperations = redisTemplate.opsForValue();// set  a1 1000valueOperations.set("a1","1000h");valueOperations.set("a2","哈哈哈");//  get  a1Object result = valueOperations.get("a1");Object result2 = valueOperations.get("a2");System.out.println("result = " + result);System.out.println("result2 = " + result2);}
​/*** 操作list 数据** @Data* public class StudentTb {**     private int id;*     private String name;*     private int age;* }*/@Testpublic void listTest(){
​StudentTb studentTb1 = new StudentTb();studentTb1.setId(1000);studentTb1.setName("xiaoming");studentTb1.setAge(18);
​StudentTb studentTb2 = new StudentTb();studentTb2.setId(1001);studentTb2.setName("lisi");studentTb2.setAge(28);
​// listOperations 专门用于操作redis中 的List 数据结构ListOperations listOperations = redisTemplate.opsForList();
​// 在redis key studentList 中添加数据 studentTb1对象listOperations.leftPush("studentList",studentTb1);listOperations.leftPush("studentList",studentTb2);
​// 从list 集合中读取数据  studentListList<StudentTb> studentList = listOperations.range("studentList", 0, -1);System.out.println("studentList = " + studentList);
​}
​/*** 操作 hash类型的数据*   存储对象*/@Testpublic void hashTest(){// hashOperations 操作数据类型为 hash的数据HashOperations hashOperations = redisTemplate.opsForHash();
​hashOperations.put("stu1","id","1000");hashOperations.put("stu1","name","xiaoming");hashOperations.put("stu1","age","18");
​// 读取hash 类型中的数据String name = (String) hashOperations.get("stu1", "name");System.out.println("name = " + name);
​}
​/*** 测试  Set 类型数据*/@Testpublic void setTest(){
​StudentTb studentTb1 = new StudentTb();studentTb1.setId(1000);studentTb1.setName("xiaoming");studentTb1.setAge(18);
​StudentTb studentTb2 = new StudentTb();studentTb2.setId(1001);studentTb2.setName("lisi");studentTb2.setAge(28);
​// setOperations 用于操作set 类型数据SetOperations setOperations = redisTemplate.opsForSet();setOperations.add("studentSet1",studentTb1,studentTb2);
​// 读取到studentSet1 对应的内容Set<StudentTb> studentSet1 = setOperations.members("studentSet1");
​System.out.println("studentSet1 = " + studentSet1);}
​/*** 测试 zset 数据类型*/@Testpublic void  zSetTest(){StudentTb studentTb1 = new StudentTb();studentTb1.setId(1000);studentTb1.setName("xiaoming");studentTb1.setAge(18);
​StudentTb studentTb2 = new StudentTb();studentTb2.setId(1001);studentTb2.setName("lisi");studentTb2.setAge(28);
​// zSetOperations 专门用于操作zsetZSetOperations zSetOperations = redisTemplate.opsForZSet();zSetOperations.add("zset1",studentTb1,88);zSetOperations.add("zset1",studentTb2,78);
​// 从zset中读取数据Set<StudentTb> zset1 = zSetOperations.range("zset1", 0, -1);System.out.println("zset1 = " + zset1);}
​/*** 操作key 相关命令*/@Testpublic void  keyTest(){
​// 删除对应的keyBoolean result = redisTemplate.delete("a2");System.out.println("result = " + result);
​// 设置a1 最多存活 10sredisTemplate.expire("a1",10, TimeUnit.MICROSECONDS);}
}

7.4 工具类

一般在开发的时候,不会直接使用RedisTemplate操作Redis

都会再封装一个工具类RedisUtil,类似下面这种(CV Ruoyi项目的)

package com.qf.util;
​
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
​
​
/*** --- 天道酬勤 ---** @author QiuShiju* @desc Redis工具类*/
@Component
public class RedisUtil {
​@Autowiredpublic RedisTemplate redisTemplate;
​/*** 缓存基本的对象,Integer、String、实体类等** @param key   缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue( ).set(key, value);}
​/*** 缓存基本的对象,Integer、String、实体类等** @param key      缓存的键值* @param value    缓存的值* @param timeout  时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue( ).set(key, value, timeout, timeUnit);}
​/*** 设置有效时间** @param key     Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout) {return expire(key, timeout, TimeUnit.SECONDS);}
​/*** 设置有效时间** @param key     Redis键* @param timeout 超时时间* @param unit    时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}
​/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key) {ValueOperations<String, T> operation = redisTemplate.opsForValue( );return operation.get(key);}
​/*** 删除单个对象** @param key*/public boolean deleteObject(final String key) {return redisTemplate.delete(key);}
​/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection) {return redisTemplate.delete(collection);}
​/*** 缓存List数据** @param key      缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList( ).rightPushAll(key, dataList);return count == null ? 0 : count;}
​/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key) {return redisTemplate.opsForList( ).range(key, 0, -1);}
​/*** 缓存Set** @param key     缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator( );while (it.hasNext( )) {setOperation.add(it.next( ));}return setOperation;}
​/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key) {return redisTemplate.opsForSet( ).members(key);}
​/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {if (dataMap != null) {redisTemplate.opsForHash( ).putAll(key, dataMap);}}
​/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key) {return redisTemplate.opsForHash( ).entries(key);}
​/*** 往Hash中存入数据** @param key   Redis键* @param hKey  Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value) {redisTemplate.opsForHash( ).put(key, hKey, value);}
​/*** 获取Hash中的数据** @param key  Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey) {HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash( );return opsForHash.get(key, hKey);}
​/*** 删除Hash中的数据** @param key* @param hKey*/public void delCacheMapValue(final String key, final String hKey) {HashOperations hashOperations = redisTemplate.opsForHash( );hashOperations.delete(key, hKey);}
​/*** 获取多个Hash中的数据** @param key   Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {return redisTemplate.opsForHash( ).multiGet(key, hKeys);}
​/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern) {return redisTemplate.keys(pattern);}
}

演示使用即可:

// 这里只是演示了取值动作..

@SpringBootTest
public class RedisTest {
​@Autowiredprivate RedisUtil redisUtil ;
​@Testpublic void stringTestByUtil(){Object a1 = redisUtil.getCacheObject("a1");System.out.println("a1 = " + a1);
​Map<String, Object> stu1 = redisUtil.getCacheMap("stu1");System.out.println("stu1 = " + stu1);
​List<Object> studentList = redisUtil.getCacheList("studentList");System.out.println("studentList = " + studentList);
​Set<Object> studentSet1 = redisUtil.getCacheSet("studentSet1");System.out.println("studentSet1 = " + studentSet1);
​}
}

八、Redis应用

8.1 存储登录状态

8.1.1 分析

需求: 实现用户没有登录时不可访问以及每1小时登录一次

思路:

  • 用户登录成功后,将用户信息存储到Redis中

    • 生成一个token当做key,用户信息当做value,并设置过期时间1小时

  • 并将这个token返回给前端

  • 前端登录成功后,从返回数据中取出token,存储到Vuex和Cookie中(Vue-admin-template架子是这么做的)

  • 后续前端每次发请求时,都会在请求头中携带这个token到后端

  • 后端设置拦截器,对接收的每个请求判断有无token

    • 无token说明没有登录,响应回前端让其重新登录

    • 有token,但是通过token从Redis中取不出数据,说明过期了,响应回前端让其重新登录

      • 至此: 思考一下,如何响应给前端让其重新登录? 前端后端要统一使用JSON交互(即统一返回对象R)的,拦截器中如何返回R?

      • 方案: 使用自定义异常类+全局异常处理

      • 思路: 拦截器中返回指定异常类,然后全局异常处理类中捕获这些异常,统一返回指定的状态码即可

      • 状态码多少? Vue-admin-template架子中设置了50008,50012,50014状态码

        • 50008: Illegal token;

        • 50012: Other clients logged in;

        • 50014: Token expired;

    • 有token,通过token从Redis中取出数据,则放行


总结: 整体思路就是: 登录时生成令牌,给到前端,前端每次携带令牌,后端对请求拦截实现鉴权

8.1.2 设置自定义异常类

设置一个没有登录异常类即可

package com.qf.ex;
​
/*** --- 天道酬勤 ---** @author QiuShiju* @desc 未登录异常*/
public class NoLoginException extends RuntimeException{
​// 为了接收状态码private int code;
​public int getCode() {return code;}
​public void setCode(int code) {this.code = code;}
​public NoLoginException(int code,String message){super(message);this.code = code;}
}

8.1.3 设置全局异常处理

package com.qf.util;import com.qf.ex.NoLoginException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** --- 天道酬勤 ---** @author QiuShiju* @desc 自定义全局异常处理类*/
@RestControllerAdvice
public class GlobalHandleException {@ExceptionHandler(NoLoginException.class)public R handlerException(Exception ex){System.out.println("出错啦!" + ex.getMessage());NoLoginException noLoginException = (NoLoginException) ex;// 返回状态码和错误信息return R.fail(noLoginException.getCode(),noLoginException.getMessage());}
}

8.1.4 登录时存储token

   @Autowiredprivate RedisUtil redisUtil;@PostMapping("/login")public R login(@RequestBody SysUser sysUser) {SysUser user = service.login(sysUser);if (user != null) {// 1 登录成功,生成令牌String token = UUID.randomUUID( ).toString( ).replace("-", "");// 2 已令牌为key,对象信息为value存储到redis// key形如: user:34j34h53j4hj36// key形如: user:56j747b65756lk// value是对象,已经配置value使用jackson2Json将对象转成JSON字符串redisUtil.setCacheObject("user:"+token,user,1, TimeUnit.MINUTES);HashMap<String, String> map = new HashMap<>( );// 3 将令牌返回前端map.put("token", token);return R.ok(map);}return R.fail( );}

8.1.5 设置拦截器

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {@Autowiredprivate RedisUtil redisUtil;// 登录成功后,将token发送给前端// 前端发送请求时,需要将token放到请求头中,发送给后台// 本例,从Authorization这个请求头中获取token值// 注意,需要将前端的请求头改变为Authorization@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("Authorization");if (token == null || "".equals(token)) {throw new NoLoginException(50008,"无效令牌,重新登录");}SysUser sysUser = redisUtil.getCacheObject("user:" + token);if (sysUser == null) {throw new NoLoginException(50014,"身份信息失效,重新登录");}return true;}
}

别忘了配置拦截器

package com.qf.config;import com.qf.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** --- 天道酬勤 ---** @author QiuShiju* @desc*/
@Configuration // 这个注解,让springboot框架知道,以下的这个类是提供配置
public class MyWebMvcInterceptorConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/sys/user/login");// 登录放行}
}

8.1.6 退出时销毁

/*** 退出登录*/
@PostMapping("/logout")
public R logout(HttpServletRequest request) {request.getSession().invalidate();String token = request.getHeader("Authorization");// 销毁redis中的tokenredisUtil.deleteObject("user:"+token);return R.ok(  );
}// ====================== 或者如下也行,不过得改前端  =======================
@GetMapping("/logout")
public R logout(String token) {redisUtil.deleteObject("user:"+token);return R.ok( );
}

8.1.7 请求测试

需要将请求头中的key修改为"Authorization"

使用接口工具和网页测试即可

8.2 存储手机验证码

略。。。

  • 前端设置输入框,按钮绑定事件,点击发请求到后端

  • 后端接收请求后,调用工具类(短信工具类),生成验证码,存Redis一份(设置过期时间5分钟),短信发一份

  • 收到短信后,输入验证码

  • 发请求,输入的验证码要和后端Redis中的验证码比较

    • 输入的验证码与Redis中的验证码不一致,验证码错了

    • 输入的验证码,Redis中没有验证码,说明过期了

    • 如果正常,返回

8.3 如何保证数据在数据库和redis缓存的一致性

方案1:先删除缓存,再处理数据库 [不推荐]

1 a用户 执行删除数据操作,先删除缓存,还没有执行删除数据库时

2 b用户 执行查询操作,发现缓存中没有数据,查询数据库中老的数据,将数据放入缓存

3 a用户 执行删除数据库的操作,这时,缓存和数据库数据不一致了

而且只要缓存没有过期,只要没有其他的修改数据库的操作,缓存和数据库会长时间不一致

方案2:先操作数据库,再删除缓存 [推荐]

1 a用户删除数据库,还没有删除缓存前

2 b用户查询数据,从缓存中获取老的数据,这时候缓存和数据库不一致

3 a用户删除缓存

4 c用户请求数据,发现缓存中数据不存在,查询数据库新数据,将数据写入缓存,这时,缓存中是最新数据

实现缓存和数据库的数据短时间不一致,只有b出现一次不一致的情况,影响小

其他方案:延迟双删等

a先删除缓存,操作数据库,间隔一定的时间,a再删除一次缓存

九、Redis缓存的面试问题

【Redis】什么是Redis缓存 雪崩、穿透、击穿?(一篇文章就够了)_redis 雪崩-CSDN博客

  • 缓存穿透: 查询一个根本就不存在的数据

  • 缓存击穿: 查询一个之前存在,但是现在过期了的数据

  • 缓存雪崩: Redis中大量时间过期销毁

  • 缓存倾斜

缓存穿透问题

缓存穿透

  • 问题:查询一个不存在的数据,由于缓存中没有该数据,导致每次请求都会去数据库查询,数据库压力增大。

  • 解决方案

    • 布隆过滤器:在缓存之前先通过布隆过滤器判断数据是否存在,如果不存在则直接返回,避免访问数据库。

      什么是布隆过滤器?如何使用?-腾讯云开发者社区-腾讯云 (tencent.com)

      利用布隆过滤器我们可以预先把数据查询的主键,比如用户 ID 或文章 ID 缓存到过滤器中。当根据 ID 进行数据查询的时候,我们先判断该 ID 是否存在,若存在的话,则进行下一步处理。若不存在的话,直接返回,这样就不会触发后续的数据库查询。需要注意的是缓存穿透不能完全解决,我们只能将其控制在一个可以容忍的范围内。
      

    • 缓存空值或默认值:对于不存在的数据,也在缓存中保存一个空值或默认值,并设置较短的过期时间,减少数据库查询压力。

    • 引入风控系统,对于频繁查询不存在的数据的请求进行限制或封禁。

缓存击穿问题

缓存击穿: 本来缓存中有对应的数据,但是缓存的数据 因为到期,需要去数据库中再次查询数据

  • 问题:当某个热点数据在缓存中过期或者不存在时,大量请求会直接访问数据库,导致数据库压力骤增。

  • 解决方案

    • 使用互斥锁(如分布式锁)来控制只有一个请求去数据库加载数据,其他请求等待。

    • 逻辑过期,不直接设置过期时间,而是用程序逻辑判断数据是否“过期”,减少因过期导致缓存击穿的情况。

    • 预先加载,对于热点数据,在其过期前主动进行加载,避免过期时刻的并发访问。

缓存雪崩问题

缓存雪崩问题:当缓存中大量的key 同时失效,此时大量的请求就会 穿过缓存层到达数据库,此时就会对数据库造成很大压力,数据库压力过大也会崩溃 ,此时就是缓存雪崩,由于缓存的失效 造成一系列的崩溃

缓存雪崩

  • 问题:当大量缓存数据同时过期或被删除时,大量请求会直接访问数据库,导致数据库压力骤增。

  • 解决方案

    • 添加随机过期时间:在设置缓存过期时间时,添加一定的随机时间,避免大量数据同时过期。

    • 使用分布式锁:在查询数据库时,使用分布式锁来避免并发查询导致的数据库压力增大。

    • 延迟双删策略:在更新数据时,先删除缓存中的数据,然后更新数据库。在更新数据库成功后,再次删除缓存中的数据,确保数据一致性。

    • 监控和告警:对Redis缓存系统进行监控和告警,及时发现和解决数据一致性问题。

缓存雪崩

缓存倾斜问题

缓存倾斜

  • 问题:某个热点数据被大量请求访问,导致该数据所在的Redis节点压力过大,甚至可能引发宕机。

  • 解决方案

    • 热点数据分散:将热点数据分散到多个Redis节点中,避免单一节点压力过大。

    • 使用多级缓存:除了Redis缓存外,还可以引入其他缓存层(如本地缓存、CDN等),将热点数据缓存到离用户更近的地方,减少Redis的访问压力。

    • 热点数据预处理:对于热点数据,可以提前进行预处理和计算,减少实时计算的压力。

    • 监控和告警:对热点数据的访问进行监控和告警,及时发现并解决潜在问题。

相关文章:

redis与springBoot整合

前提 要实现,使用Redis存储登录状态 需要一个完整的前端后端的项目 前端项目搭建 解压脚手架 安装依赖 配置请求代理 选做: 禁用EsLint语法检查 Vue Admin Template关闭eslint校验&#xff0c;lintOnSave&#xff1a;false设置无效解决办法_lintonsave: false-CSDN博客 …...

YoloV9改进策略:BackBone改进|CAFormer在YoloV9中的创新应用,显著提升目标检测性能

摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入YoloV9模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…...

消防应急物资仓库管理系统

集驰电子消防装备仓库管理系统(DW-S302系统)是一套成熟系统&#xff0c;依托3D技术、大数据、RFID技术、数据库技术、对装备器材进行统一管理&#xff0c;以RFID射频识别技术为核心&#xff0c;构建以物资综合管理为基础&#xff0c;智能分析定位为主要特色功能的装备器材库综合…...

【论文阅读】Semi-Supervised Few-shot Learning via Multi-Factor Clustering

通过多因素聚类的半监督小样本学习 引用&#xff1a;Ling J, Liao L, Yang M, et al. Semi-supervised few-shot learning via multi-factor clustering[C]//Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition. 2022: 14564-14573. 论文地址…...

第十三章 RabbitMQ之消息幂等性

目录 一、引言 二、消息幂等解决方案 2.1. 方案一 2.2. 方案二 一、引言 幂等是一个数学概念&#xff0c;用函数表达式来描述是这样的&#xff1a;f(x) f(f(x)) 。在程序开发中&#xff0c;则是指同一个业务&#xff0c;执行一次或多次对业务状态的影响是一致的。有些业务…...

tpcms-master.zip

网盘&#xff1a;https://pan.notestore.cn/s.html?id34https://pan.notestore.cn/s.html?id34...

Spring国际化和Validation

SpringBoot国际化和Validation融合 场景 在应用交互时&#xff0c;可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数&#xff0c;后端获取参数&#xff0c;根据不同得locale来获取不同得语言得文本信息返回给前端。 实现原…...

②EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 Modbus TCP (接上一章&#xff09; GW系列型号 配置说明 上载 网线连接电脑到模块上的 WEB 网页设置网口&#…...

【华为HCIP实战课程八】OSPF网络类型及报文类型详解,网络工程师

一、点到点网络类型 1、两台路由器 2、支持广播、组播 P2P(PPP、HDLC、帧中继子接口) 我们需要三个维度考虑 A、是否自动通过组播发现邻居 B、时间(Hello和Dead) C、DR和BDR----多点接入网络需要用到(广播和NBMA) 点到点是组播自动发现邻居,Hello 10S,Dead 40S…...

信息安全工程师(28)机房安全分析与防护

前言 机房安全分析与防护是一个复杂而细致的过程&#xff0c;涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵&#xff1a;未经授权的人员可能通过门窗、通风口等进入机房&#xff0c;…...

大数据处理从零开始————9.MapReduce编程实践之信息过滤之学生成绩统计demo

1.项目目标 1.1 需求概述 现在我们要统计某学校学生的成绩信息&#xff0c;筛选出成绩在60分及以上的学生。 1.2 业务分析 如果我们想实现该需求&#xff0c;可以通过编写一个MapReduce程序&#xff0c;来处理包含学生信息的文本文件&#xff0c;每行包含【学生的姓名&#x…...

自动化测试 | 窗口截图

driver.get_screenshot_as_file 是 Selenium WebDriver 的一个方法&#xff0c;它允许你将当前浏览器窗口&#xff08;或标签页&#xff09;的截图保存为文件。这个方法对于自动化测试中的截图验证非常有用&#xff0c;因为它可以帮助你捕获测试执行过程中的页面状态。 以下是…...

初中数学网上考试系统的设计与实现(论文+源码)_kaic

初中数学网上考试系统的设计与实现 学生&#xff1a; 指导教师&#xff1a; 摘 要&#xff1a;科技在人类的历史长流中愈洗愈精&#xff0c;不仅包括人们日常的生活起居&#xff0c;甚至还包括了考试的变化。之前的考试需要大量的时间和精力&#xff0c;组织者还需要挑选并考查…...

关系运算(3)

关系代数 昨天讲完附加关系代数运算&#xff0c;今天讲扩展关系代数运算。 扩展代数运算 正如其名&#xff0c;这种运算定义了前面基本和附加都没有的运算。 去重运算 可以将关系R中跟查询条件相关但是形成了重复的元组去除&#xff0c;只保留查询结果&#xff08;简洁&…...

tp6的系统是如何上架的

TP6&#xff08;ThinkPHP6&#xff09;的系统上架过程&#xff0c;通常指的是将基于ThinkPHP6框架开发的应用程序部署到生产环境&#xff0c;并使其可以通过互联网访问。以下是一个大致的上架流程&#xff0c;包括准备工作、部署步骤以及后续维护等方面&#xff1a; 一、准备工…...

Vue:开发小技巧

目录 1. Table表格偏移 1. Table表格偏移 通过设置自小的宽度进行控制 :min-width <el-table-column label"操作" align"center" class-name"small-padding fixed-width" fixed"right" min-width"150px"><templa…...

力扣之1369.获取最近第二次的活动

题目&#xff1a; sql建表语句 Create table If Not Exists UserActivity (username varchar(30), activity varchar(30), startDate date, endDate date); Truncate table UserActivity; insert into UserActivity (username, activity, startDate, endDate) values (Alic…...

Python 和 Jupyter Kernel 版本不一致

使用jupyter notebook时明明已经安装了包&#xff0c;但是导入时提示&#xff1a; ModuleNotFoundError: No module named ptitprince 1、检查安装环境 !pip show ptitprince Name: ptitprince Version: 0.2.7 Summary: A Python implementation of Rainclouds, originally…...

Android常用布局

目录 布局文件中常见的属性 1. 基本布局属性 1&#xff09;android:layout_width 2&#xff09;android:layout_height 3&#xff09;android:layout_margin 4&#xff09;android:padding 2. 线性布局 (LinearLayout) 属性 1&#xff09;android:orientation 2&#xff09;and…...

初级网络工程师之从入门到入狱(五)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、链路聚合1.1、手动进行链路聚合1.1.1、 拓扑图&#xff1a;1.1.2、 LSW11.1.3、 LSW2 1.2、…...

JavaScript轮播图实现

这个代码创建了一个简单的轮播图&#xff0c;可以通过点击左右箭头或自动播放来切换图片。 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>js轮播图练习</title><style>.box {width: 60vw;height: 500px;m…...

【LLM开源项目】LLMs-开发框架-Langchain-Tutorials-Basics-v2.0

【1】使用LCEL构建简单的LLM应用程序(Build a Simple LLM Application with LCEL) https://python.langchain.com/docs/tutorials/llm_chain/ 如何使用LangChain构建简单的LLM应用程序。功能&#xff1a;将把文本从英语翻译成另一种语言。 实现&#xff1a;LLM调用加上一些提…...

Python 爬取天气预报并进行可视化分析

今天&#xff0c;我们就来学习如何使用 Python 爬取天气预报数据&#xff0c;并用数据可视化的方式将未来几天的天气信息一目了然地展示出来。 在本文中&#xff0c;我们将分三步完成这一任务&#xff1a; 使用 Python 爬取天气数据数据解析与处理用可视化展示天气趋势 让我…...

最左侧冗余覆盖子串

题目描述 给定两个字符串 s1 和 s2 和正整数 k&#xff0c;其中 s1 长度为 n1&#xff0c;s2 长度为 n2。 在 s2 中选一个子串&#xff0c;若满足下面条件&#xff0c;则称 s2 以长度 k 冗余覆盖 s1 该子串长度为 n1 k 该子串中包含 s1 中全部字母 该子串每个字母出现次数…...

性能测试-JMeter(2)

JMeter JMeter断言响应断言JSON断言断言持续时间 JMeter关联正则表达式提取器正则表达式正则表达式提取器 XPath提取器JSON提取器 JMeter属性JMeter录制脚本 JMeter断言 断言&#xff1a;让程序自动判断预期结果和实际结果是否一致 提示&#xff1a; -Jmeter在请求的返回层面有…...

芯课堂 | Synwit_UI_Creator(μgui)平台之图像处理篇

今天小编给大家介绍的是UI_Creator&#xff08;μgui&#xff09;平台下关于图像处理的选项。 UI_Creator&#xff08;μgui&#xff09;平台图片类控件有图像控件和分级图像控件&#xff0c;均包含以下选项&#xff1a; 1、消除水波纹&#xff1a; 由于16位真彩色&#xff08…...

QT C++ 软键盘/悬浮键盘/触摸屏键盘的制作

目录 1、前言 2、界面设计 3、英文、数字的输入 4、符号的输入 5、中文的输入 6、中文拼音库的选择 7、其他 8、结语 1、前言 使用QT C在带显示器的Linux系统 开发板上&#xff08;树莓派等&#xff09;编写操作UI界面时&#xff0c;很多时候都需要一个软键盘来输入文字…...

element-ui点击文字查看图片预览功能

今天做一个点击文字查看图片的功能&#xff0c;大体页面长这样子&#xff0c;点击查看显示对应的图片 引入el-image-viewer&#xff0c;点击的文字时候设置图片预览组件显示并传入图片的地址 关键代码 <el-link v-if"scope.row.fileList.length > 0" type&…...

SpringBoot集成Redis使用Cache缓存

使用SpringBoot集成Redis使用Cache缓存只要配置相应的配置类&#xff0c;然后使用Cache注解就能实现 RedisConfig配置 新建RedisConfig配置类 package com.bdqn.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annota…...

【瑞萨RA8D1 CPK开发板】lcd显示

1.8寸lcd使用gpio模拟spi驱动 由于板子引出的接口限制&#xff0c;故使用gpio模拟spi驱动中景园的1.8寸lcd 1.77寸液晶屏 1.8寸TFT LCD SPI TFT彩屏st7735驱动128x160高清屏-淘宝网 (taobao.com) 使用RASC 的gpio配置 根据厂家提供的驱动文件移植 #define LCD_SCLK_Clr() g…...