Springboot 集成Redis

99 阅读5分钟

Redis 概念

Redis(Remote Dictionary Service),即远程字段服务,是一个单线程+多路IO复用技术,多路复用是指使用一个线程来检测文件描述符(socket)的就绪技术,开源的、Nosql、基于内存的key-value数据库。

  • 默认16个数据库,类似数组的下标从0开始,初始默认使用0数据库。
  • 统一密码管理,所有的库密码相同。

redis五大数据类型

  • 字符串
  • list 列表
  • Set 集合
  • Hash 哈希表
  • Zet sort 有序集合

部署

mkdir -p /blessing/redis/data
mkdir -p /blessing/redis/conf
# --requirepass "DawnSilverGravel" redis密码
# --appendonly yes 开启持久化
docker run -itd \
    --restart=always \
    --name redis \
    -p 6379:6379 \
    -v /blessing/redis/data:/data \
    -v /blessing/redis/conf/reids.conf:/etc/redis/redis.conf \
    redis:7.0 redis-server /etc/redis/redis.conf \
    --requirepass "DawnSilverGravel" \
    --appendonly yes

相关类源码

引入依赖

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

RedisTemplate类是Springboot支持Redisd的核心类。在给定对象和Redis存储中的底层二进制数据之间执行自动序列化/反序列化。默认情况下,它对其对象使用Java序列化(通过JdkSerializationRedisSerializer)。对于字符串密集型操作,考虑专用的StringRedisTemplate

RedisTemplate类继承图如下:

Pasted image 20230803193736.png

由图可知:RedisTemplate实现了InitializingBean接口,所以实例化的时候调用afterPropertiesSet进行初始化,当没有设置序列化类参数的时候,默认使用JdkSerializationRedisSerializer Pasted image 20230803195307.png

redis 序列化归类为一下四种:

  • JDK 序列化方式 (默认)
  • String 序列化方式
  • JSON 序列化方式 (常用)
  • XML 序列化方式

Pasted image 20230803200159.png

spring-boot-starter-data-redis 默认注入RedisTemplateStringRedisTemplateBean,具体配置在RedisAutoConfiguration类中。 Pasted image 20230803212516.png 它们生效的条件是:没有注入同名的bean以及只有一个RedisConnectionFactory实例的时候。

RedisConnectionFactory由注解@Import注入的LettuceConnectionConfigurationJedisConnectionConfiguration来配置,优先LettuceConnectionConfiguration,内部创建的条件如下;

Pasted image 20230803213052.png

  • Lettuce:是一个基于Netty的开源连接器。
  • Jedis:是一个社区驱动的连接器。

RedisTemplate模版通用的操作API有如下:

Pasted image 20230803215837.png

集成使用

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.13</version>
</parent>

<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-redis</artifactId>  
    </dependency>  
    <dependency>  
        <groupId>redis.clients</groupId>  
        <artifactId>jedis</artifactId>  
        <version>3.8.0</version>  
    </dependency>  
</dependencies>

application.yml

server:  
  port: 8900  
spring:  
  redis:  
    port: 6379  
    host: 192.168.107.141  
    password: DawnSilverGravel  
    database: 0  
    lettuce:  
      pool:  
        # 最大连接数 默认为8  
        max-active: 10  
        # 最大阻塞等待时间,负数表示没限制  
        max-wait: -1  
        # 最大空闲默认 8      
        max-idle: 10        
        # 最小空闲  
        min-idle: 0  
    # 连接超时时间  
    timeout: 60000

Configuration

@Configuration  
public class RedisConfiguration {  
  
    @Resource  
    private RedisProperties redisProperties;  
  
