• SpringBoot整合Redis缓存


    SpringBoot整合Redis缓存

    一、缓存概念知识

    1、是什么缓存

    日常生活中经常会听到缓存这个词,那到底什么是缓存呢?其实缓存就是数据交换的缓冲区(称作Cache),是临时存贮数据(使用频繁的数据)的地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行;如果找不到则去数据库中查找。

    缓存的本质就是用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库读取最新的数据,减少数据库IO,减轻服务器压力,减少网络延迟,加快页面打开速度。

    2、缓存的优缺点

    优点:

    • 加快了响应速度
    • 减少了对数据库的读操作,数据库的压力降低

    缺点:

    • 内存容量相对硬盘小

    • 缓存中的数据可能与数据库中数据不一致

    • 内存断电就会清空数据,造成数据丢失

    3、为什么使用缓存

    一般在远端服务器上,考虑到客户端请求量多,某些数据请求量大,这些热点数据要频繁的从数据库中读取,给数据库造成压力,导致响应客户端较慢。所以,在一些不考虑实时性的数据中,经常将这些数据存在内存中,当请求时候,能够直接读取内存中的数据及时响应。

    缓存主要有解决高性能与高并发与减少数据库压力。缓存本质就是将数据存储在内存中,当数据没有发生本质变化的时候,应尽量避免直接连接数据库进行查询,因为并发高时很可能会将数据库压塌,而是应去缓存中读取数据,只有缓存中未查找到时再去数据库中查询,这样就大大降低了数据库的读写次数,增加系统的性能和能提供的并发量。

    二、Redis概念知识

    1、Redis简介

    Redis 是一个高性能的 Key-Value 开源数据库, 是一个非关系型的数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案。但它不能替代关系型数据库,只能作为特定环境下的扩充。

    2、为什么用Redis作为缓存

    • 支持高可用: Redis 支持 masterslave 主从机制、sentinal 哨兵模式、cluster 集群模式,大大保证了 Redis 运行的稳定和高可用性。
    • 支持多种数据结构: Redis 不仅支持简单的 Key/Value 类型的数据,同时还提供 list、set、zset、hash 等数据结构的存储。
    • 支持数据持久化: 可以将内存中的数据持久化在磁盘中,当宕机或者故障重启时,可以再次加载进如 Redis,从而不会或减少数据的丢失。
    • 生态完善: Redis 已成为业界内缓存的首选目标,所以很多语言和工具对其支持。

    3、Redis 支持的数据类型

    Redis 支持的数据结构类型包括:

    • 字符串(string)
    • 哈希表(hash)
    • 列表(list)
    • 集合(set)
    • 有序集合(zset)

    为了保证读取效率,Redis 把数据对象存储在内存中,并支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。

    redis的特性决定了它的功能,它可以用来做以下这些事情!

    1. 排行榜,利用zset可以方便的实现排序功能
    2. 计数器,利用redis中原子性的自增操作,可以统计到阅读量,点赞量等功能
    3. 简单消息队列,list存储结构,满足先进先出的原则,可以使用lpush/rpop或rpush/lpop实现简单消息队列
    4. session共享,分布式系统中,可以利用redis实现session共享。spring官方提供的分布式解决方案Spring Session就是利用redis 实现的

    4、Redis缓存常见问题

    1. 缓存穿透

    在这里插入图片描述

    缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

    几种解决办法:

    • 缓存空值,在从 DB 查询对象为空时,也要将空值存入缓存,具体的值需要使用特殊的标识, 能和真正缓存的数据区分开,另外将其过期时间设为较短时间。
    • 使用布隆过滤器,布隆过滤器能判断一个 key 一定不存在(不保证一定存在,因为布隆过滤器结构原因,不能删除,但是旧值可能被新值替换,而将旧值删除后它可能依旧判断其可能存在),在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。

    2. 缓存击穿

    在这里插入图片描述

    缓存击穿: 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

    几种解决办法:

    • 设置二级缓存,或者设置热点缓存永不过期,需要根据实际情况进行配置。
    • 使用互斥锁,在执行过程中,如果缓存过期,那么先获取分布式锁,在执行从数据库中加载数据,如果找到数据就存入缓存,没有就继续该有的动作,在这个过程中能保证只有一个线程操作数据库,避免了对数据库的大量请求。

    3. 缓存雪崩

    在这里插入图片描述

    缓存雪崩: 当缓存服务器重启、或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。

    几种解决办法:

    • 缓存组件设计高可用,缓存高可用是指,存储缓存的组件的高可用,能够防止单点故障、机器故障、机房宕机等一系列问题。例如 Redis sentinel 和 Redis Cluster,都实现了高可用。
    • 请求限流与服务熔断降级机制,限制服务请求次数,当服务不可用时快速熔断降级。
    • 设置缓存过期时间一定的随机分布,避免集中在同一时间缓存失效。
    • 定时更新缓存策略,对于实时性要求不高的数据,定时进行更新。

    4. 缓存一致性

    使用缓存很大可能导致数据不一致问题,如下:

    • 更熟数据库成功 -> 更新缓存失败 -> 数据不一致
    • 更新缓存成功 -> 更新数据库失败 -> 数据不一致
    • 更新数据库成功 -> 淘汰缓存失败 -> 数据不一致
    • 淘汰缓存成功 -> 更新数据库失败 -> 查询缓存mis

    所以使用缓存时候,应该结合实际情况,考虑缓存的数据是否有一致性需求。

    三、SpringBoot整合redis

    1、使用redis缓存

    1. 引入redis依赖

    在pom.xml文件中引入Redis依赖,如下

    
        org.springframework.boot
        spring-boot-starter-data-redis
    
    
    • 1
    • 2
    • 3
    • 4

    2. 修改项目启动类

    增加注解@EnableCaching,开启缓存功能,如下:

    package com.demo;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cache.annotation.EnableCaching;
    
    @SpringBootApplication
    @MapperScan("com.demo")
    @EnableCaching
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3. 配置redis数据库

    在application.properties中配置Redis连接信息,如下:

    spring:
      redis:
        # redis库
        database: 0
        # redis 服务器地址
        host: localhost
        # redis 端口号
        port: 6379
        # redis 密码
        password:
        # 连接超时时间(毫秒)
        timeout: 1000
        lettuce:
          pool:
            # 连接池最大链接数(负数表示没有限制)
            max-active: 8
            # 连接池最大阻塞等待时间(负数表示没有限制)
            max-wait: -1
            # 连接池最大空闲连接数
            max-idle: 8
            # 连接池最小空闲连接数
            min-idle: 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4. 创建redis配置类

    新建Redis缓存配置类,如下:

    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.*;
    
    import java.time.Duration;
    
    /**
     * Redis 配置类
     */
    @Configuration
    public class RedisConfig {
    
        /**
         * 配置缓存管理器
         * @param factory Redis 线程安全连接工厂
         * @return 缓存管理器
         */
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory factory) {
            // 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
            RedisCacheConfiguration cacheConfig1 = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置过期时间 10 分钟
                    .entryTtl(Duration.ofMinutes(10))
                    // 设置缓存前缀
                    .prefixKeysWith("cache:user:")
                    // 禁止缓存 null 值
                    .disableCachingNullValues()
                    // 设置 key 序列化
                    .serializeKeysWith(keyPair())
                    // 设置 value 序列化
                    .serializeValuesWith(valuePair());
    
            RedisCacheConfiguration cacheConfig2 = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置过期时间 30 秒
                    .entryTtl(Duration.ofSeconds(30))
                    .prefixKeysWith("cache:admin:")
                    .disableCachingNullValues()
                    .serializeKeysWith(keyPair())
                    .serializeValuesWith(valuePair());
    
            // 返回 Redis 缓存管理器
            return RedisCacheManager.builder(factory)
                    .withCacheConfiguration("user", cacheConfig1)
                    .withCacheConfiguration("admin", cacheConfig2)
                    .build();
        }
    
        /**
         * 配置键序列化
         * @return StringRedisSerializer
         */
        private RedisSerializationContext.SerializationPair keyPair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
        }
    
        /**
         * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
         * @return GenericJackson2JsonRedisSerializer
         */
        private RedisSerializationContext.SerializationPair valuePair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    5. 操作redis

    SpringBoot提供了两个bean来操作redis,分别是RedisTemplateStringRedisTemplate,这两者的主要区别如下:

    RedisTemplate使用的是JdkSerializationRedisSerializer ,存入数据会将数据先序列化成字节数组然后在存入Redis数据库;

    StringRedisTemplate使用的是StringRedisSerializer。

    示例如下:

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userServer;
        
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
        
        /**
         * 查询所有课程
         */
        @RequestMapping("/allCourses")
        public String findAll() {
            List courses = userServer.findAll();
            
            // 将查询结果写入redis缓存
            stringRedisTemplate.opsForValue().set("hot", String.valueOf(courses));
            
            // 读取redis缓存
            System.out.println(stringRedisTemplate.opsForValue().get("courses"));
    
            return "ok";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2、使用 SpringCache 的注解

    1. 注解说明

    • @CacheConfig: 一般配置在类上,指定缓存名称,这个名称是和上面“置缓存管理器”中缓存名称的一致。
    • @Cacheable: 用于对方法返回结果进行缓存,如果已经存在该缓存,则直接从缓存中获取,缓存的key可以从入参中指定,缓存的 value 为方法返回值。
    • @CachePut: 无论是否存在该缓存,每次都会重新添加缓存,缓存的key可以从入参中指定,缓存的value为方法返回值,常用作于更新。
    • @CacheEvict: 用于清除缓存
    • @Caching: 用于一次性设置多个缓存。

    2. 常用注解配置参数

    • value: 缓存管理器中配置的缓存的名称,这里可以理解为一个组的概念,缓存管理器中可以有多套缓存配置,每套都有一个名称,类似于组名,这个可以配置这个值,选择使用哪个缓存的名称,配置后就会应用那个缓存名称对应的配置。
    • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
    • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。
    • unless: 不缓存的条件,和 condition 一样,也是 SpEL 编写,返回 true 或者 false,为 true 时则不进行缓存。

    3. 自动缓存

    @Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

    如果添加了@Cacheable注解,那么方法被调用后,值会被存入redis,下次再调用的时候会直接从redis中取值返回。

    @Service
    @CacheConfig(cacheNames = "user")
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
        
        // 获取全部用户
        @Cacheable(key = "'allUsers'", unless = "#result==null")
        public List findAll() {
            return userMapper.allUsers();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    四、案例说明

    1、创建实体类(model层)

    public class User {
        private String username;
        private String password;    
        
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、创建接口(mapper层)

    @Component
    public interface UserMapper {
    
        // 查询所有用户
        @Select("select * from user_tbl")
        List allUsers();
        
        // 更新用户信息
        @Update("update user_tbl set password=#{password} where username=#{username};")
        boolean updateUser(String username, String password);
    
        // 删除用户
        @Select("delete from user_tbl where username = #{username};")
        Integer delUser(String username);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、创建服务类(service层)

    @Service
    @CacheConfig(cacheNames = "user")
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        // 查询全部用户
        @Cacheable(key = "'allUsers'", unless = "#result==null")
        public List allUsers() {
            return userMapper.allUsers();
        }
            
        // 更新用户信息
        @CachePut(key = "#user.username")
        public void updateUser(String username, String password) {
            userMapper.updateUser(username, password);
        }
        
        // 删除用户
        @CacheEvict(key = "#username")
        public void delUser(String username) {
            userMapper.delUser(username);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4、创建控制器(controller层)

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userServer;
        
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
    
        /**
         * 查询所有用户
         */
        @RequestMapping("/allUsers")
        public String allUsers() {
            userServer.allUsers();
            return "ok";
        }
        
        
        /**
         * 更新用户信息
         */
        @RequestMapping("/updateUser")
        public String updateUser() {
            String username = "tom";
            String password = "abc123";
            userServer.updateUser(username,password);
            return "ok";
        }
        
        
        /**
         * 删除用户
         */
        @RequestMapping("/delUser")
        public String delUser() {
            String username = "tom"; 
            userServer.delUser(username);
            return "ok";
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    LeetCode 146:LRU 缓存
    图像变换算法
    备战金九银十,Java 研发面试题整理 PDF,走到哪刷
    Redis -- 主从
    kafka的请求处理机制
    C++入门指南:类和对象总结笔记(下)
    JDBC和数据库连接池
    集线器-交换机-路由器
    eslint prettier husky代码规范配置
    uniapp 树状数据无限极 进行展示并选择
  • 原文地址:https://blog.csdn.net/m0_67402774/article/details/126114968