1. Redis缓存实战与分布式锁解析
Redis作为当下最流行的内存数据库之一,在缓存加速和分布式锁场景中扮演着重要角色。我在多个电商和金融项目中深度应用Redis后,发现很多开发者对Redis的使用仍停留在基础命令层面。本文将分享我在高并发场景下积累的实战经验,特别是缓存设计模式和分布式锁的实现细节。
2. Redis缓存核心设计模式
2.1 缓存穿透防护方案
缓存穿透是指查询不存在的数据,导致请求直接打到数据库。我在实际项目中采用布隆过滤器+空值缓存的组合方案:
# 布隆过滤器伪代码示例 from pybloom_live import ScalableBloomFilter bf = ScalableBloomFilter(initial_capacity=1000, error_rate=0.001) def get_data(key): if not bf.add(key): # 先检查布隆过滤器 return None data = redis.get(key) if data is None: data = db.query(key) if data is None: # 数据库也不存在 redis.setex(key, 300, "NULL") # 设置短时空值缓存 else: redis.setex(key, 3600, data) return data关键经验:空值缓存过期时间建议设置为正常缓存的1/10,避免占用过多内存
2.2 缓存雪崩预防措施
我们曾因缓存集中过期导致数据库瞬时QPS飙升到5万+。现在采用分级缓存策略:
- 一级缓存:本地缓存(Caffeine)2秒
- 二级缓存:Redis集群 30-60分钟随机过期
- 三级缓存:持久化存储
// 多级缓存示例 public Object getData(String key) { Object value = caffeineCache.get(key); if (value != null) return value; value = redisTemplate.opsForValue().get(key); if (value != null) { caffeineCache.put(key, value); return value; } value = database.query(key); redisTemplate.opsForValue().set(key, value, 30 + ThreadLocalRandom.current().nextInt(30), TimeUnit.MINUTES); return value; }3. 分布式锁深度实现
3.1 Redlock算法实践
在金融支付系统中,我们采用Redlock实现跨Redis节点的分布式锁:
# 获取锁 SET resource_name my_random_value NX PX 30000 # 释放锁 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end关键参数说明:
- NX:仅当key不存在时设置
- PX 30000:30秒自动过期
- my_random_value:全局唯一客户端标识
踩坑记录:曾经因未设置唯一value导致误删其他客户端锁,引发资金损失
3.2 锁续期机制
对于长事务场景,我们实现了看门狗线程自动续期:
private void renewLock() { Thread renewalThread = new Thread(() -> { while (lockHeld.get()) { try { Thread.sleep(10000); // 10秒续期一次 redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); } catch (Exception e) { log.error("锁续期失败", e); } } }); renewalThread.setDaemon(true); renewalThread.start(); }4. 性能优化实战技巧
4.1 Pipeline批量操作
在用户画像分析场景,通过pipeline将100次查询合并为1次网络IO:
with redis.pipeline() as pipe: for user_id in user_ids: pipe.hgetall(f"user:{user_id}") results = pipe.execute()实测性能提升:
| 操作方式 | QPS | 网络耗时 |
|---|---|---|
| 单次请求 | 1200 | 85ms |
| Pipeline | 9800 | 9ms |
4.2 内存优化方案
当存储10GB用户会话数据时,我们通过以下配置节省40%内存:
hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -25. 生产环境问题排查
5.1 热点Key发现
使用redis-cli监控命令发现热点:
redis-cli --hotkeys # 或通过监控采集 redis-cli monitor | awk -F '"' '{print $2}' | sort | uniq -c | sort -nr5.2 连接池调优
推荐配置(基于8核服务器):
spring.redis.lettuce.pool.max-active=800 spring.redis.lettuce.pool.max-idle=100 spring.redis.lettuce.pool.min-idle=20 spring.redis.lettuce.shutdown-timeout=100006. 高可用架构设计
我们的金融级方案采用:
- 主从集群:3主3从
- 哨兵部署:5节点
- 持久化策略:AOF everysec + RDB每小时
- 网络拓扑:跨机房部署
故障转移测试结果:
| 故障类型 | 平均恢复时间 | 数据丢失 |
|---|---|---|
| 主节点宕机 | 8.2秒 | 0 |
| 机房断网 | 11.5秒 | ≤1秒数据 |
7. 监控指标体系建设
关键监控项配置示例(Prometheus):
- name: redis_memory_used query: redis_memory_used_bytes{instance="$host:9121"} warning: > increase(redis_memory_used_bytes{instance="$host:9121"}[5m]) > 1000000000 critical: > redis_memory_used_bytes{instance="$host:9121"} / redis_memory_max_bytes{instance="$host:9121"} > 0.98. 安全防护实践
生产环境必须配置:
# redis.conf rename-command FLUSHDB "" rename-command CONFIG "" requirepass complexPassword123! bind 10.0.0.1 protected-mode yes9. 版本升级注意事项
从Redis 5升级到6时的关键步骤:
- 从节点先升级
- 主节点逐个升级
- 验证新特性:
redis-cli --tls --cert ./redis.crt --key ./redis.key - 特别注意线程IO模式变化
10. 典型业务场景实现
10.1 秒杀系统设计
我们的实现方案:
-- inventory.lua local key = KEYS[1] local change = tonumber(ARGV[1]) local current = tonumber(redis.call('GET', key)) if current + change < 0 then return 0 end redis.call('INCRBY', key, change) return 1调用方式:
Long result = redisTemplate.execute( inventoryScript, Collections.singletonList("item:1001"), Collections.singletonList("-1") );10.2 延迟队列实现
基于Sorted Set的方案:
def add_delayed_task(task_id, delay): execute_time = time.time() + delay redis.zadd("delayed_queue", {task_id: execute_time}) def poll_tasks(): while True: now = time.time() tasks = redis.zrangebyscore("delayed_queue", 0, now) if tasks: for task in tasks: process_task(task) redis.zrem("delayed_queue", task) time.sleep(0.1)11. 客户端最佳实践
11.1 连接管理
Jedis连接池正确用法:
try (Jedis jedis = pool.getResource()) { // 操作redis } // 自动归还连接错误示范:
Jedis jedis = pool.getResource(); // 忘记close()导致连接泄漏11.2 序列化优化
推荐配置:
@Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); return template; }12. 扩展方案选型
12.1 Redis模块推荐
常用模块对比:
| 模块名称 | 功能 | 生产验证 |
|---|---|---|
| RedisSearch | 全文搜索 | ★★★★ |
| RedisGraph | 图数据库 | ★★★ |
| RedisTimeSeries | 时序数据 | ★★★★★ |
12.2 集群方案对比
选型参考:
- Codis:适合已有大量客户端连接
- Twemproxy:简单但功能有限
- Redis Cluster:官方方案,推荐新项目使用
13. 性能基准测试
我们的压测结果(16核32G):
| 操作 | QPS | 平均延迟 | 99%延迟 |
|---|---|---|---|
| GET | 125000 | 0.8ms | 2.1ms |
| SET | 98000 | 1.2ms | 3.4ms |
| LPUSH | 87000 | 1.4ms | 4.2ms |
14. 数据迁移方案
我们使用的redis-shake配置:
[source] type = "cluster" servers = ["10.0.1.1:6379","10.0.1.2:6379"] [target] type = "cluster" servers = ["10.0.2.1:6379","10.0.2.2:6379"] [advanced] ncpu = 8 psync = true15. 特殊数据结构应用
15.1 HyperLogLog统计UV
def add_uv(user_id, date): redis.pfadd(f"uv:{date}", user_id) def get_uv(date): return redis.pfcount(f"uv:{date}")误差率测试结果:
| 数据量 | 实际UV | 统计UV | 误差率 |
|---|---|---|---|
| 10万 | 100000 | 99874 | 0.126% |
| 100万 | 1000000 | 1002315 | 0.231% |
16. 持久化策略调优
我们的金融业务配置:
save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec aof-rewrite-incremental-fsync yes17. 内存碎片整理
定期执行:
redis-cli --bigkeys redis-cli memory purge监控指标:
redis-cli info memory | grep fragmentation18. 多租户方案
通过key前缀隔离:
public class TenantAwareRedisTemplate extends RedisTemplate<String, Object> { private ThreadLocal<String> tenantId = new ThreadLocal<>(); @Override protected String preProcessKey(String key) { return tenantId.get() + ":" + key; } }19. 事务与Lua脚本
原子性转账示例:
-- transfer.lua local from = KEYS[1] local to = KEYS[2] local amount = tonumber(ARGV[1]) local fromBalance = tonumber(redis.call('GET', from)) if fromBalance < amount then return 0 end redis.call('DECRBY', from, amount) redis.call('INCRBY', to, amount) return 120. 客户端缓存实践
Redis 6特性使用:
# redis.conf client-tracking onJava客户端配置:
ClientTrackingParams params = ClientTrackingParams .builder() .on() .bcast() .prefixes("user:") .build(); connection.sync().clientTracking(params);