    @Bean  
    public RedisConnectionFactory lettuceConnectionFactory() {  
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();  
        redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());  
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());  
        redisStandaloneConfiguration.setPassword(redisProperties.getPassword());  
        redisStandaloneConfiguration.setPort(redisProperties.getPort());  
        return new LettuceConnectionFactory(redisStandaloneConfiguration);  
    }  
  
  
    @Bean  
    public RedisConnectionFactory jedisConnectionFactory() {  
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();  
        redisStandaloneConfiguration.setDatabase(1);  
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());  
        redisStandaloneConfiguration.setPassword(redisProperties.getPassword());  
        redisStandaloneConfiguration.setPort(redisProperties.getPort());  
        return new JedisConnectionFactory(redisStandaloneConfiguration);  
    }  
  
    @Bean  
    public RedisTemplate<String, Object> redisTemplate() {  
        // 数据库0  
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();  
        // key序列化  
        redisTemplate.setKeySerializer(RedisSerializer.string());  
        // 字符  
        redisTemplate.setValueSerializer(RedisSerializer.json());  
        redisTemplate.setConnectionFactory(lettuceConnectionFactory());  
        return redisTemplate;  
    }  
    @Bean  
    public RedisTemplate<String, Object> redisTemplate1() {  
        // 数据库1  
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();  
        // 设置默认序列化  
        redisTemplate.setDefaultSerializer(RedisSerializer.string());  
        redisTemplate.setConnectionFactory(jedisConnectionFactory());  
        return redisTemplate;  
    }    }

Application

@SpringBootApplication
public class RedisApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =
                new SpringApplicationBuilder(RedisApplication.class)
                        .web(WebApplicationType.NONE)
                        .run(args);
        RedisTemplate<String, Object> redisTemplate =
                context.getBean("redisTemplate", RedisTemplate.class);
        RedisTemplate<String, Object> redisTemplate1 =
                context.getBean("redisTemplate1", RedisTemplate.class);
        redisTemplate.opsForValue().set("11", "2123");
        redisTemplate.boundValueOps("22").set("2124");

        redisTemplate1.opsForValue().set("33", "2123");
        redisTemplate1.boundValueOps("44").set("2124");
        // 其他同理...
    }
}

热门问题

  • 缓存穿透

系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回,缓存中没有就去db中查询,db中如果有就会将其丢到缓存中,但是有些key对应更多数据在db中并不存在,每次 针对此次key的请求从缓存中取不到,请求都会压到db,从而可能压垮db。 比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用大量此类攻击可能 压垮数据库

解决方案:

  1. 对空值缓存
    如果一个查询返回的数据为空(不管数据库是否存在),我们仍然把这个结果(null)进行缓存,给其 设置一个很短的过期时间,最长不超过五分钟
  2. 设置白名单
    使用redis中的bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次范文和 bitmap里面的id进行比较,如果访问的id不在bitmaps里面,则进行拦截,不允许访问
  3. 使用布隆过滤器,这是一个随机哈希算法
  4. 监控redis
    当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名 单限制对其提供服务(比如:IP黑名单)
  • 缓存击穿

redis中某个热点key(访问量很高的key)过期,此时大量请求同时过来,发现缓存中没有命中,这些请求都打到db上了,导致db压力瞬时大增,可能会打垮db,这种情况成为缓存击穿

特点:

  • 数据库访问压力瞬时增大
  • redis里面没有出现大量的key过期
  • redis正常运行

解决方案:

  1. 预先设置热门数据,调整过期时间内
  2. 使用锁
    缓存中拿不到数据的时候,此时不是立即去db中查询,而是去获取分布式锁(比如redis中的setnx), 拿到锁再去db中load数据;没有拿到锁的线程休眠一段时间再重试整个获取数据的方法。
  • 缓存雪崩

key对应的数据存在,但是极短时间内有大量的key集中过期,此时若有大量的并发请求过来,发现缓存 没有数据,大量的请求就会落到db上去加载数据,会将db击垮,导致服务奔溃。

缓存雪崩与缓存击穿的区别:前者是大量的key集中过期,而后者是某个热点key过期。

解决方案:

  1. 构建多级缓存
    nginx缓存+redis缓存+其他缓存(ehcache等)
  2. 使用锁或队列
    用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发 请求落到底层存储系统上,不适用高并发情况。
  3. 监控缓存过期,提前更新
  4. 将缓存失效时间分散开来,在原有的失效时间基础上增加一个随机值。

参考文档

Spring Data Redis

Redis 官网

Redis 命令参考 — Redis 命令参考 (redisfans.com)