最常见8个Redis误用案例
作者:洛水石
> 标签:Redis、缓存、Java、性能优化、避坑
__________________________________________________
一、误用1:Key 没有设置过期时间

1.1 问题现象
``__INLINE_java
// ❌ 错误:不设置过期时间
redisTemplate.opsForValue().set("user:1001", userJson);
// 结果:内存持续增长,最终 OOM
__`__INLINE_
危害:
- 内存无限增长
- 冷数据占用热数据空间
- 最终触发 OOM,服务宕机

1.2 正确做法
__`__INLINE_java
// ✅ 正确:必须设置过期时间
redisTemplate.opsForValue().set("user:1001", userJson, 30, TimeUnit.MINUTES);
// ✅ 更优:根据业务场景设置合理的TTL
public enum CacheTTL {
USER_INFO(30, TimeUnit.MINUTES), // 用户信息30分钟
PRODUCT_DETAIL(10, TimeUnit.MINUTES), // 商品详情10分钟
SESSION(24, TimeUnit.HOURS), // 会话24小时
RATE_LIMIT(1, TimeUnit.MINUTES); // 限流1分钟
private final long duration;
private final TimeUnit unit;
}
__`__INLINE_
1.3 监控与治理
__`__INLINE_bash
查看内存使用情况
redis-cli INFO memory
查看 key 的数量和过期情况
redis-cli INFO keyspace
扫描没有过期时间的 key
redis-cli --scan --pattern '*' | while read key; do
ttl=$(redis-cli TTL "$key")
if [ "$ttl" -eq -1 ]; then
echo "No TTL: $key"
fi
done
__`__INLINE_
__________________________________________________
二、误用2:使用 Keys 命令扫描大数据量

2.1 问题现象
__`__INLINE_java
// ❌ 错误:生产环境使用 keys
Set<String__QUOTE_1__
keys 命令的危害:
- 时间复杂度 O(N)
- 阻塞 Redis 主线程
- 生产环境禁止使用
2.2 正确做法:使用 Scan
__`__INLINE_java
// ✅ 正确:使用 SCAN 迭代
public Set<String__QUOTE_2__
__________________________________________________
三、误用3:缓存穿透没有防护
3.1 问题现象
__`__INLINE_java
// ❌ 错误:查询不到就不缓存
public User getUser(Long userId) {
String key = "user:" + userId;
String json = redisTemplate.opsForValue().get(key);
if (json == null) {
// 每次都打到数据库
User user = userMapper.selectById(userId);
return user; // 没有缓存空值
}
return JSON.parseObject(json, User.class);
}
__`__INLINE_
危害:
- 恶意请求不存在的 key
- 数据库压力剧增
- 服务雪崩
3.2 正确做法:缓存空值+布隆过滤器
__`__INLINE_java
@Service
public class UserService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RBloomFilter<Long__QUOTE_3__
__________________________________________________
四、误用4:缓存雪崩没有预案
4.1 问题现象
__`__INLINE_java
// ❌ 错误:所有 key 同时过期
redisTemplate.opsForValue().set("product:1", json, 60, TimeUnit.MINUTES);
redisTemplate.opsForValue().set("product:2", json, 60, TimeUnit.MINUTES);
redisTemplate.opsForValue().set("product:3", json, 60, TimeUnit.MINUTES);
// 60分钟后,所有 key 同时失效,DB 被打挂
__`__INLINE_
4.2 正确做法:随机过期时间
__`__INLINE_java
@Service
public class CacheService {
private static final int BASE_TTL = 60; // 基础60分钟
private static final int RANDOM_RANGE = 10; // 随机范围10分钟
public void setWithRandomTTL(String key, Object value) {
// 基础时间 + 随机偏移
int ttl = BASE_TTL + ThreadLocalRandom.current().nextInt(RANDOM_RANGE);
redisTemplate.opsForValue().set(key, JSON.toJSONString(value), ttl, TimeUnit.MINUTES);
}
/**
* 多级缓存+熔断
*/
@Cacheable(value = "product", key = "#productId")
@CircuitBreaker(name = "product", fallbackMethod = "getProductFallback")
public Product getProduct(Long productId) {
// 1. 本地缓存 (Caffeine)
Product local = localCache.getIfPresent(productId);
if (local != null) return local;
// 2. Redis
String key = "product:" + productId;
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
Product product = JSON.parseObject(json, Product.class);
localCache.put(productId, product);
return product;
}
// 3. 数据库
Product product = productMapper.selectById(productId);
if (product != null) {
setWithRandomTTL(key, product);
localCache.put(productId, product);
}
return product;
}
public Product getProductFallback(Long productId, Exception ex) {
// 返回兜底数据(静态页面、默认商品)
return Product.defaultProduct();
}
}
__`__INLINE_
__________________________________________________
五、误用5:使用 Redis 做消息队列
5.1 问题现象
__`__INLINE_java
// ❌ 错误:使用 List 做消息队列
redisTemplate.opsForList().leftPush("queue:order", orderJson);
String order = redisTemplate.opsForList().rightPop("queue:order");
// 问题:
// 1. 没有 ACK 机制,消息可能丢失
// 2. 没有重试机制
// 3. 消费者故障,消息堆积
__`__INLINE_
5.2 正确做法:使用 Stream 或专业 MQ
__`__INLINE_java
// ✅ 正确:使用 Redis Stream(Redis 5.0+)
@Service
public class RedisStreamProducer {
@Autowired
private StringRedisTemplate redisTemplate;
public void sendOrder(Order order) {
Map<String, String__QUOTE_4__
@Service
public class RedisStreamConsumer {
@Autowired
private StringRedisTemplate redisTemplate;
@PostConstruct
public void consume() {
StreamMessageListenerContainer<String, ObjectRecord<String, String>__QUOTE_5__
建议:生产环境使用 RabbitMQ / RocketMQ / Kafka
__________________________________________________
六、误用6:大 Key 问题
6.1 问题现象
__`__INLINE_java
// ❌ 错误:存储大对象
String hugeJson = JSON.toJSONString(allUsers); // 10MB
redisTemplate.opsForValue().set("all:users", hugeJson);
// 问题:
// 1. 阻塞 Redis(单线程)
// 2. 网络传输慢
// 3. 内存碎片
__`__INLINE_
6.2 正确做法:拆分+压缩
__`__INLINE_java
@Service
public class BigKeySolution {
/**
* 方案1:拆分为 Hash
*/
public void saveUsersByHash(List<User__QUOTE_6__
__________________________________________________
七、误用7:事务使用不当
7.1 问题现象
__`__INLINE_java
// ❌ 错误:认为 Redis 事务和数据库一样
redisTemplate.multi();
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForValue().set("key2", "value2");
redisTemplate.exec();
// 问题:
// 1. Redis 事务没有回滚
// 2. 命令排队期间,其他客户端可执行命令
// 3. 不是原子性执行
__`__INLINE_
7.2 正确做法:使用 Lua 脚本或 Redisson
__`__INLINE_java
@Service
public class RedisTransaction {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 方案1:Lua 脚本保证原子性
*/
public boolean atomicDecrement(String stockKey, int quantity) {
String lua =
"local stock = tonumber(redis.call('get', KEYS[1]));" +
"if stock == nil then return -1 end;" +
"if stock < tonumber(ARGV[1]) then return 0 end;" +
"redis.call('decrby', KEYS[1], ARGV[1]);" +
"return 1;";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(lua, Long.class),
Collections.singletonList(stockKey),
String.valueOf(quantity)
);
return result != null && result == 1;
}
/**
* 方案2:Redisson 分布式锁
*/
public void deductStockWithLock(Long productId, int quantity) {
RLock lock = redissonClient.getLock("stock:lock:" + productId);
try {
// 尝试获取锁,最多等待3秒,持有10秒
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("获取锁失败,请重试");
}
// 执行业务逻辑
Integer stock = Integer.valueOf(redisTemplate.opsForValue().get("stock:" + productId));
if (stock < quantity) {
throw new BusinessException("库存不足");
}
redisTemplate.opsForValue().decrement("stock:" + productId, quantity);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("锁中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
__`__INLINE_
__________________________________________________
八、误用8:持久化配置不当
8.1 问题现象
__`__INLINE_bash
❌ 错误:AOF 每次都 fsync
appendonly yes
appendfsync always
结果:写入性能暴跌,TPS 从 10万降到 1千
__`__INLINE_
8.2 正确做法:根据场景选择
__`__INLINE_bash
✅ 推荐配置
开启 AOF(数据安全性优先)
appendonly yes
每秒 fsync(平衡性能和安全)
appendfsync everysec
开启 RDB(快速恢复)
save 900 1
save 300 10
save 60 10000
AOF 重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
__``
|
配置 |
数据安全 |
性能 |
适用场景 |
|
appendfsync always |
最高 |
最低 |
金融交易 |
|
appendfsync everysec |
高 |
中 |
通用推荐 |
|
appendfsync no |
低 |
最高 |
缓存场景 |
__________________________________________________
总结
|
误用 |
危害 |
解决方案 |
|
无过期时间 |
OOM |
强制TTL |
|
Keys扫描 |
阻塞 |
Scan命令 |
|
缓存穿透 |
DB雪崩 |
空值缓存/布隆过滤器 |
|
缓存雪崩 |
同时失效 |
随机TTL+多级缓存 |
|
List做队列 |
消息丢失 |
Stream/专业MQ |
|
大Key |
阻塞/慢 |
拆分/压缩 |
|
事务误用 |
数据不一致 |
Lua/Redisson |
|
持久化不当 |
性能差 |
everysec |
作者:洛水石 | Redis优化 | 避坑指南
作者:洛水石 | Redis优化 | 避坑指南
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)