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

ops/2025/1/20 19:26:43/

  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 依赖

   <!-- 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 连接信息

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

redisStringRedisSerializer_56">2.3 重写redis中的StringRedisSerializer序列化器

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;}@Overridepublic Object deserialize(byte[] bytes) {return (bytes == null ? null : new String(bytes, charset));}@Overridepublic 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配置类

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序列化方式采用jacksontemplate.setValueSerializer(valueSerializer);// hash的value序列化方式采用jacksontemplate.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工具类

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 {@Autowiredpublic 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

@RestController
@RequestMapping("/yes")
public class ExtendController extends BaseController {@Autowiredprivate UserInfoService userInfoService;@Autowiredprivate 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 的性能和状态。

http://www.ppmy.cn/ops/151738.html

相关文章

yt-dlp脚本下载音频可选设置代理

import yt_dlp# 配置:是否使用代理 use_proxy = True # 设置为 False 可关闭代理# 代理地址 proxy_url = socks5://127.0.0.1:1089URLS = [https://www.bilibili.com/video/BV1WTktYcEcQ/?spm_id_from=333.1007.tianma.6-2-20.click&vd_source=dcb58f8fe1faf749f438620b…

《多模态语言模型的局限性与生态系统发展现状分析》

1. 多模态语言模型的主要局限性 推理能力问题 复杂推理任务表现不稳定图像理解深度差异大推理过程存在逻辑跳跃 技术实现挑战 视觉特征与语言理解的融合不完善训练数据和方法有限跨模态理解算法需优化 2. 生态系统的不成熟表现 评测标准问题 缺乏标准化评测框架性能评估方法…

复杂查询优化:避免 SQL 查询中的 N+1 查询问题

在 SQL 查询优化中&#xff0c;N1 查询问题是一个常见的性能问题&#xff0c;特别是在关系型数据库中。当你的查询不当时&#xff0c;可能会导致对数据库进行大量的额外查询&#xff0c;造成不必要的性能损耗。 什么是 N1 查询问题&#xff1f; N1 查询问题通常出现在一对多或…

T-SQL语言的数据库交互

T-SQL语言的数据库交互 引言 随着信息技术的不断发展&#xff0c;数据库在各个行业中扮演着越来越重要的角色。数据库的有效管理和优化对于企业的数据安全、效率提升和决策支持至关重要。T-SQL&#xff08;Transact-SQL&#xff09;作为微软SQL Server的重要扩展语言&#xf…

【Rust自学】13.3. 闭包 Pt.3:使用泛型参数和fn trait来存储闭包

13.3.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…

《 C++ 点滴漫谈: 二十二 》操作符炼金术:用C++ operator重塑代码美学

摘要 C 的 operator 关键字和操作符重载是语言的核心特性之一&#xff0c;使开发者能够扩展内置操作符以适应自定义类型&#xff0c;从而实现更高效、直观的代码表达。本文全面解析了 operator 关键字的基本概念、支持重载的操作符范围及其使用场景&#xff0c;详细介绍了操作…

vscode——如何让标点总是成对出现

vscode——如何让标点总是成对出现&#xff1a; 打开vscode&#xff0c;在设置中输入editor.autoClosing 将设置参数全部改成always

Java复习第三天

一、代码题 1.爬楼梯 (1)题目 假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n2 输出:2解释:有两种方法可以爬到楼顶。 1阶1阶 2 阶示例 2: 输入:n3 输出:3解释:有三种方法可以爬到楼顶。 1 阶1阶…