SpringBoot集成Redis


Redis一個基於內存實現的NoSql數據庫,通常將其用於數據緩存以此來減少系統的壓力,下面我將在SpringBoot中分別實現Redis單機模式、Redis主從復制、Redis哨兵模式、Redis-Cluster模式在SpringBoot中的集成的示例。

一、SpringBoot中集成單機版Redis

Redis的相關安裝教程這里不再贅述,請參考Docker上安裝Redis。示例項目源代碼https://github.com/vcharfred/spring-demo的Hoxton分支的redis-template*模塊中

添加redis的自動裝配類

版本號不用寫,直接使用springboot默認適配的版本即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 使用lettuce做redis的連接池需要額外引入這個包-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

開箱即用的配置

正如標題所說開箱即用,我們只需要在application.yml配置類里面添加如下redis配置即可

spring:
  redis:
    # 地址
    host: 192.168.56.102
    # 端口號
    port: 3306
    # 密碼
    password: 123456
    # 超時時間,單位毫秒
    timeout: 3000
    # 數據庫編號
    database: 0
    # 配置lettuce
    lettuce:
      pool:
        # 連接池中的最小空閑連接
        min-idle: 1
        # 連接池中的最大空閑連接
        max-idle: 6
        # 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
        max-active: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制);單位毫秒
        max-wait: 1000
      #關閉超時時間;單位毫秒
      shutdown-timeout: 200

springboot的redis自動裝配類已經配置了一個StringRedisTemplate的bean,如果操作都是使用String的數據類型,那么直接使用即可,使用示例

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
 * 增刪查改示例1
 */
@Override
public String crud1() {
    ValueOperations<String, String> operations = this.stringRedisTemplate.opsForValue();
    // 獲取數據
    String val = operations.get("student:1");
    if(null==val){
        log.info("緩存中數據不存在");
        Student student = Student.create();
        val = JSON.toJSONString(student);
        // 添加數據
        operations.set("student:1", val);

        Student student2 = Student.create();
        student2.setId(2L);
        operations.set("student:2", JSON.toJSONString(student2));
        this.stringRedisTemplate.opsForList().leftPush("student_list", JSON.toJSONString(student));
        this.stringRedisTemplate.opsForList().leftPush("student_list", JSON.toJSONString(student2));

    }else {
        // 刪除數據
        this.stringRedisTemplate.delete("student:2");
        log.info("刪除緩存");
    }
    log.info(val);

    // 獲取列表數據
    List<String> list = this.stringRedisTemplate.opsForList().range("student_list", 0, 3);
    log.info(JSON.toJSONString(list));
    return val;
}

自定義配置Redis操作的bean

StringRedisTemplate雖然可以滿足需求,但是還是需要我們去序列化一下;因此我簡化操作我們需要自定義配置創建RedisTemplate的bean。創建一個配置類繼承CachingConfigurerSupport類。

@Configuration
public class RedisConfig<K, V> extends CachingConfigurerSupport {

