作者:洛水石
> 标签: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优化 | 避坑指南

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