你真的会用 RedisTemplate 吗?
Redis 是现代微服务架构中不可或缺的组件。它不仅仅是一个缓存工具,更是一个功能强大的内存数据结构存储系统,支持丰富的数据结构与多种典型业务场景。Spring 提供的 RedisTemplate 是操作 Redis 的强大工具,但你真的用对了吗?
本文将结合 Redis 的四大核心特性:缓存、数据存储、分布式协调、实时数据处理,通过具体业务场景 + 实战代码,带你深入掌握 RedisTemplate 的正确用法。
一、缓存:提升读取性能的利器
📌 业务场景
电商系统中,商品详情页访问频率极高,若每次都访问数据库,会造成巨大压力。此时,使用 Redis 缓存商品详情有效减轻数据库压力。
✅ 实现思路
典型的 Cache Aside Pattern(旁路缓存模式) :先查缓存,缓存没有再查数据库,并写入缓存。
🧩 示例代码
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductRepository productRepository;
private static final String PRODUCT_KEY_PREFIX = "product:";
public Product getProductById(Long productId) {
String key = PRODUCT_KEY_PREFIX + productId;
// 1. 先查缓存
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2. 缓存未命中,查询数据库
product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("商品不存在"));
// 3. 写入缓存,设置过期时间
redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(10));
return product;
}
}
✅ 注意事项:防止缓存穿透、雪崩等问题,可以设置 null 值缓存、添加随机过期时间等。
二、数据存储:结构化存储用户会话或统计信息
📌 业务场景
用户登录后需要存储会话信息或用户的偏好设置、购物车等状态信息。
✅ 实现思路
利用 Redis 的数据结构,如 Hash、List、Set 等,存储结构化数据。
🧩 示例代码(用户会话信息存储)
@Service
public class SessionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String SESSION_KEY_PREFIX = "session:";
public void saveUserSession(String sessionId, Map<String, Object> sessionData) {
String key = SESSION_KEY_PREFIX + sessionId;
redisTemplate.opsForHash().putAll(key, sessionData);
redisTemplate.expire(key, Duration.ofHours(1));
}
public Map<Object, Object> getUserSession(String sessionId) {
String key = SESSION_KEY_PREFIX + sessionId;
return redisTemplate.opsForHash().entries(key);
}
}
✅ 优势:Hash 结构能很好地存储对象属性,支持字段级别的读取与更新,节省内存。
三、分布式协调:实现分布式锁
📌 业务场景
多个服务节点处理订单,要保证同一个订单只能被处理一次,需要分布式锁控制。
✅ 实现思路
利用 Redis 的 SET key value NX PX timeout 命令实现互斥锁。
🧩 示例代码(简易分布式锁)
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
public boolean tryLock(String key, String value, long expireTimeMillis) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(
LOCK_PREFIX + key, value, Duration.ofMillis(expireTimeMillis));
return Boolean.TRUE.equals(success);
}
public void unlock(String key, String value) {
String redisKey = LOCK_PREFIX + key;
String currentValue = (String) redisTemplate.opsForValue().get(redisKey);
if (value.equals(currentValue)) {
redisTemplate.delete(redisKey);
}
}
}
🧪 使用示例
if (redisDistributedLock.tryLock("order:123", UUID.randomUUID().toString(), 5000)) {
try {
// 处理订单逻辑
} finally {
redisDistributedLock.unlock("order:123", uuid);
}
}
⚠️ 注意:为防止误删锁,需确保只有锁的持有者才能释放。可使用 Lua 脚本增强安全性。
四、实时数据处理:秒杀系统中的库存扣减
📌 业务场景
秒杀活动中,商品库存需高并发扣减,要求实时、原子性操作,不能超卖。
✅ 实现思路
使用 Redis 原子操作,如 decr,配合消息队列处理异步后逻辑。
🧩 示例代码
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String STOCK_KEY_PREFIX = "seckill:stock:";
public boolean trySeckill(Long productId) {
String stockKey = STOCK_KEY_PREFIX + productId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock != null && stock >= 0) {
// 发送订单消息到 MQ(略)
return true;
} else {
// 回滚库存(避免负库存)
redisTemplate.opsForValue().increment(stockKey);
return false;
}
}
}
✅ 优化建议:
- 秒杀前先将库存预热到 Redis
- 使用 Lua 脚本保证原子性更强
- 异步下单处理+数据库最终一致性
总结:RedisTemplate 不止是“缓存工具”
| 特性 | 场景 | 核心 API 用法 |
|---|---|---|
| 缓存 | 商品详情、用户信息缓存 | opsForValue().get/set |
| 数据存储 | 用户会话、购物车 | opsForHash().putAll/entries |
| 分布式协调 | 分布式锁、幂等控制 | setIfAbsent, delete |
| 实时数据处理 | 秒杀扣库存、排行榜 | decrement, increment, Lua 脚本 |
RedisTemplate 是一个强大但容易被误用的工具。理解 Redis 的数据结构与原子性操作,结合实际业务场景,才能真正发挥它的威力。
如果你读到这里,不妨回头问问自己:
你真的会用 RedisTemplate 吗?
欢迎点赞 + 收藏 + 转发,别忘了把这份指南分享给你正在“误用 Redis”的同事 😄
进阶用法
📦 1. Redis 工具类封装
避免每次都写 opsForX(),我们可以封装一个通用的 RedisUtils 工具类,提升开发效率和代码可读性。
🔧 RedisUtils 示例
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ========== String ==========
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public Boolean setIfAbsent(String key, Object value, Duration timeout) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout);
}
// ========== Hash ==========
public void hSet(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
// ========== List ==========
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}
public Object rPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
// ========== Set ==========
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
// ========== 通用 ==========
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
}
🔐 2. 使用 Lua 脚本实现原子性分布式锁
Redis 的分布式锁要确保解锁操作具备“原子性”,避免误删其他线程的锁,Lua 是唯一方式。
📜 Lua 脚本解锁逻辑
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
✅ Java 实现
@Component
public class RedisLockWithLua {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
// 加锁
public boolean tryLock(String key, String value, Duration expire) {
return Boolean.TRUE.equals(
redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + key, value, expire)
);
}
// 解锁(Lua 脚本)
public boolean unlock(String key, String value) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(LOCK_PREFIX + key), value);
return result != null && result == 1L;
}
}
🧩 3. 自定义缓存注解(类似 Spring Cache)
更进一步,我们可以自定义注解 + AOP 拦截,实现自动缓存功能。
🧾 自定义注解
java
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCacheable {
String key(); // 缓存 key
int ttl() default 300; // 缓存时间(秒)
}
🛠 AOP 实现自动缓存处理
@Aspect
@Component
public class RedisCacheAspect {
@Autowired
private RedisUtils redisUtils;
@Around("@annotation(redisCacheable)")
public Object around(ProceedingJoinPoint joinPoint, RedisCacheable redisCacheable) throws Throwable {
String key = redisCacheable.key();
int ttl = redisCacheable.ttl();
// 1. 先查 Redis
Object cache = redisUtils.get(key);
if (cache != null) {
return cache;
}
// 2. 执行业务方法
Object result = joinPoint.proceed();
// 3. 写入缓存
redisUtils.set(key, result, ttl, TimeUnit.SECONDS);
return result;
}
}
📌 使用示例
@Service
public class UserService {
@RedisCacheable(key = "user:info:#{#userId}", ttl = 600)
public User getUserInfo(Long userId) {
// 模拟数据库查询
return userRepository.findById(userId).orElse(null);
}
}
🧠 提示:上面
#{#userId}是 SpEL 表达式,默认不支持,需要额外解析器支持,可以扩展MethodBasedEvaluationContext实现。
✅ 总结:打造 RedisTemplate 的企业级用法
| 功能模块 | 技术点 | 优势 |
|---|---|---|
| Redis 工具类封装 | 通用 API 封装 | 提高代码可读性、复用性 |
| Lua 分布式锁 | Redis + Lua 脚本解锁 | 解锁原子性保障,防止误删 |
| 注解缓存 | AOP + 注解 + RedisTemplate | 自动缓存,提高开发效率 |
这些进阶用法能够帮助你将 RedisTemplate 真正用于生产级场景,打造高性能、可维护的服务系统。