    /**
     * 自定義緩存注解key的生成策略。默認的生成策略是看不懂的(亂碼內容)
     * 通過Spring 的依賴注入特性進行自定義的配置注入並且此類是一個配置類可以更多程度的自定義配置
     * 這里是生成的key是:類全名.方法名 方法參數(的md5加密)
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder prefix = new StringBuilder();
            prefix.append(target.getClass().getName());
            prefix.append(".").append(method.getName());
            StringBuilder sb = new StringBuilder();
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return prefix.append(DigestUtils.md5DigestAsHex(sb.toString().getBytes()));
        };
    }

    /**
     * 緩存配置管理器
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory factory) {
        //以鎖寫入的方式創建RedisCacheWriter對象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        //設置緩存注解的緩存時間,緩存1小時
        Duration duration = Duration.ofSeconds(3600L);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(duration);
        return new RedisCacheManager(writer, redisCacheConfiguration);
    }

    /**
     * 修改redisTemplate的序列化方式
     *
     * @param factory LettuceConnectionFactory
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<K, V> redisTemplate(LettuceConnectionFactory factory) {
        //創建RedisTemplate對象
        RedisTemplate<K, V> template = new RedisTemplate<K, V>();
        template.setConnectionFactory(factory);
        //設置key的序列化方式
        template.setKeySerializer(keySerializer());
        template.setHashKeySerializer(keySerializer());

        //設置RedisTemplate的Value序列化方式Jackson2JsonRedisSerializer;默認是JdkSerializationRedisSerializer
        template.setValueSerializer(valueSerializer());
        template.setHashValueSerializer(valueSerializer());

        template.afterPropertiesSet();
        return template;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會拋出異常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        //解決時間序列化問題
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());

        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

}

使用示例

@Autowired
private RedisTemplate<String, Student> redisTemplate;

@Override
public Student crud2() {
    ValueOperations<String, Student> operations = this.redisTemplate.opsForValue();
    // 獲取數據
    Student val = operations.get("student2:1");
    if(null==val){
        log.info("緩存中數據不存在");
        val = Student.create();
        // 添加數據
        operations.set("student2:1", val);
        
        Student student2 = Student.create();
        student2.setId(2L);
        operations.set("student2:2", student2);
        this.redisTemplate.opsForList().leftPush("student_list2", val);
        this.redisTemplate.opsForList().leftPush("student_list2", student2);
    }else {
        // 刪除數據
        this.redisTemplate.delete("student2:2");
        log.info("刪除緩存");
    }
    log.info(JSON.toJSONString(val));
    
    // 獲取列表數據
    List<Student> list = this.redisTemplate.opsForList().range("student_list2", 0, 3);
    log.info(JSON.toJSONString(list));
    return val;
}

二、SpringBoot中實現Redis的讀寫分離

spring-boot-starter-data-redis 原生並不支持讀寫分離;因此需要我們去手動封裝實現;實現思路:1.通過AOP來實現;2.封裝工具類,我們在工具類里面實現;3.由使用者自己去處理。這里我們通過再次封裝redisTemplate類來實現,簡化調用者的工作。由於需要讀寫分離,因此我們需要自定義配置來實現。代碼如下:

redis自定義屬性配置RedisReadWriteConfig

@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedisReadWriteConfig implements Serializable {

    /**
     * 主機地址
     */
    @Value("${spring.redis.host}")
    private String host;

    /**
     * 認證密碼
     */
    @Value("${spring.redis.password:#{null}}")
    private String password;

    /**
     * 端口號
     */
    @Value("${spring.redis.port:6379}")
    private int port = 6379;

    /**
     * 數據庫編號
     */
    @Value("${spring.redis.database:0}")
    private int database;

    /**
     * 連接超時時間,單位毫秒
     */
    @Value("${spring.redis.timeout:3000}")
    private long timeout;

    /**
     * 關閉超時時間,單位毫秒
     */
    @Value("${spring.redis.lettuce.shutdown-timeout:200}")
    private long shutdownTimeout;

    /**
     * 連接池中的最小空閑連接
     */
    @Value("${spring.redis.lettuce.pool.min-idle:1}")
    private int minIdle;

    /**
     * 連接池中的最大空閑連接
     */
    @Value("${spring.redis.lettuce.pool.max-idle:6}")
    private int maxIdle = 6;

    /**
     * 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
     */
    @Value("${spring.redis.lettuce.pool.max-active:10}")
    private int maxActive = 10;

    /**
     * 連接池最大阻塞等待時間(使用負值表示沒有限制),單位毫秒
     */
    @Value("${spring.redis.lettuce.pool.max-wait:1000}")
    private long maxWait = 1000;

    /**
     * redis只讀庫配置
     */
    private List<RedisReadConfig> redisReadConfigs;

    @Data
    @Validated
    @AllArgsConstructor
    @NoArgsConstructor
    static class RedisReadConfig {

