Redis 作为最主流的分布式缓存,几乎是 SpringBoot 项目的"标配"------无论是减轻数据库压力、提升接口响应速度,还是实现会话共享、分布式锁,都离不开它。
本篇文章就来介绍一下 SpringBoot 整合 Redis的操作步骤, 同时讲讲Redis中 String、Hash、List、Set、ZSet 五种核心数据类型 的增删改查,还有缓存失效、序列化配置、注意事项等。
一、为什么用 Redis 做缓存?
在项目中引入 Redis,核心解决两个核心问题:
-
• 减轻数据库压力:把高频查询数据(如用户信息、商品列表)缓存到 Redis,避免每次请求都查数据库,接口响应速度从毫秒级提升到微秒级;
-
• 支持多种场景:除了缓存,还能实现分布式锁、计数器、限流、消息队列(简单场景)、排行榜等功能;
-
• 高性能、高可用:Redis 基于内存操作,读写速度极快,支持主从复制、哨兵模式,稳定性拉满。
二、第一步:引入依赖
SpringBoot 提供了专门的 Redis 启动器,无需手动配置复杂依赖,直接引入即可:
go
<!-- SpringBoot Redis 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
说明 :spring-boot-starter-data-redis 底层已经集成了 Redis 客户端(默认 Lettuce,替代了早期的 Jedis),配合 commons-pool2 连接池,性能更优。
三、第二步:application.yml 核心配置
配置 Redis 连接信息、连接池、序列化方式:
go
spring:
redis:
# Redis 服务器地址(本地/线上地址)
host: localhost
# 端口(默认6379)
port: 6379
# 密码(如果没设置密码,注释掉即可)
password: 123456
# 数据库索引(Redis 默认有16个库,0-15,可按需切换)
database: 0
# 超时时间(连接、读取、写入超时,单位毫秒)
timeout: 5000
# 连接池配置(Lettuce 连接池)
lettuce:
pool:
# 最大连接数(核心,根据业务压测调整,默认8)
max-active: 100
# 最大空闲连接
max-idle: 20
# 最小空闲连接
min-idle: 5
# 连接等待超时时间(毫秒)
max-wait: 1000
# 自定义 Redis 序列化配置(避免缓存乱码,可选但推荐)
redis:
serialization:
key-prefix: "springboot:redis:" # 缓存key前缀,避免不同项目key冲突
expire-default: 3600 # 默认缓存过期时间(秒),1小时
四、第三步:Redis 序列化配置
SpringBoot 默认的 Redis 序列化方式会导致缓存的 key、value 乱码,不利于调试,我们自定义配置,使用 JSON 序列化,兼顾可读性和性能:
go
package com.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "redis.serialization")
public class RedisConfig {
// 缓存key前缀
private String keyPrefix;
// 默认缓存过期时间(秒)
private Long expireDefault;
// 1. 配置 RedisTemplate(操作Redis的核心工具类)
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 配置key序列化(String类型,避免乱码)
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// 配置value序列化(JSON类型,可读性强,支持对象)
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(jsonSerializer);
redisTemplate.setHashValueSerializer(jsonSerializer);
// 初始化参数
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
// 2. 配置 RedisCacheManager(配合@Cacheable注解使用,缓存管理)
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
// 基础配置(序列化、过期时间)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 过期时间
.entryTtl(Duration.ofSeconds(expireDefault))
// key序列化
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// value序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
// 允许缓存null值(可选,根据业务调整)
.disableCachingNullValues();
// 自定义不同缓存的过期时间(可选)
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
// 比如:用户缓存过期时间2小时,商品缓存过期时间1小时
cacheConfigs.put("userCache", config.entryTtl(Duration.ofSeconds(7200)));
cacheConfigs.put("productCache", config.entryTtl(Duration.ofSeconds(3600)));
// 构建缓存管理器
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.build();
}
// getter/setter(Lombok可省略)
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public Long getExpireDefault() {
return expireDefault;
}
public void setExpireDefault(Long expireDefault) {
this.expireDefault = expireDefault;
}
}
五、Redis 五种数据类型操作
我们通过 RedisTemplate 操作 Redis,下面分别实战 String、Hash、List、Set、ZSet 五种核心数据类型,每种类型都包含「增、删、改、查」完整用法,可直接复制到项目中使用。
1. String 类型(最常用,适合单个值、字符串、对象缓存)
适用场景:用户信息、验证码、令牌、单个数值(如计数器),是最基础、最常用的类型。
go
package com.demo.service;
import com.demo.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisStringService {
private final RedisTemplate<String, Object> redisTemplate;
// 从配置文件读取key前缀
private final RedisConfig redisConfig;
// 拼接key(避免重复,规范写法)
private String getKey(String key) {
return redisConfig.getKeyPrefix() + key;
}
// 1. 新增String缓存(无过期时间)
public void set(String key, Object value) {
redisTemplate.opsForValue().set(getKey(key), value);
}
// 2. 新增String缓存(带过期时间)
public void setWithExpire(String key, Object value, Long expireTime, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(getKey(key), value, expireTime, timeUnit);
}
// 3. 获取String缓存
public Object get(String key) {
return redisTemplate.opsForValue().get(getKey(key));
}
// 4. 修改String缓存(直接覆盖)
public void update(String key, Object value) {
this.set(key, value);
}
// 5. 删除String缓存
public Boolean delete(String key) {
return redisTemplate.delete(getKey(key));
}
// 6. 自增(计数器场景,如文章阅读量、点赞数)
public Long increment(String key, Long delta) {
return redisTemplate.opsForValue().increment(getKey(key), delta);
}
// 7. 自减
public Long decrement(String key, Long delta) {
return redisTemplate.opsForValue().decrement(getKey(key), delta);
}
// 实战示例:缓存用户信息
public void cacheUser(User user) {
// 缓存key:springboot:redis:user:1(1是用户ID)
String key = "user:" + user.getId();
// 过期时间:2小时(7200秒)
this.setWithExpire(key, user, 7200L, TimeUnit.SECONDS);
}
// 实战示例:获取缓存的用户信息
public User getCachedUser(Long userId) {
String key = "user:" + userId;
return (User) this.get(key);
}
}
2. Hash 类型(适合存储对象,可单独操作对象字段)
适用场景:用户信息、商品信息等对象缓存,无需修改整个对象,可单独修改某个字段(如用户昵称、商品库存),节省内存和带宽。
go
package com.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisHashService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisConfig redisConfig;
private String getKey(String key) {
return redisConfig.getKeyPrefix() + key;
}
// 获取Hash操作对象
private HashOperations<String, String, Object> getHashOps() {
return redisTemplate.opsForHash();
}
// 1. 新增Hash缓存(存储整个对象,key是对象标识,hashKey是字段名)
public void putAll(String key, Map<String, Object> hashMap) {
String redisKey = getKey(key);
getHashOps().putAll(redisKey, hashMap);
// 设置过期时间
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
}
// 2. 新增单个Hash字段
public void put(String key, String hashKey, Object value) {
String redisKey = getKey(key);
getHashOps().put(redisKey, hashKey, value);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
}
// 3. 获取单个Hash字段值
public Object get(String key, String hashKey) {
return getHashOps().get(getKey(key), hashKey);
}
// 4. 获取整个Hash对象(所有字段和值)
public Map<String, Object> getAll(String key) {
return getHashOps().entries(getKey(key));
}
// 5. 获取所有Hash字段名
public Set<String> getHashKeys(String key) {
return getHashOps().keys(getKey(key));
}
// 6. 修改单个Hash字段
public void update(String key, String hashKey, Object value) {
this.put(key, hashKey, value);
}
// 7. 删除单个Hash字段
public Long delete(String key, String... hashKeys) {
return getHashOps().delete(getKey(key), (Object[]) hashKeys);
}
// 实战示例:缓存用户信息(Hash类型)
public void cacheUserHash(Long userId, Map<String, Object> userMap) {
// 缓存key:springboot:redis:user:hash:1
String key = "user:hash:" + userId;
this.putAll(key, userMap);
}
// 实战示例:修改用户昵称(单独修改Hash字段)
public void updateUserName(Long userId, String newName) {
String key = "user:hash:" + userId;
this.put(key, "username", newName);
}
}
3. List 类型(有序、可重复,适合队列、列表场景)
适用场景:消息队列(简单场景)、最新列表、历史记录(如浏览记录、消息列表),支持从头部、尾部插入/删除数据。
go
package com.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisListService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisConfig redisConfig;
private String getKey(String key) {
return redisConfig.getKeyPrefix() + key;
}
// 获取List操作对象
private ListOperations<String, Object> getListOps() {
return redisTemplate.opsForList();
}
// 1. 从列表头部插入数据
public Long leftPush(String key, Object value) {
String redisKey = getKey(key);
Long result = getListOps().leftPush(redisKey, value);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
return result;
}
// 2. 从列表尾部插入数据
public Long rightPush(String key, Object value) {
String redisKey = getKey(key);
Long result = getListOps().rightPush(redisKey, value);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
return result;
}
// 3. 从列表头部弹出数据(弹出后删除)
public Object leftPop(String key) {
return getListOps().leftPop(getKey(key));
}
// 4. 从列表尾部弹出数据(弹出后删除)
public Object rightPop(String key) {
return getListOps().rightPop(getKey(key));
}
// 5. 获取列表指定范围的数据(start=0,end=-1 表示所有数据)
public List<Object> range(String key, Long start, Long end) {
return getListOps().range(getKey(key), start, end);
}
// 6. 获取列表长度
public Long size(String key) {
return getListOps().size(getKey(key));
}
// 7. 删除列表中指定值(count=0删除所有,count=1删除第一个,count=-1删除最后一个)
public Long remove(String key, Long count, Object value) {
return getListOps().remove(getKey(key), count, value);
}
// 实战示例:存储用户浏览记录(从尾部插入,保留最新10条)
public void addBrowseRecord(Long userId, String productId) {
String key = "browse:record:" + userId;
// 从尾部插入最新浏览的商品ID
this.rightPush(key, productId);
// 限制列表长度,只保留最新10条
Long size = this.size(key);
if (size > 10) {
// 从头部删除多余数据
this.leftPop(key);
}
}
// 实战示例:获取用户最新浏览记录
public List<Object> getBrowseRecords(Long userId) {
String key = "browse:record:" + userId;
// 获取所有浏览记录(从第0条到最后一条)
return this.range(key, 0L, -1L);
}
}
四、Set 类型(无序、不可重复的集合)
Set 是 Redis 中一种无序 且元素不可重复 的数据结构。它非常适合处理需要去重 、计算交集/并集的场景。
🎯 核心适用场景
-
• 标签系统:用户标签、商品标签等。
-
• 数据去重:例如去重的用户ID集合。
-
• 社交关系:好友列表、共同好友(交集)、推荐好友(差集)。
下面是一个完整的 RedisSetService 服务类,封装了 Set 的常用操作。
go
package com.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Service;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisSetService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisConfig redisConfig;
private String getKey(String key) {
return redisConfig.getKeyPrefix() + key;
}
// 获取Set操作对象
private SetOperations<String, Object> getSetOps() {
return redisTemplate.opsForSet();
}
// 1. 向Set中添加数据(可添加多个)
public Long add(String key, Object... values) {
String redisKey = getKey(key);
Long result = getSetOps().add(redisKey, values);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
return result;
}
// 2. 获取Set中所有数据
public Set<Object> members(String key) {
return getSetOps().members(getKey(key));
}
// 3. 判断数据是否在Set中(去重校验)
public Boolean isMember(String key, Object value) {
return getSetOps().isMember(getKey(key), value);
}
// 4. 删除Set中的指定数据
public Long remove(String key, Object... values) {
return getSetOps().remove(getKey(key), values);
}
// 5. 获取Set的长度
public Long size(String key) {
return getSetOps().size(getKey(key));
}
// 6. 交集(两个Set的共同数据,如共同好友)
public Set<Object> intersect(String key1, String key2) {
return getSetOps().intersect(getKey(key1), getKey(key2));
}
// 7. 并集(两个Set的所有数据,去重)
public Set<Object> union(String key1, String key2) {
return getSetOps().union(getKey(key1), getKey(key2));
}
// 实战示例:给用户添加标签(去重)
public void addUserTag(Long userId, String... tags) {
String key = "user:tag:" + userId;
this.add(key, tags);
}
// 实战示例:获取用户所有标签
public Set<Object> getUserTags(Long userId) {
String key = "user:tag:" + userId;
return this.members(key);
}
// 实战示例:获取两个用户的共同标签
public Set<Object> getCommonTags(Long userId1, Long userId2) {
String key1 = "user:tag:" + userId1;
String key2 = "user:tag:" + userId2;
return this.intersect(key1, key2);
}
}
五、ZSet 类型(有序集合)
ZSet (Sorted Set) 在 Set 的基础上,为每个元素关联了一个 分数(score) ,并根据分数进行排序。它是实现排行榜功能的绝佳选择。
🎯 核心适用场景
- • 各类排行榜:商品销量榜、文章点赞榜、用户积分榜、游戏高分榜。
以下 RedisZSetService 展示了 ZSet 的核心操作和排行榜实现。
go
package com.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class RedisZSetService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisConfig redisConfig;
private String getKey(String key) {
return redisConfig.getKeyPrefix() + key;
}
// 获取ZSet操作对象
private ZSetOperations<String, Object> getZSetOps() {
return redisTemplate.opsForZSet();
}
// 1. 向ZSet中添加数据(指定分数)
public Boolean add(String key, Object value, Double score) {
String redisKey = getKey(key);
Boolean result = getZSetOps().add(redisKey, value, score);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
return result;
}
// 2. 批量添加ZSet数据
public Long addBatch(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
String redisKey = getKey(key);
Long result = getZSetOps().add(redisKey, tuples);
redisTemplate.expire(redisKey, redisConfig.getExpireDefault(), TimeUnit.SECONDS);
return result;
}
// 3. 增加元素的分数(如点赞数+1、积分+10)
public Double incrementScore(String key, Object value, Double delta) {
return getZSetOps().incrementScore(getKey(key), value, delta);
}
// 4. 按分数升序排序,获取指定范围的数据
public Set<ZSetOperations.TypedTuple<Object>> rangeByScore(String key, Double min, Double max) {
return getZSetOps().rangeByScoreWithScores(getKey(key), min, max);
}
// 5. 按分数降序排序,获取指定范围的数据(排行榜常用,如前10名)
public Set<ZSetOperations.TypedTuple<Object>> reverseRangeByScore(String key, Double min, Double max, Long start, Long end) {
return getZSetOps().reverseRangeByScoreWithScores(getKey(key), min, max, start, end);
}
// 6. 获取元素的分数
public Double score(String key, Object value) {
return getZSetOps().score(getKey(key), value);
}
// 7. 获取ZSet的长度
public Long size(String key) {
return getZSetOps().size(getKey(key));
}
// 8. 删除ZSet中的指定元素
public Long remove(String key, Object... values) {
return getZSetOps().remove(getKey(key), values);
}
// 实战示例:商品销量排行榜(降序,取前10名)
public Set<ZSetOperations.TypedTuple<Object>> getProductSalesTop10() {
String key = "product:sales:rank";
// 分数从0到无穷大,取前10名,降序排列
return this.reverseRangeByScore(key, 0.0, Double.MAX_VALUE, 0L, 9L);
}
// 实战示例:更新商品销量(销量+1,分数+1)
public void updateProductSales(String productId) {
String key = "product:sales:rank";
this.incrementScore(key, productId, 1.0);
}
}
六、@Cacheable 注解缓存
如果业务场景只是简单的 "查询-缓存" ,无需复杂的 Redis 操作,强烈推荐使用 Spring 提供的 @Cacheable 注解。它能自动实现 "查询数据库 → 存入缓存 → 后续命中缓存" 的完整流程,极大简化代码。
💡 核心注解说明
-
•
@Cacheable:在方法执行前检查缓存,命中则直接返回,未命中则执行方法并将结果存入缓存。 -
•
@CachePut:无论缓存是否存在,都会执行方法,并用结果更新缓存。常用于更新操作。 -
•
@CacheEvict:删除指定缓存。常用于删除操作。
go
package com.demo.service;
import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserCacheService {
private final UserMapper userMapper;
// 1. @Cacheable:查询时缓存,key是用户ID,缓存名称是userCache(对应RedisCacheManager配置)
@Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
// 若缓存中没有,才会执行此方法(查询数据库)
return userMapper.selectById(userId);
}
// 2. @CachePut:更新数据时,同步更新缓存(保证缓存与数据库一致)
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user;
}
// 3. @CacheEvict:删除数据时,删除对应的缓存
@CacheEvict(value = "userCache", key = "#userId")
public void deleteUser(Long userId) {
userMapper.deleteById(userId);
}
}
重要提示 :使用
@Cacheable等注解,必须 在 SpringBoot 启动类上添加@EnableCaching注解来开启缓存功能。
七、注意事项
从开发到上线,以下要点能帮你避开大多数"坑"。
| 问题 | 现象与风险 | 解决方案 |
|---|---|---|
| 1. 缓存乱码 | 读取出的值是乱码或无法反序列化。 | 必须配置自定义序列化 (如 Jackson2JsonRedisSerializer),避免使用默认的 JdkSerializationRedisSerializer。 |
| 2. 缓存穿透 | 查询一个数据库中根本不存在的数据,导致请求直达数据库。 | 1. 缓存空值(null)并设置较短过期时间。 2. 使用布隆过滤器预先校验。 |
| 3. 缓存击穿 | 某个热点Key突然过期,大量并发请求瞬间涌向数据库。 | 1. 设置热点Key永不过期 。 2. 使用互斥锁(Mutex),只让一个线程去查库,其他线程等待。 |
| 4. 缓存雪崩 | 大量Key在同一时间过期,导致所有请求落库,数据库压力骤增。 | 1. 给缓存过期时间加上随机值 ,分散过期时间。 2. 核心数据永不过期,通过后台任务异步更新。 |
| 5. Key命名规范 | 不同项目或模块的Key冲突,导致数据错乱。 | 统一添加前缀 ,如 项目名:模块名:业务名:id。 |
| 6. 过期时间设置 | 过长占用内存,过短导致缓存命中率低。 | 根据业务特点设置,高频变化数据 时间短,低频稳定数据时间长。 |
| 7. Redis安全 | 线上环境未设密码,导致被恶意攻击或数据泄露。 | 必须设置强密码 ,并配置防火墙限制访问IP。 |
| 8. 连接池配置 | 高并发下连接池耗尽,出现获取连接超时错误。 | 根据压测结果,合理调整 max-active、max-idle、min-idle 等参数。 |
大家在项目中用 Redis 时,有没有遇到过缓存穿透、雪崩的"惊险"时刻?或者有什么独家的 Redis 调优技巧?
欢迎在评论区留言交流,分享你的实战经验~ 关注我 ,后续持续更新 SpringBoot 实战教程,从基础到高级,带你轻松搞定后端开发,少踩坑、多高效,我们下期再见!