image.png

1、SpringBoot整合Redis

1.1引入Redis依赖

  1. <!-- Redis依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
  6. <!--Redis连接池的依赖-->
  7. <dependency>
  8. <groupId>org.apache.commons</groupId>
  9. <artifactId>commons-pool2</artifactId>
  10. </dependency>

1.2配置Redis连接信息

  1. spring:
  2. redis: # Redis配置
  3. host: 119.23.106.219
  4. port: 6379
  5. password: 123456
  6. lettuce: # springboot默认引入的是lettuce连接池,如果要是用jedis连接池需要引入jedis依赖
  7. pool:
  8. max-active: 8 # 最大连接
  9. max-idle: 8 # 最大空闲连接
  10. min-idle: 0 # 最小空闲连接
  11. max-wait: 1000ms # 连接等待时间

1.3编写Redis测试类

  1. @SpringBootTest
  2. class BlogApplicationTests {
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. @Test
  6. void testString() {
  7. redisTemplate.opsForValue().set("ycl","杨存乐");
  8. Object name = redisTemplate.opsForValue().get("ycl");
  9. System.out.println(name);
  10. }
  11. }

image.png
但是用Redis连接工具查看,看到的是序列化的
image.png
点开源码看一下
image.png

  1. if (this.defaultSerializer == null) {
  2. this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
  3. }

如果没有序列化器,就用JKD的序列化器

1.4给RedisTemplate的key和values设置序列化

  1. @Configuration
  2. public class RedisConfig {
  3. /**
  4. * 给RedisTemplate的key和values设置序列化
  5. * @param connectionFactory
  6. * @return
  7. */
  8. @Bean
  9. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
  10. // 创建RedisTemplate对象
  11. RedisTemplate<String, Object> template = new RedisTemplate<>();
  12. // 设置连接工厂
  13. template.setConnectionFactory(connectionFactory);
  14. // 创建JSON序列化工具
  15. GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
  16. // 设置Key的序列化
  17. template.setKeySerializer(RedisSerializer.string());
  18. template.setHashKeySerializer(RedisSerializer.string());
  19. // 设置Value的序列化
  20. template.setValueSerializer(jsonRedisSerializer);
  21. template.setHashValueSerializer(jsonRedisSerializer);
  22. // 返回
  23. return template;
  24. }
  25. }

1.5测试序列化是否成功

image.png
去控Redis连接工具查看
image.png
序列化成功

1.6测试存入一个对象

  1. /**
  2. * 测试存入一个对象
  3. */
  4. @Test
  5. void testUser() {
  6. User user = new User();
  7. user.setUsername("杨存乐");
  8. user.setEmail("357487967@qq,com");
  9. redisTemplate.opsForValue().set("user:ycl",user);
  10. User user1 = (User) redisTemplate.opsForValue().get("user:ycl");
  11. System.out.println(user1.toString());
  12. }

查看
image.png

1.7使用StringRedisTemplate操作数据

  1. @Autowired
  2. private StringRedisTemplate stringRedisTemplate;
  3. /**
  4. * 测试存入一个对象用StringRedisTemplate操作
  5. */
  6. @Test
  7. void testUser2() {
  8. User user = new User();
  9. user.setUsername("杨存乐");
  10. user.setEmail("357487967@qq,com");
  11. String userString = JSON.toJSONString(user);
  12. stringRedisTemplate.opsForValue().set("user:str:ycl", userString);
  13. String s = stringRedisTemplate.opsForValue().get("user:str:ycl");
  14. User user1 = JSON.parseObject(userString, User.class);
  15. System.out.println(user1);
  16. }

引入fastJSON依赖,用fastJSON手动序列化JSON

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.71</version>
  5. </dependency>

image.png

2、缓存穿透

缓存穿透的原因:是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,给数据库带来巨大的压力。
常见的两种解决方案:缓存空对象 布隆过滤

2.1缓存空对象

先查缓存,缓存没有查数据库,数据库再没有,缓存一个null对象(避免请求一直访问数据库),并设置一个过期时间(避免无用数据过多的占用Redis内存)
image.png
优点:实现简单,维护方便
缺点:额外的内存消耗可能造成短期的不一致

2.2布隆过滤

image.png
优点:内存占用较少,没有多余key
缺点:实现复杂存在误判可能

3、缓存雪崩

缓存雪崩的原因:在同一时间大量缓存的key失效或者Redis服务宕机,导致大量请求到达数据库,给数据库带来巨大的压力。
解决方案:

3.1给不同的key的过期时间设置随机值(避免在同一时间大量key过期)

3.2Redis做集群(防止Redis宕机)

3.3给缓存业务添加降级或限流策略

3.4给业务增加多级缓存

4、缓存击穿

缓存击穿的原因:一个被高频访问并且缓存重建业务比较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大的压力
常见的解决方案:互斥锁、逻辑过期

5、博客添加缓存

image.png

  1. @Override
  2. @Transactional
  3. public Blog queryBlog(Integer id) {
  4. // 先从Redis中查询
  5. Blog blog = redisUtil.getObject(RedisKeyConstants.Blog_INFO_KEY + id, Blog.class);
  6. if (ObjectUtil.isNotEmpty(blog)) {
  7. return blog;
  8. }
  9. // 从数据库查
  10. blog = getById(id);
  11. if (ObjectUtil.isNotEmpty(blog)) {
  12. // 写入Redis
  13. redisUtil.set(RedisKeyConstants.Blog_INFO_KEY + id, blog);
  14. }
  15. return blog;
  16. }

6、缓存更新策略

image.png
在项目中我们采用主动更新策略
image.png
主动更新又分为直接更新缓存和直接删除缓存

6.1直接更新缓存

好处就是下次查到的是最新的数据
缺点就是每次更新都有对缓存的操作,如果两次更新中没人访问,对缓存的无效写操作较多

6.2直接删除缓存

更新数据库时让缓存失效,查询时再更新缓存

6.2.1先删除缓存,再更新数据库

image.png
由于操作缓存比更新数据库快,如果在更新的过程中,遇到查询缓存的线程,就会导致写入缓存的是更新前的数据,这样会导致数据库和缓存的数据不一致。

6.2.2先更新数据库,再删除缓存

image.png
由于写缓存比更新数据库要快得多,这种出现异常的情况非常少,所以采用这种。
image.png

  1. @Override
  2. @Transactional
  3. public Boolean updateBlog(Blog blog) {
  4. boolean b = updateById(blog);
  5. // 判断缓存key是否存在
  6. if (redisUtil.exists(RedisKeyConstants.Blog_INFO_KEY + blog.getId())) {
  7. // 若存在,删除缓存
  8. redisUtil.deleteKey(RedisKeyConstants.Blog_INFO_KEY + blog.getId());
  9. }
  10. return b;
  11. }

6.3延时双删

image.png
大概就是这么个意思,理解就行。