        /**
         * 主機地址
         */
        @NotEmpty
        private String host;

        /**
         * 認證密碼
         */
        private String password;

        /**
         * 端口號
         */
        private int port = 6379;

        /**
         * 數據庫編號
         */
        private int database = 0;
    }

    /**
     * 只讀實例配置
     *
     * @return 返回所有數據讀取的配置
     */
    public List<RedisStandaloneConfiguration> buildReadRedisStandaloneConfiguration() {
        if (enableReadWriteModel()) {
            redisReadConfigs = redisReadConfigs.stream().distinct().collect(Collectors.toList());
            List<RedisStandaloneConfiguration> list = new ArrayList<>(redisReadConfigs.size());
            for(RedisReadConfig readConfig : redisReadConfigs){
                list.add(createRedisStandaloneConfiguration(readConfig));
            }
            return list;
        }
        return null;
    }

    /**
     * 寫實例配置
     *
     * @return 返回所有數據讀取的配置
     */
    public RedisStandaloneConfiguration buildWriteRedisStandaloneConfiguration() {
        RedisReadConfig redisServerConfig = new RedisReadConfig();
        redisServerConfig.setHost(this.host);
        redisServerConfig.setPort(this.port);
        redisServerConfig.setPassword(this.password);
        redisServerConfig.setDatabase(this.database);
        return createRedisStandaloneConfiguration(redisServerConfig);
    }

    /**
     * 是否啟動讀寫分離模式
     *
     * @return 啟用返回true;否則false
     */
    public boolean enableReadWriteModel(){
        return redisReadConfigs != null && !redisReadConfigs.isEmpty();
    }

    private RedisStandaloneConfiguration createRedisStandaloneConfiguration(RedisReadConfig redisServerConfig) {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        // 連接地址
        redisStandaloneConfiguration.setHostName(redisServerConfig.getHost());
        // 認證密碼
        redisStandaloneConfiguration.setPassword(redisServerConfig.getPassword());
        // 端口號,默認6379
        redisStandaloneConfiguration.setPort(redisServerConfig.getPort());
        // 數據庫編號
        redisStandaloneConfiguration.setDatabase(redisServerConfig.getDatabase());
        return redisStandaloneConfiguration;
    }

}

配置示例

server:
  port: 8088
spring:
  redis:
    # 地址
    host: 192.168.56.102
    # 端口號
    port: 6379
    # 密碼
    password: 123456
    # 超時時間,單位毫秒
    timeout: 3000
    # 數據庫編號
    database: 0
    # 配置lettuce
    lettuce:
      pool:
        # 連接池中的最小空閑連接
        min-idle: 1
        # 連接池中的最大空閑連接
        max-idle: 6
        # 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
        max-active: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制);單位毫秒
        max-wait: 1000
      #關閉超時時間;單位毫秒
      shutdown-timeout: 200
    # redis只讀庫配置
    redis-read-configs:
      - host: 192.168.56.104
        port: 6379
        password: 123456
      - host: 192.168.56.105
        port: 6379
        password: 123456

自定義redis連接工廠ReadWriteLettuceConnectionFactory

由於我們有多個只讀庫,為了實現動態切換,我們自己是實現一個工廠,方便后面操作

@Slf4j
@Component
public class ReadWriteLettuceConnectionFactory implements DisposableBean {

    private final LettuceConnectionFactory writeConnectionFactory;

    private final List<LettuceConnectionFactory> readConnectionFactories = new ArrayList<>();

    private final AtomicInteger pos = new AtomicInteger();

    private int max = -1;

