Spring Boot 整合 Redis:提升应用性能的利器

Redis (Remote Dictionary Server) 是一款高性能的键值对存储数据库,它以内存存储为主,具有速度快、支持丰富的数据类型等特点,被广泛应用于缓存、会话管理、排行榜等场景。 Spring Boot 提供了对 Redis 的良好支持,使得我们可以轻松地在 Spring Boot 项目中集成 Redis,从而提升应用的性能和可扩展性。

本文将详细介绍如何在 Spring Boot 项目中整合 Redis,并提供完整的代码示例和最佳实践。

一、为什么要使用 Redis?

  1. 高性能缓存: Redis 以内存存储为主,读写速度非常快,可以作为缓存层,减少对数据库的访问,从而提升应用性能。
  2. 丰富的数据类型:Redis 支持 String、List、Set、Hash、Sorted Set 等多种数据类型,可以满足不同的应用场景需求。
  3. 会话管理: Redis 可以用于存储用户会话信息,实现会话共享和负载均衡。
  4. 排行榜: Redis 的 Sorted Set可以方便地实现排行榜功能。
  5. 消息队列: Redis 的 List 数据类型可以用于实现简单的消息队列功能。
  6. 分布式锁: Redis可以用于实现分布式锁,解决分布式环境下的资源竞争问题。

二、Spring Boot 整合 Redis实践

2.1 添加 Redis 依赖

java 复制代码
   <!-- redis 缓存操作 -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
   </dependency>

   <!-- 缓存依赖 -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-cache</artifactId>
   </dependency>
   
   <!-- pool 对象池 -->
   <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
  </dependency>

2.2 配置 Redis 连接信息

java 复制代码
spring:
  # redis 配置
  redis:
    # 地址
    host: **.**.**.**
    # 端口,默认为6379
    port: 6380
    # 数据库索引
    database: 12
    # 密码
    password: ******
    # 连接超时时间
    timeout: 60s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

2.3 重写redis中的StringRedisSerializer序列化器

java 复制代码
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;

import java.nio.charset.Charset;

public class HashKeyStringRedisSerializer implements RedisSerializer<Object> {

    private final Charset charset;

    private final String target = "\"";

    private final String replacement = "";

    public HashKeyStringRedisSerializer() {
        this(Charset.forName("UTF8"));
    }

    public HashKeyStringRedisSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public Object deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

    @Override
    public byte[] serialize(Object object) {
        String string = JSON.toJSONString(object);
        if (string == null) {
            return null;
        }
        string = string.replace(target, replacement);
        return string.getBytes(charset);
    }
}

2.4 创建Redis配置类

java 复制代码
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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.cache.annotation.EnableCaching;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

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

@Configuration
@EnableCaching
public class RedisConfig {

    /**
     * 过期时间1天
     */
    private final Duration timeToLive = Duration.ofDays(1);

    /**
     * HaskKey的序列化方式
     */
    private final HashKeyStringRedisSerializer hashKeySerializer = new HashKeyStringRedisSerializer();

    /**
     * String的序列化方式
     */
    private final StringRedisSerializer stringSerializer = new StringRedisSerializer();

    /**
     * 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
     */
    private final Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer(Object.class);

    /**
     * 代码块,会优先执行
     * 用来设置Jackson2JsonRedisSerializer
     */
    {
        ObjectMapper objectMapper = new ObjectMapper();
        //设置所有访问权限以及所有的实际类型都可序列化和反序列化
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        //下面两行解决Java8新日期API序列化问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);
        valueSerializer.setObjectMapper(objectMapper);
    }

