SpringBoot整合redis(redis支持单节点和集群)

一、简介

  Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

  Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

  今天我们用一个maven项目实战一个Spring BootRedis的详细整合与使用。

二、Maven依赖

  注意:本文中的com.fasterxml.jackson使用的依赖的版本号为2.9.10,不要使用到最新的版本2.12.4。Spring BootRedis的redis的版本最好使用最新版(当前最新为2.5.2),并且保持两者版本一致,避免不兼容的情况。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!--spring boot 版本-->
        <version>2.5.2</version>
        <relativePath/>
    </parent>

    <groupId>com.alian</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis</name>
    <description>spring boot演示redis</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.package.directory>target</project.package.directory>
        <java.version>1.8</java.version>
        <!--com.fasterxml.jackson 版本-->
        <jackson.version>2.9.10</jackson.version>
        <!--阿里巴巴fastjson 版本-->
        <fastjson.version>1.2.68</fastjson.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${parent.version}</version>
        </dependency>

        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>${parent.version}</version>
        </dependency>

        <!--用于序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!--java 8时间序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!--JSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

三、RedisAutoConfiguration源码

首先我们先看下RedisAutoConfiguration的源码,源码路径:spring-boot-autoconfigure-2.5.2.jar包中org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

  通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。可是实际使用中会有几个问题:

  • RedisTemplate的泛型是<Object,Object>,编码时候经常要做类型转换
  • RedisTemplate没设置key及value的序列化方式,容易导致存储乱码,或者解析异常,查看也不友好

  我们实际中更常用到的是<String,Object>形式的RedisTemplate,当看到这个@ConditionalOnMissingBean注解后,我们就可以实现自己的RedisTemplate了。@ConditionalOnMissingBean 是修饰Bean的一个注解,仅当 BeanFactory 中不包含指定的 bean class 和/或 bean name 时条件匹配。
  我们顺便可以看看ConditionalOnMissingBean 源码

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
    Class<?>[] value() default {};

    String[] type() default {};

    Class<?>[] ignored() default {};

    String[] ignoredType() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

  从ConditionalOnMissingBean 的源码以及上面RedisAutoConfiguration 的源码,我们知道如果Spring容器中有bean nameredisTemplate对象了,这个自动配置的RedisTemplate就不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate

四、自定义RedisConfig配置(核心)

废话不多说,直接上代码。这里再次提醒:com.fasterxml.jackson使用的依赖的版本号为2.9.10,具体可以看下我的maven依赖。

4.1 配置源代码

RedisConfig.java

package com.alian.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class RedisConfig {

    /**
     * redis配置
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 实例化redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // key采用String的序列化
        redisTemplate.setKeySerializer(keySerializer());
        // value采用jackson序列化
        redisTemplate.setValueSerializer(valueSerializer());
        // Hash key采用String的序列化
        redisTemplate.setHashKeySerializer(keySerializer());
        // Hash value采用jackson序列化
        redisTemplate.setHashValueSerializer(valueSerializer());
        //执行函数,初始化RedisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * key类型采用String序列化
     *
     * @return
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * value采用JSON序列化
     *
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        //设置jackson序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //设置序列化对象
        jackson2JsonRedisSerializer.setObjectMapper(getMapper());
        return jackson2JsonRedisSerializer;
    }


    /**
     * 使用com.fasterxml.jackson.databind.ObjectMapper
     * 对数据进行处理包括java8里的时间
     *
     * @return
     */
    private ObjectMapper getMapper() {
        ObjectMapper mapper = new ObjectMapper();
        //设置可见性
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //默认键入对象
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //设置Java 8 时间序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //禁用把时间转为时间戳
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(timeModule);
        return mapper;
    }
}

4.2 单节点配置

如果redis是单节点,application.properties 配置如下:(为了演示方便,我这里Redis数据库索引使用的是1)

# Redis数据库索引(默认为0,设置为1是为了演示方便)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=192.168.0.193
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接超时时间(毫秒)
spring.redis.timeout=1000

4.2 集群节点配置

如果是redis集群,application.properties 配置如下:(集群节点至少需要3组主从)

#集群地址配置,值为 host:port,用逗号分隔
spring.redis.cluster.nodes=192.168.0.111:6379,192.168.0.113:6379,192.168.0.101:6379,192.168.0.102:6379,192.168.0.103:6379,192.168.0.114:6379,192.168.0.104:6379

五、实践

整合完毕后,不管是单节点的redis,还是redis集群,都可以进行如下的测试:

5.1、测试源代码

RedisService .java