    public ReadWriteLettuceConnectionFactory(RedisReadWriteConfig readWriteConfig) {
        this.writeConnectionFactory = createLettuceConnectionFactory(readWriteConfig, readWriteConfig.buildWriteRedisStandaloneConfiguration());
        Assert.notNull(writeConnectionFactory, "redis config can not null, if don't used please remove dependence redis jar.");

        if (readWriteConfig.enableReadWriteModel()) {
            List<RedisStandaloneConfiguration> list = readWriteConfig.buildReadRedisStandaloneConfiguration();
            if(null!=list){
                for(RedisStandaloneConfiguration rsc:list){
                    LettuceConnectionFactory connectionFactory = createLettuceConnectionFactory(readWriteConfig, rsc);
                    if(connectionFactory!=null){
                        log.info("redis-read-datasource - load a datasource [{}:{}] success!", rsc.getHostName(), rsc.getPort());
                        readConnectionFactories.add(connectionFactory);
                        max++;
                    }else {
                        log.warn("redis-read-datasource - load a datasource [{}:{}] fail!", rsc.getHostName(), rsc.getPort());
                    }
                }
            }else {
                log.warn("found read redis config, but can't load a datasource fail!");
            }
        }
    }

    /**
     * 獲取讀連接池
     * @return  返回連接工廠
     */
    public LettuceConnectionFactory getReadFactory() {
        // 簡單的負載均衡:輪詢方案
        if(pos.get()>max){
            pos.set(0);
        }
        int index = pos.getAndIncrement();
        log.info("chose redis-read-datasource index is [{}]", pos);
        return getReadFactory(index);
    }

    private LettuceConnectionFactory getReadFactory(int index) {
        if(index>max){
            log.warn("no suitable redis-read-datasource [{}], the max {}.", index, max);
            // 發生錯誤自動切換到寫連接上去
            return writeConnectionFactory;
        }
        return readConnectionFactories.get(index);
    }

    /**
     * 獲取寫連接池
     * @return  返回連接工廠
     */
    public LettuceConnectionFactory getWriteFactory() {
        return writeConnectionFactory;
    }

    /**
     * 創建Lettuce連接工廠
     *
     * @param readWriteConfig redis配置
     * @param redisStandaloneConfiguration redis獨立配置
     * @return 返回連接工廠
     */
    private LettuceConnectionFactory createLettuceConnectionFactory(RedisReadWriteConfig readWriteConfig
            , RedisStandaloneConfiguration redisStandaloneConfiguration) {

        if (redisStandaloneConfiguration == null) {
            return null;
        }

        // 連接池配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        // 連接池中的最小空閑連接
        poolConfig.setMinIdle(readWriteConfig.getMinIdle());
        // 連接池中的最大空閑連接
        poolConfig.setMaxIdle(readWriteConfig.getMaxIdle());
        // 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
        poolConfig.setMaxTotal(readWriteConfig.getMaxActive());
        // 連接池最大阻塞等待時間(使用負值表示沒有限制)
        poolConfig.setMaxWaitMillis(readWriteConfig.getMaxWait());

        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder lettucePoolingClientConfigurationBuilder
                = LettucePoolingClientConfiguration.builder();
        // 連接池配置
        lettucePoolingClientConfigurationBuilder.poolConfig(poolConfig);
        // 關閉超時時間,單位毫秒
        lettucePoolingClientConfigurationBuilder.shutdownTimeout(Duration.ofMillis(readWriteConfig.getShutdownTimeout()));
        // 超時時間,單位毫秒
        lettucePoolingClientConfigurationBuilder.commandTimeout(Duration.ofMillis(readWriteConfig.getTimeout()));

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration
                , lettucePoolingClientConfigurationBuilder.build());
        lettuceConnectionFactory.afterPropertiesSet();
        return lettuceConnectionFactory;
    }

    @Override
    public void destroy() throws Exception {
        writeConnectionFactory.destroy();
        if(!readConnectionFactories.isEmpty()){
            for(LettuceConnectionFactory connectionFactory:readConnectionFactories){
                connectionFactory.destroy();
            }
            readConnectionFactories.clear();
        }
        log.info("redis-datasource all closed success.");
    }
}

重寫RedisTemplate類實現多個只讀庫的動態切換

