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校验,lintOnSave:false设置无效解决办法_lintonsave: false-CSDN博客 …...
YoloV9改进策略:BackBone改进|CAFormer在YoloV9中的创新应用,显著提升目标检测性能
摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入YoloV9模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…...
消防应急物资仓库管理系统
集驰电子消防装备仓库管理系统(DW-S302系统)是一套成熟系统,依托3D技术、大数据、RFID技术、数据库技术、对装备器材进行统一管理,以RFID射频识别技术为核心,构建以物资综合管理为基础,智能分析定位为主要特色功能的装备器材库综合…...
【论文阅读】Semi-Supervised Few-shot Learning via Multi-Factor Clustering
通过多因素聚类的半监督小样本学习 引用: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. 方案二 一、引言 幂等是一个数学概念,用函数表达式来描述是这样的:f(x) f(f(x)) 。在程序开发中,则是指同一个业务,执行一次或多次对业务状态的影响是一致的。有些业务…...
tpcms-master.zip
网盘:https://pan.notestore.cn/s.html?id34https://pan.notestore.cn/s.html?id34...
Spring国际化和Validation
SpringBoot国际化和Validation融合 场景 在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。 实现原…...
②EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关
EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 Modbus TCP (接上一章) GW系列型号 配置说明 上载 网线连接电脑到模块上的 WEB 网页设置网口&#…...
【华为HCIP实战课程八】OSPF网络类型及报文类型详解,网络工程师
一、点到点网络类型 1、两台路由器 2、支持广播、组播 P2P(PPP、HDLC、帧中继子接口) 我们需要三个维度考虑 A、是否自动通过组播发现邻居 B、时间(Hello和Dead) C、DR和BDR----多点接入网络需要用到(广播和NBMA) 点到点是组播自动发现邻居,Hello 10S,Dead 40S…...
信息安全工程师(28)机房安全分析与防护
前言 机房安全分析与防护是一个复杂而细致的过程,涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵:未经授权的人员可能通过门窗、通风口等进入机房,…...
大数据处理从零开始————9.MapReduce编程实践之信息过滤之学生成绩统计demo
1.项目目标 1.1 需求概述 现在我们要统计某学校学生的成绩信息,筛选出成绩在60分及以上的学生。 1.2 业务分析 如果我们想实现该需求,可以通过编写一个MapReduce程序,来处理包含学生信息的文本文件,每行包含【学生的姓名&#x…...
自动化测试 | 窗口截图
driver.get_screenshot_as_file 是 Selenium WebDriver 的一个方法,它允许你将当前浏览器窗口(或标签页)的截图保存为文件。这个方法对于自动化测试中的截图验证非常有用,因为它可以帮助你捕获测试执行过程中的页面状态。 以下是…...
初中数学网上考试系统的设计与实现(论文+源码)_kaic
初中数学网上考试系统的设计与实现 学生: 指导教师: 摘 要:科技在人类的历史长流中愈洗愈精,不仅包括人们日常的生活起居,甚至还包括了考试的变化。之前的考试需要大量的时间和精力,组织者还需要挑选并考查…...
关系运算(3)
关系代数 昨天讲完附加关系代数运算,今天讲扩展关系代数运算。 扩展代数运算 正如其名,这种运算定义了前面基本和附加都没有的运算。 去重运算 可以将关系R中跟查询条件相关但是形成了重复的元组去除,只保留查询结果(简洁&…...
tp6的系统是如何上架的
TP6(ThinkPHP6)的系统上架过程,通常指的是将基于ThinkPHP6框架开发的应用程序部署到生产环境,并使其可以通过互联网访问。以下是一个大致的上架流程,包括准备工作、部署步骤以及后续维护等方面: 一、准备工…...
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.获取最近第二次的活动
题目: 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时明明已经安装了包,但是导入时提示: 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)android:layout_width 2)android:layout_height 3)android:layout_margin 4)android:padding 2. 线性布局 (LinearLayout) 属性 1)android:orientation 2)and…...
初级网络工程师之从入门到入狱(五)
本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、链路聚合1.1、手动进行链路聚合1.1.1、 拓扑图:1.1.2、 LSW11.1.3、 LSW2 1.2、…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...
Appium下载安装配置保姆教程(图文详解)
目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...
SQL进阶之旅 Day 22:批处理与游标优化
【SQL进阶之旅 Day 22】批处理与游标优化 文章简述(300字左右) 在数据库开发中,面对大量数据的处理任务时,单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”,深入探讨如何通过批量操作和游标技术提…...
C++ 类基础:封装、继承、多态与多线程模板实现
前言 C 是一门强大的面向对象编程语言,而类(Class)作为其核心特性之一,是理解和使用 C 的关键。本文将深入探讨 C 类的基本特性,包括封装、继承和多态,同时讨论类中的权限控制,并展示如何使用类…...
