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 的性能和状态。
相关推荐
mikey棒棒棒2 小时前
Redis——优惠券秒杀问题(分布式id、一人多单超卖、乐悲锁、CAS、分布式锁、Redisson)
数据库·redis·lua·redisson·watchdog·cas·并发锁
Asthenia04123 小时前
浏览器缓存机制深度解析:电商场景下的性能优化实践
后端
databook4 小时前
『Python底层原理』--Python对象系统探秘
后端·python
超爱吃士力架5 小时前
MySQL 中的回表是什么?
java·后端·面试
追逐时光者6 小时前
Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
后端·.net
Familyism6 小时前
Redis
数据库·redis·缓存
典龙3306 小时前
如何使用springboot项目如何实现小程序里面商品的浏览记录功能案例
spring boot
苏三说技术6 小时前
10亿数据,如何迁移?
后端
bobz9656 小时前
openvpn 显示已经建立,但是 ping 不通
后端
customer087 小时前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源