為了實現多個只讀庫能夠自動的切換和輪詢,需要對原來的RedisTemplate類做些升級,通過查看其源碼發現每次執行相關操作前都會調用getRequiredConnectionFactory()這個方法;基於此我們通過繼承RedisTemplate然后重寫其getRequiredConnectionFactory()方法來實現只讀庫的切換。

public class ReadWriteRedisTemplate<K, V> extends RedisTemplate<K, V> {

    private ReadWriteLettuceConnectionFactory readWriteConnectionFactory;

    private boolean isRead = false;

    /**
     * RedisTemplate每次執行方法時都會調用這個方法;如果只有1讀1寫,那么就沒有必要再弄這個封裝類,直接在創建的時候指定即可
     *
     * @return RedisConnectionFactory
     */
    @Override
    public RedisConnectionFactory getRequiredConnectionFactory() {
        return getFactory();
    }

    public void setReadWriteConnectionFactory(ReadWriteLettuceConnectionFactory readWriteConnectionFactory, boolean isRead) {
        this.isRead = isRead;
        this.readWriteConnectionFactory = readWriteConnectionFactory;
        setConnectionFactory(getFactory());
    }

    private RedisConnectionFactory getFactory(){
        if(this.isRead){
            return this.readWriteConnectionFactory.getReadFactory();
        }
        return this.readWriteConnectionFactory.getWriteFactory();
    }
}

配置RedisTemplate的注入bean

@Configuration
public class RedisCachingConfigurer<K, V> extends CachingConfigurerSupport {

    /**
     * 讀數據的RedisTemplate
     *
     * @param factory LettuceConnectionFactory
     */
    @Bean(name = "readRedisTemplate")
    public RedisTemplate<K, V> readRedisTemplate(ReadWriteLettuceConnectionFactory factory) {
        return redisTemplate(factory, true);
    }

    /**
     * 寫數據的RedisTemplate
     *
     * @param factory LettuceConnectionFactory
     */
    @Bean(name = "writeRedisTemplate")
    public RedisTemplate<K, V> writeRedisTemplate(ReadWriteLettuceConnectionFactory factory) {
        return redisTemplate(factory, false);
    }

    private RedisTemplate<K, V> redisTemplate(ReadWriteLettuceConnectionFactory factory, boolean isRead) {
        //創建Redis緩存操作助手RedisTemplate對象
        ReadWriteRedisTemplate<K, V> template = new ReadWriteRedisTemplate<K, V>();
        template.setReadWriteConnectionFactory(factory, isRead);
        //設置key的序列化方式
        template.setKeySerializer(keySerializer());
        template.setHashKeySerializer(keySerializer());

        //將RedisTemplate的Value序列化方式由JdkSerializationRedisSerializer更換為Jackson2JsonRedisSerializer
        template.setValueSerializer(valueSerializer());
        template.setHashValueSerializer(valueSerializer());

        template.afterPropertiesSet();
        return template;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會拋出異常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        //解決時間序列化問題
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());

        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }
}

使用示例

@Autowired
private RedisTemplate<String, Student>  writeRedisTemplate;
@Autowired
private RedisTemplate<String, Student>  readRedisTemplate;

/**
 * 由使用者自己判斷應該是讀還是寫;注意只讀的只能做讀操作
 */
@Override
public Student crud1() {
    // 獲取數據
    Student val = this.readRedisTemplate.opsForValue().get("student-rw1:1");
    if (null == val) {
        log.info("緩存中數據不存在");
        val = Student.create();
        // 添加數據
        this.writeRedisTemplate.opsForValue().set("student-rw1:1", val);

        Student student2 = Student.create();
        student2.setId(2L);
        this.writeRedisTemplate.opsForValue().set("student-rw1:2", student2);
        this.writeRedisTemplate.opsForList().leftPush("student_list-rw1", val);
        this.writeRedisTemplate.opsForList().leftPush("student_list-rw1", student2);
    } else {
        // 刪除數據
        this.writeRedisTemplate.delete("student-rw1:2");
        log.info("刪除緩存");
    }
    log.info(JSON.toJSONString(val));

    // 獲取列表數據
    List<Student> list = this.readRedisTemplate.opsForList().range("student_list-rw1", 0, 3);
    log.info(JSON.toJSONString(list));
    return val;
}