package com.alian.redis.service;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * PostConstruct在构造函数之后,init()方法之前执行。
     */
    @PostConstruct
    public void redisTestWithPostConstruct() {
        System.out.println("--------------------redis中String(字符串)测试开始--------------------");
        stringRedis();
        System.out.println("--------------------redis中String(字符串)测试结束--------------------");
        System.out.println();
        System.out.println("--------------------redis中list(列表)测试开始--------------------");
        listRedis();
        System.out.println("--------------------redis中list(列表)测试List结束--------------------");
        System.out.println();
        System.out.println("--------------------redis中Hash(散列)开始--------------------");
        hashRedis();
        System.out.println("--------------------redis中Hash(散列)结束--------------------");
    }


    private void stringRedis() {
        // 定义一个json对象
        JSONObject json = new JSONObject();
        json.put("title", "SpringBoot整合redis(单节点)");
        json.put("author", "Alian");
        json.put("webUrl", "https://blog.csdn.net/Alian_1223");
        json.put("publishTime", LocalDateTime.now());
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.string";
        // 缓存json字符串,同时设置过期时间为5分钟
        redisTemplate.opsForValue().set(cacheKey, json.toJSONString(), 40, TimeUnit.SECONDS);
        //从缓存中获取到的缓存的值
        String jsonValue = (String) redisTemplate.opsForValue().get(cacheKey);
        System.out.println("[String(字符串)],从缓存中获取到的缓存的值:" + jsonValue);
    }

    private void listRedis() {
        //定义一个List对象
        List<Object> list = new ArrayList<>();
        list.add("apple");
        list.add("orange");
        list.add("pear");
        //加入一个java8时间,检测是否序列化
        list.add(LocalDateTime.now());
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.list";
        // 缓存list,同时设置过期时间为30秒(列表的左侧放入)
        redisTemplate.opsForList().leftPushAll(cacheKey, list);
        redisTemplate.expire(cacheKey, 3, TimeUnit.MINUTES);
        //从缓存中获取到的缓存的值
        List<Object> value = redisTemplate.opsForList().range(cacheKey, 0, -1);
        System.out.println("[list(列表)],从缓存中获取到的缓存的值:" + value);
    }

    private void hashRedis() {
        //定义一个map对象
        Map<String, Object> map = new HashMap<>();
        map.put("name", "梁南生");
        map.put("sex", "男");
        map.put("salary", "120000");
        map.put("birth", LocalDate.of(1995, 8, 12));
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.hash";
        // 缓存map,同时设置过期时间为2小时
        redisTemplate.opsForHash().putAll(cacheKey, map);
        redisTemplate.expire(cacheKey, 2, TimeUnit.HOURS);
        //从缓存中获取姓名和生日
        List<Object> list = redisTemplate.opsForHash().multiGet(cacheKey, Arrays.asList("name", "birth"));
        System.out.println("[Hash(散列)],从缓存中获取到的姓名和生日为:" + list);
    }
}

  这里我教大家一个新的方法进行简单的单元测试,那就是注解@PostConstruct,这个注解不是Spring的注解,而是java原生的javax.annotation.PostConstruct,使用上需要注意的如下:

  • 非静态方法才能使用
  • 被注解的方法不能有参数
  • 被注解的方法不能有返回值
  • 被注解的方法不能抛出异常
  • 被注解的方法只执行一次

缺点是服务启动时间稍慢一点,启动时加载的顺序
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

5.2、测试结果

运行结果:

--------------------redis中String(字符串)测试开始--------------------
[String(字符串)],从缓存中获取到的缓存的值:{"publishTime":"2021-07-14T17:11:47.275","author":"Alian","webUrl":"https://blog.csdn.net/Alian_1223","title":"SpringBoot整合redis(单节点)"}
--------------------redis中String(字符串)测试结束--------------------

--------------------redis中list(列表)测试开始--------------------
[list(列表)],从缓存中获取到的缓存的值:[2021-07-14 17:11:47, pear, orange, apple]
--------------------redis中list(列表)测试List结束--------------------

--------------------redis中Hash(散列)开始--------------------
[Hash(散列)],从缓存中获取到的姓名和生日为:[梁南生, 1995-08-12]
--------------------redis中Hash(散列)结束--------------------

5.3、redis中Java 8值的序列化效果图

最后我们看下Java8的时间是否已经按照我们的格式(yyyy-MM-dd HH:mm:ss)序列化好,从客户端查看如下图所示:
在这里插入图片描述
关于Java8时间的序列化需要注意的是,序列化对象的值类型是Java8时间类型,而不是某个对象如json或字符串里包含一个java8时间类型的属性,至此,我们的redis里的key,value的序列化方式也实现了。

结语

  本文主要把Spring BootRedis进行详细整合,包括单节点redisredis集群,也对部分原理进行简单介绍,这个整合在实际开发中是比较实用和灵活的,比如灵活的切换单节点redis和redis集群,使用上并没有半点不同。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Spring Boot整合Redis集群的步骤: 1. 添加Redis依赖:在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 2. 配置Redis集群节点:在application.properties或application.yml文件中配置Redis集群节点的信息,例如: ```yaml spring.redis.cluster.nodes=192.168.0.1:6379,192.168.0.2:6379,192.168.0.3:6379 ``` 3. 配置Redis连接池:可以根据需要配置Redis连接池的相关参数,例如最大连接数、最大空闲连接数等。以下是一个示例配置: ```yaml spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=10 spring.redis.jedis.pool.min-idle=5 spring.redis.jedis.pool.max-wait=3000 ``` 4. 创建RedisTemplate Bean:在配置类中创建RedisTemplate Bean,用于操作Redis集群。以下是一个示例配置: ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } } ``` 5. 使用RedisTemplate操作Redis集群:在需要使用Redis的地方,注入RedisTemplate,并使用其提供的方法操作Redis集群。以下是一个示例: ```java @Autowired private RedisTemplate<String, Object> redisTemplate; public void setValue(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public Object getValue(String key) { return redisTemplate.opsForValue().get(key); } ``` 这样就完成了Spring Boot与Redis集群整合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值