像素岛沙盒冒险
64.86MB · 2026-02-07
这不是电影桥段,是去年618大促前夜的真实事故。今天,把血泪教训熬成干货,手把手教你构建高可用Redis整合方案。
# application.yml(事故配置)
spring:
redis:
host: prod-redis
port: 6379
password: xxx
# 仅配置基础项 → 连接池用默认值!
后果:
max-active=8(Spring Boot 2.0+)TimeoutExceptionspring:
redis:
host: ${REDIS_HOST:127.0.0.1}
port: 6379
password: ${REDIS_PASSWORD}
timeout: 2000ms
lettuce:
pool:
max-active: 200 # 核心!根据QPS动态调整(公式见下文)
max-idle: 50
min-idle: 20
max-wait: 3000ms # 超过3秒直接熔断,避免线程堆积
shutdown-timeout: 100ms
# 高级防护
cluster:
max-redirects: 3 # 集群模式防重定向风暴
max-active ≈ (单机QPS × 平均响应时间ms) / 1000 × 安全系数(1.5)
示例:单机QPS=1500,平均RT=50ms → 1500×50/1000×1.5 ≈ 113 → 取整120
笔者实践:在Apollo配置中心动态调整参数,大促前预热扩容,大促后自动缩容
// 用户服务存入
redisTemplate.opsForValue().set("user:1001", new User(1001, "张三", "138****1234"));
// 订单服务读取
User user = (User) redisTemplate.opsForValue().get("user:1001");
// 报错:ClassCastException!
根因:
JdkSerializationRedisSerializer序列化含类路径com.order.model.User vs com.user.entity.User)→ 反序列化失败@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 关键:统一序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// String序列化(Key必须用StringRedisSerializer!)
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
// 补充:String专用Template(高频场景性能提升30%)
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
价值:
redis-cli查看// 错误:查不到直接返回
User user = userCache.get(userId);
if (user == null) {
user = db.query(userId); // 恶意请求打爆DB!
userCache.put(userId, user);
}
防御组合拳:
布隆过滤器(启动时加载白名单)
@PostConstruct
public void initBloomFilter() {
List<Long> allUserIds = userService.getAllUserIds();
for (Long id : allUserIds) {
bloomFilter.put(id);
}
}
// 查询前校验
if (!bloomFilter.mightContain(userId)) return null;
空值缓存(设置短TTL)
if (user == null) {
redisTemplate.opsForValue().set(key, EMPTY_FLAG, 2, TimeUnit.MINUTES);
return null;
}
双重检测锁 + 逻辑过期
public User getUser(Long id) {
String key = "user:" + id;
// 1. 先查缓存(含逻辑过期时间)
String json = stringRedisTemplate.opsForValue().get(key);
if (json != null) {
UserVO vo = JSON.parseObject(json, UserVO.class);
// 未过期直接返回
if (System.currentTimeMillis() < vo.getExpireTime()) {
return vo.getData();
}
// 已过期,但尝试加锁重建
String lockKey = "lock:user:" + id;
if (tryLock(lockKey, 100)) {
// 双重检测:防止其他线程已重建
if (System.currentTimeMillis() >= vo.getExpireTime()) {
rebuildCache(id, key); // 异步重建
}
unlock(lockKey);
}
return vo.getData(); // 仍返回旧数据(保证可用性)
}
// 2. 缓存未命中,查DB并写入
return loadFromDbAndCache(id, key);
}
三重防护:
过期时间随机化
// 基础TTL 30分钟 + 随机偏移(0~300秒)
long expireTime = 1800 + new Random().nextInt(300);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
永不过期 + 后台更新(核心数据)
多级缓存(本地缓存+Redis)
@Cacheable(value = "user", key = "#id", cacheManager = "caffeineRedisCacheManager")
public User getUser(Long id) { ... }
management:
endpoints:
web:
exposure:
include: health,metrics,redis
metrics:
export:
prometheus:
enabled: true
关键监控项:
redis.connections.active:活跃连接数(阈值>80%告警)redis.commands.completed:命令执行速率redis.commands.duration:P99耗时(突增预示问题)@Scheduled(fixedRate = 60000)
public void checkRedisPool() {
GenericObjectPool<StatefulConnection<?, ?>> pool =
(GenericObjectPool) lettuceConnectionFactory.getPool();
PoolStats stats = pool.getStats();
if (stats.getActive() > stats.getMaxTotal() * 0.8) {
log.warn("【Redis连接池告警】活跃连接:{}/{}, 等待线程:{}",
stats.getActive(), stats.getMaxTotal(), stats.getNumWaiters());
// 推送企业微信告警
}
}
@CircuitBreaker(Resilience4j),Redis故障时快速降级你在Redis整合中踩过哪些坑?
评论区分享你的“惊魂时刻”
觉得实用?点赞+收藏+关注,转发给那个总说“Redis很简单”的同事