封裝工具類

從上面的例子上我們發現手動切換不是太方便,因此這里我封裝一個幫助類

@Component
public class RedisHelper {

    private final RedisTemplate<String, String>  writeRedisTemplate;
    private final RedisTemplate<String, String>  readRedisTemplate;

    public RedisHelper(RedisTemplate<String, String> writeRedisTemplate, RedisTemplate<String, String> readRedisTemplate) {
        this.writeRedisTemplate = writeRedisTemplate;
        this.readRedisTemplate = readRedisTemplate;
    }

    /**
     * 設置值
     */
    public <T> boolean set(String key, T value) {
        if (value instanceof String) {
            return set(key, (String) value);
        }
        return set(key, JSON.toJSONString(value));
    }

    /**
     * 設置值
     */
    public <T> boolean set(String key, T value, long validTime) {
        if (value instanceof String) {
            return set(key, (String) value, validTime);
        }
        return set(key, JSON.toJSONString(value), validTime);
    }

    /**
     * 設置值
     */
    private boolean set(String key, String value, long validTime) {
        Boolean res = this.writeRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = this.writeRedisTemplate.getStringSerializer();
            byte[] keyByte = Objects.requireNonNull(serializer.serialize(key));
            byte[] valueByte = Objects.requireNonNull(serializer.serialize(value));
            connection.set(keyByte, valueByte);
            connection.expire(keyByte, validTime);
            return true;
        });
        return res != null && res;
    }

    /**
     * 設置某個值的緩存時間
     */
    public boolean setExpire(String key, long validTime) {
        Boolean res = this.writeRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = this.writeRedisTemplate.getStringSerializer();
            byte[] keyByte = Objects.requireNonNull(serializer.serialize(key));
            connection.expire(keyByte, validTime);
            return true;
        });
        return res != null && res;
    }

    /**
     * 設置值
     */
    private boolean set(String key, String value) {
        Boolean res = this.writeRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = this.writeRedisTemplate.getStringSerializer();
            connection.set(Objects.requireNonNull(serializer.serialize(key)), Objects.requireNonNull(serializer.serialize(value)));
            return true;
        });
        return res != null && res;
    }

    /**
     * 獲取值
     */
    public <T> T get(String key, Class<T> clazz) {
        return JSON.parseObject(getValue(key), clazz);
    }

    /**
     * 獲取值
     */
    public <T> List<T> getList(String key, Class<T> clazz) {
        return JSONArray.parseArray(getValue(key), clazz);
    }

    /**
     * 獲取值
     */
    public String get(String key) {
        return getValue(key);
    }

    /**
     * 獲取值
     */
    private String getValue(String key) {
        return this.readRedisTemplate.execute((RedisCallback<String>) connection -> {
            RedisSerializer<String> serializer = this.readRedisTemplate.getStringSerializer();
            byte[] value = connection.get(Objects.requireNonNull(serializer.serialize(key)));
            return serializer.deserialize(value);
        });
    }

    /**
     * 刪除值
     */
    public void del(String key) {
        this.writeRedisTemplate.delete(key);
    }

    /**
     * 批量刪除相同前綴的key
     */
    public void batchDel(String prefix) {
        Set<String> keys = keys(prefix);
        if (null != keys && !keys.isEmpty()) {
            this.writeRedisTemplate.delete(keys);
        }
    }

    /**
     * 批量刪除
     */
    public void batchDel(Collection<String> keys) {
        this.writeRedisTemplate.delete(keys);
    }

    /**
     * 判斷值緩存key是否存在
     */
    public boolean exist(String key) {
        Boolean res = this.writeRedisTemplate.hasKey(key);
        return res != null && res;
    }

    /**
     * 獲取相同前綴的key
     */
    public Set<String> keys(String prefix) {
        return this.readRedisTemplate.keys(prefix + "*");
    }

    /**
     * 如果key不存在則設置,此方法使用了redis的原子性
     */
    public boolean setNx(String key, String value, long validTime) {
        return setNx(key, value, validTime, TimeUnit.SECONDS);
    }

    /**
     * 如果key不存在則設置,此方法使用了redis的原子性
     */
    public boolean setNx(String key, String value, long validTime, TimeUnit timeUnit) {
        try {
            ValueOperations<String, String> operations = this.writeRedisTemplate.opsForValue();
            Boolean lock = operations.setIfAbsent(key, value, validTime, timeUnit);
            return lock != null && lock;
        } catch (Exception e) {
            this.del(key);
            e.printStackTrace();
        }
        return false;
    }
}

