Source:https://redisson.org/docs/
GitHub:https://github.com/redisson/redisson
Redis:https://redis.io/documentation
Capability Boundaries
✅ Strong Suits
- Cache key conventions — format
{业务名}:{类型}:{ID}, unified prefix - Expiration time principles — long TTL for hot data, short TTL for non-hot data, never set -1
- Serialization choices — comparison and configuration of the three options: JSON vs JDK vs String
- Redisson distributed locks — tryLock/watchDog/leaseTime/reentrant/fair lock/RedLock
- Protection against the three major cache problems — penetration (Bloom filter/null value)/breakdown (mutex lock)/avalanche (random TTL)
- Caffeine vs Redis selection — when to use local cache and when to use Redis
- Correct RedisTemplate configuration — unified Key/Value serialization
❌ Out of Scope
- Message queue/pub-sub → kafka / RabbitMQ
- Spring Session sharing → spring-security
- Jedis/Lettuce client selection → this document recommends Lettuce (Spring Boot default)
Serialization choice decision
| Option | Pros | Cons | Applicable scenarios |
|---|---|---|---|
| StringRedisTemplate | Simple, readable | Can only store String, objects need manual serialization | Pure string caching |
| RedisTemplate + Jackson2JsonRedisSerializer | Readable, cross-language | Slightly lower performance | ✅ Recommended (suitable for most scenarios) |
| RedisTemplate + JdkSerializationRedisSerializer | Default (includes type information) | Unreadable, large size, Java-only | ❌ Avoid (it is the default) |
| RedisTemplate + GenericJackson2JsonRedisSerializer | Contains @class type | Many redundant fields | Caching complex objects |
The mistakes LLMs make most often
| # | Mistake | Correct approach |
|---|---|---|
| 1 | Cache has no expiration time (permanent) | TTL must be set to prevent memory overflow |
| 2 | Mixing StringRedisTemplate and RedisTemplate | Use one consistently, RedisTemplate + JSON serialization is recommended |
| 3 | Cache penetration (querying non-existent data goes straight through to DB) | Null value caching (Boolean.FALSE, TTL 5 minutes) or Bloom filter |
| 4 | Using Redis for high-frequency hot caching (over 1000 qps each time) | Add a Caffeine local cache layer (L1 local + L2 Redis) |
| 5 | lock.lock() without try-finally |
must finally { lock.unlock() } |
| 6 | Cache keys have no naming convention | Unified format:saas:user:10001、saas:order:list |
| 7 | Redisson specifies leaseTime but still expects the watchdog to renew | When leaseTime is specified, the watchdog does not take effect! |
| 8 | Cache avalanche is not handled (a large number of keys expire at the same time) | Add a random value to TTL (±30%) |
| 9 | Hot key cache breakdown | Mutex lock or never expire + asynchronous update |
| 10 | Using RedisTemplate default JDK serialization | Change to JSON serialization (readable, cross-language) |
Core patterns
Pattern 1: Unified RedisTemplate configuration (JSON serialization)
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// ✅ 推荐:Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LazySerializationTypeValidator.getDefaultTyper(),
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer()); // Key: String
template.setValueSerializer(serializer); // Value: JSON
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
Pattern 2: Cache penetration protection (null value caching)
public User getUser(Long id) {
String key = String.format("saas:user:%s", id);
User user = (User) redisTemplate.opsForValue().get(key);
// 空值标记,直接返回
if (user == NULL_PLACEHOLDER) return null;
// 缓存命中
if (user != null) return user;
// 查询DB
user = userMapper.selectById(id);
if (user == null) {
// ✅ 空值缓存(防穿透),TTL 5分钟
redisTemplate.opsForValue().set(key, NULL_PLACEHOLDER, 5, TimeUnit.MINUTES);
return null;
}
// ✅ 正常缓存,30分钟
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
return user;
}
Pattern 3: The three major Redisson distributed lock patterns
Pattern A: tryLock + watchDog (recommended) — has wait time + automatic renewal
RLock lock = redissonClient.getLock("lock:order:" + orderId);
// ✅ 只指定 waitTime,不指定 leaseTime → watchDog 生效(默认30s续期)
if (lock.tryLock(10, TimeUnit.SECONDS)) { // 等10秒获取锁
try {
// 业务处理(可能超过30秒,watchDog自动续期)
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
Pattern B: tryLock + leaseTime — hard timeout (watchdog does not take effect)
RLock lock = redissonClient.getLock("lock:order:" + orderId);
// ⚠️ 指定了 leaseTime → watchDog 不生效
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
// 必须在30秒内完成,否则锁自动释放
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
Pattern C: lock() — blocking wait + watchDog
RLock lock = redissonClient.getLock("lock:order:" + orderId);
lock.lock(); // 阻塞直到获取锁,watchDog 生效
try {
// 业务处理
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
Pattern 4: Cache avalanche protection (random TTL)
// 相同业务场景的缓存 TTL 加随机值,避免同时过期
int baseTtl = 3600; // 1小时
int randomTtl = baseTtl + new Random().nextInt(600); // +0~10分钟随机
redisTemplate.opsForValue().set(key, value, randomTtl, TimeUnit.SECONDS);
Pattern 5: Cache Key conventions
// 命名格式: {业务}:{领域}:{实体}:{唯一标识}
private static final String USER_CACHE_KEY = "saas:user:detail:%s";
private static final String ORDER_LIST_KEY = "saas:order:list:%s:%d"; // userId + page
private static final String CONFIG_KEY = "saas:config:global";
String key = String.format(USER_CACHE_KEY, userId);
Gotchas
- Caches must have an expiration time set — never setting a TTL = memory leak
- Redisson lock must be unlocked with try-finally — otherwise deadlock
- watchDog automatically renews the lease — when leaseTime is not specified, defaults to 30s, renewing every 10s
- If leaseTime is specified, the watchdog does not take effect — if you need automatic renewal, do not specify leaseTime
- Redis Cluster does not support batch mget (cross-Slot) — switch to a loop of get or use hash tags to pin the Slot
- Cache avalanche: a large number of Keys expire at the same time — add a random value to the TTL (±30%)
- Cache breakdown: a hot Key expires and a large number of requests penetrate at that instant — mutex lock or never expire + asynchronous update
- Caffeine is better suited for high-frequency caching in a single service — Redis is used for distributed sharing, Caffeine is used for local acceleration
- isHeldByCurrentThread() must be checked before unlock — to avoid IllegalMonitorStateException
- RedisTemplate defaults to JDK serialization — switch to JSON serialization (readable, cross-language)
- Lettuce (Spring Boot default) is more recommended than Jedis — asynchronous, connection pooling, active community
- Bloom filter (RedissonBloomFilter) is used to prevent penetration — suitable for a large number of keys and when 100% accuracy is not required
Data Privacy
This skill does not collect, store, or transmit any user data.