    /**
     * 在SpringBoot2.0之后,spring容器自动的生成了StringRedisTemplate和RedisTemplate<Object,Object>,可以直接注入
     * 但是在实际使用中,大多不会直接使用RedisTemplate<Object,Object>,而是会对key,value进行序列化,所以我们还需要新增一个配置类
     * 换句话说,由于原生的redis自动装配,在存储key和value时,没有设置序列化方式,故自己创建redisTemplate实例
     *
     * @param factory
     * @return
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // key采用String的序列化方式
        template.setKeySerializer(stringSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(hashKeySerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(valueSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(valueSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean(name = "redisCacheManager")
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        // 配置序列化(解决乱码的问题),通过config对象对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存的默认过期时间
                .entryTtl(timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer))
                // 不缓存空值
                .disableCachingNullValues();
        //根据redis缓存配置和reid连接工厂生成redis缓存管理器
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }

}

2.5 创建Redis工具类

java 复制代码
import cn.hutool.core.collection.CollectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class RedisCache {

    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 自增计数器
     * @param key
     * @param delta
     * @return
     */
    public Long incrementValue(final String key, final Long delta)
    {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <K, V> void setCacheCommonMap(final String key, final Map<K, V> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <K, V> Map<K, V> getCacheCommonMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     */
    public void deleteCacheMapValue(final String key, final String hKey)
    {
        redisTemplate.opsForHash().delete(key, hKey);
    }

    /**
     * 删除并缓存List数据
     *
     * @param key 缓存的键值
     * @param dataMap 待缓存的Map数据
     * @return 缓存的对象
     */
    public <T> void deleteAndSetCacheMap(final String key, final Map<String, T> dataMap)
    {
        if(redisTemplate.delete(key)){
            setCacheMap(key, dataMap);
        }
    }

    /**
     * 删除并缓存List数据,带过期时间
     *
     * @param key 缓存的键值
     * @param dataMap 待缓存的Map数据
     * @return 缓存的对象
     */
    public <T> void deleteAndSetCacheMap(final String key, final Map<String, T> dataMap, final long timeout, final TimeUnit unit)
    {
        if(redisTemplate.delete(key)){
            setCacheMap(key, dataMap);
            expire(key, timeout, unit);
        }
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }

    /**
     * 删除并缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long deleteAndSetCacheList(final String key, final List<T> dataList)
    {
        long count = 0;
        if(redisTemplate.delete(key)){
            count = redisTemplate.opsForList().rightPushAll(key, dataList);
        }
        return count;
    }

    /**
     * 删除并缓存List数据,带过期时间
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long deleteAndSetCacheList(final String key, final List<T> dataList, final long timeout, final TimeUnit unit)
    {
        long count = 0;
        if(redisTemplate.delete(key)){
            count = redisTemplate.opsForList().rightPushAll(key, dataList);
            expire(key, timeout, unit);
        }
        return count;
    }

    /**
     * 向集合中插入元素,并设置分数
     * @param key
     * @param value
     * @param score
     * @param <T>
     */
    public <T> void addCacheZSet(final String key, final T value, double score)
    {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 批量添加ZSet
     * @param key
     * @param map
     * @param <T>
     */
    public <T> void batchAddCacheZSet(final String key, final Map<T, Double> map)
    {
        Set<DefaultTypedTuple<T>> set = new HashSet<>();
        Iterator<Map.Entry<T, Double>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<T, Double> entry = iterator.next();
            set.add(new DefaultTypedTuple<T>(entry.getKey(), entry.getValue()));
        }
        redisTemplate.opsForZSet().add(key, set);
    }

    /**
     * 给指定元素添加分数
     * @param key
     * @param value
     * @param score
     * @param <T>
     * @return
     */
    public <T> Double incrementZSetScore(final String key, final T value, double score){
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    /**
     * 获取指定元素的分数
     * @param key
     * @param value
     * @param <T>
     * @return
     */
    public <T> Double getZSetScore(final String key, final T value){
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 删除指定元素
     * @param key
     * @param values
     * @param <T>
     */
    public <T> void deleteZSetKey(final String key, final T[] values){
        redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 根据key模糊删除
     * @param key
     * @return
     */
    public Long blurDelete(final String key) {
        Set<String> keys = redisTemplate.keys(key + ":*");
        if(CollectionUtil.isEmpty(keys)){
            return null;
        }
        return deleteObject(keys);
    }
}

2.6 编写测试方式操作Redis

java 复制代码
@RestController
@RequestMapping("/yes")
public class ExtendController extends BaseController {

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private RedisCache redisCache;

    @GetMapping("/{id}")
    public Result<UserInfo> getUserById(@PathVariable int id) {
        UserInfo user = userInfoService.getById(id);
        redisCache.setCacheObject(String.valueOf(id), user, 1440, TimeUnit.MINUTES);
        return Result.success(user);
    }
}

三、最佳实践

  1. 选择合适的数据类型: 根据业务需求选择合适的 Redis 数据类型,例如 String、List、Set、Hash、SortedSet。
  2. 合理设置过期时间: 为缓存数据设置合理的过期时间,避免缓存过期导致大量请求访问数据库。
  3. 使用连接池: Spring Boot默认使用连接池来管理 Redis 连接,可以提高性能。
  4. 考虑序列化: 对于非 String类型的数据,需要考虑序列化和反序列化。Spring Boot 默认使用 JdkSerializationRedisSerializer进行序列化。
  5. 监控 Redis: 使用 Redis 自带的监控工具或第三方工具监控 Redis 的性能和状态。
相关推荐
Quantum&Coder17 分钟前
Ruby语言的数据库编程
开发语言·后端·golang
ByteBlossom66619 分钟前
Ruby语言的网络编程
开发语言·后端·golang
静水楼台x1 小时前
Java中json的一点理解
java·后端·json
晴空๓1 小时前
如何查看特定版本的Spring源码
java·spring boot·spring
macrozheng3 小时前
Jenkins+Docker一键打包部署项目!步骤齐全,少走坑路!
java·spring boot·后端·docker·jenkins
!!!5253 小时前
MyBatis-增删改查操作&一些细节
java·数据库·spring boot·mybatis
azhou的代码园3 小时前
基于Java+SpringBoot+Vue的前后端分离的体质测试数据分析及可视化设计
java·vue.js·spring boot
啊晚3 小时前
ASP.NET Core - 依赖注入(四)
后端·asp.net
云端 架构师4 小时前
PL/SQL语言的文件操作
开发语言·后端·golang