使用示例

@Autowired
private RedisHelper redisHelper;
/**
 * 通過工具類自動切換
 */
@Override
public Student crud2() {
    // 獲取數據
    Student val = this.redisHelper.get("student-rw2:1", Student.class);
    if (null == val) {
        log.info("緩存中數據不存在");
        val = Student.create();
        // 添加數據
        this.redisHelper.set("student-rw2:1", val);

        Student student2 = Student.create();
        student2.setId(2L);
        this.redisHelper.set("student-rw2:2", student2);

        List<Student> list = Lists.newArrayList(val, student2);
        this.redisHelper.set("student_list-rw2", list);

    } else {
        // 刪除數據
        this.redisHelper.del("student-rw2:2");
        log.info("刪除緩存");
    }
    log.info(JSON.toJSONString(val));
    // 獲取列表數據
    List<Student> list = this.redisHelper.getList("student_list-rw2", Student.class);
    log.info(JSON.toJSONString(list));
    return val;
}

三、Redis哨兵模式在SpringBoot中的使用

哨兵模式變化的就是配置信息;其他的和單機版的沒有區別。

spring:
  redis:
    # redis集群的密碼
    password: 123456
    # 超時時間,單位毫秒
    timeout: 3000
    # 數據庫編號
    database: 0
    # 配置lettuce
    lettuce:
      pool:
        # 連接池中的最小空閑連接
        min-idle: 1
        # 連接池中的最大空閑連接
        max-idle: 6
        # 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
        max-active: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制);單位毫秒
        max-wait: 1000
      #關閉超時時間;單位毫秒
      shutdown-timeout: 200
    # 哨兵配置
    sentinel:
      master: mymaster
      # 多個使用逗號分開
      nodes: 192.168.56.102:26379
      # 哨兵的密碼
      password: 123456

哨兵模式就是每次通過哨兵來獲取redis的master節點信息;同時會訂閱其節點切換頻道,當發生故障轉移時,客戶端能收到哨兵的通知,通過重新初始化連接池,完成主節點的切換。

四、Redis-Cluster模式在SpringBoot中的使用

Redis-Cluster模式變化的也只是配置信息;其他的和單機版的沒有區別。

spring:
  redis:
    # 密碼
    password: 123456
    # 超時時間,單位毫秒
    timeout: 3000
    # 數據庫編號
    database: 0
    # 配置lettuce
    lettuce:
      pool:
        # 連接池中的最小空閑連接
        min-idle: 1
        # 連接池中的最大空閑連接
        max-idle: 6
        # 連接池最大連接數(使用負值表示沒有限制,不要配置過大,否則可能會影響redis的性能)
        max-active: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制);單位毫秒
        max-wait: 1000
      #關閉超時時間;單位毫秒
      shutdown-timeout: 200
    # 集群配置
    cluster:
      # 最大失敗次數
      max-redirects: 3
      # 集群節點
      nodes: 192.168.56.101:6379,192.168.56.102:6379,192.168.56.103:6379,192.168.56.104:6379,192.168.56.105:6379,192.168.56.106:6379

關注微信訂閱號‘起岸星辰’獲取最新資訊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM