Spring Boot 集成 Redis 最佳实践
本文介绍在 Spring Boot 中使用 Redis 的依赖安装、基础配置、通用工具类编写与常见使用方法,包含字符串、对象、哈希、集合、分布式锁与限流等场景。示例偏向 Lombok 与 Spring Boot 3/Spring Data Redis(Lettuce)。
依赖安装
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
基础配置
# application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password: ${REDIS_PASSWORD:}
database: 0
timeout: 2000
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
// RedisConfig.java
package com.example.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(customMapper());
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
return template;
}
private ObjectMapper customMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return mapper;
}
}
通用工具类编写
// RedisUtils.java
package com.example.redis;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
@RequiredArgsConstructor
public class RedisUtils {
private final StringRedisTemplate stringRedis;
private final RedisTemplate<String, Object> redis;
// 字符串操作
public void set(String key, String value, long ttlSeconds) {
if (ttlSeconds > 0) {
stringRedis.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds));
} else {
stringRedis.opsForValue().set(key, value);
}
}
public String get(String key) { return stringRedis.opsForValue().get(key); }
public Boolean setIfAbsent(String key, String value, long ttlMillis) {
return stringRedis.opsForValue().setIfAbsent(key, value, Duration.ofMillis(ttlMillis));
}
public Long incr(String key, long delta) { return stringRedis.opsForValue().increment(key, delta); }
public Boolean expire(String key, long ttlSeconds) { return stringRedis.expire(key, ttlSeconds, TimeUnit.SECONDS); }
public Boolean del(String key) { return stringRedis.delete(key); }
// 对象操作(JSON 序列化)
public void setObj(String key, Object value, long ttlSeconds) {
if (ttlSeconds > 0) redis.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS);
else redis.opsForValue().set(key, value);
}
public Object getObj(String key) { return redis.opsForValue().get(key); }
// Hash
public void hset(String key, String field, Object value) { redis.opsForHash().put(key, field, value); }
public Object hget(String key, String field) { return redis.opsForHash().get(key, field); }
public void hmset(String key, Map<String, Object> map) { redis.opsForHash().putAll(key, map); }
// Set
public Long sadd(String key, String... members) { return stringRedis.opsForSet().add(key, members); }
public Long srem(String key, String... members) { return stringRedis.opsForSet().remove(key, (Object[]) members); }
public Boolean sismember(String key, String member) { return stringRedis.opsForSet().isMember(key, member); }
// 简易分布式锁(基于 setIfAbsent + 过期 + Lua 释放)
public Boolean tryLock(String key, String token, long ttlMillis) {
return stringRedis.opsForValue().setIfAbsent(key, token, Duration.ofMillis(ttlMillis));
}
public boolean releaseLock(String key, String token) {
String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long res = stringRedis.execute(new DefaultRedisScript<>(lua, Long.class), Collections.singletonList(key), token);
return res != null && res > 0;
}
}
使用方法
缓存读写(Cache Aside)
// UserService.java
package com.example.user;
import com.example.redis.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserService {
private final RedisUtils redis;
private final UserRepository repo;
public User getById(Long id) {
String key = "user:" + id;
Object cached = redis.getObj(key);
if (cached instanceof User u) { return u; }
User u = repo.findById(id).orElse(null);
if (u != null) { redis.setObj(key, u, 900); }
return u;
}
public void update(User u) {
repo.save(u);
redis.del("user:" + u.getId());
}
}
限流(每分钟 N 次)
// RateLimiter.java
package com.example.limiter;
import com.example.redis.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class RateLimiter {
private final RedisUtils redis;
public boolean allow(String key, int limit) {
String k = "rate:" + key + ":" + (System.currentTimeMillis() / 60000);
Long count = redis.incr(k, 1);
if (count != null && count == 1) { redis.expire(k, 60); }
return count != null && count <= limit;
}
}
分布式锁
// LockExampleService.java
package com.example.lock;
import com.example.redis.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class LockExampleService {
private final RedisUtils redis;
public void criticalSection(String key) {
String token = UUID.randomUUID().toString();
if (Boolean.TRUE.equals(redis.tryLock("lock:" + key, token, 5000))) {
try {
// 关键业务逻辑
} finally {
redis.releaseLock("lock:" + key, token);
}
}
}
}
工程化建议
- 统一 Key 规范:
<业务>:<资源>:<标识>,避免冲突与大 Key。 - 所有缓存必须设置 TTL;更新时先写库再删缓存,防止脏读。
- 对对象使用 JSON 序列化并保持稳定的字段结构;避免存放敏感信息。
- 监控与告警:命中率、慢查询、连接池、内存使用与淘汰策略。
- 生产环境优先使用 Sentinel 或 Cluster,并设置超时与重试策略。
- 如需更强的锁语义,考虑使用 Redisson;或在 Lua 中加入续租与重入逻辑。
常见问题
- 序列化不一致导致读取报错:统一
RedisTemplate的序列化器配置。 - 连接耗尽:合理设置连接池与超时时间,避免阻塞操作。
- 热点 Key:利用本地缓存、分片 Key、随机过期时间减少雪崩与击穿。
- 大量批量写:考虑使用 Pipeline 或批处理,减少 RTT。
总结
- 通过规范化的依赖与配置、统一的
RedisTemplate序列化策略、通用工具类与清晰的使用模式(缓存、限流、锁),可在 Spring Boot 项目中稳定地发挥 Redis 的高性能与可扩展能力。
14 0
评论 (0)
请先登录后再评论
暂无评论