Redis+MySQL双写踩坑记:我是如何优化千万级粉丝列表查询性能的 RedisMySQL双写踩坑记千万级粉丝列表查询性能优化实战记得去年接手社交平台核心模块重构时我遇到了职业生涯中最棘手的性能瓶颈——某顶流明星发布新动态后粉丝列表查询接口响应时间从200ms飙升到12秒。这个看似简单的谁关注了我功能在千万级数据量下暴露出了缓存与数据库协同设计的深层问题。1. 问题定位从慢查询到架构瓶颈那是一个周五晚上运维突然在群里我用户ID 8848的粉丝列表接口超时率突破90%打开监控面板看到的是触目惊心的曲线——这个拥有3700万粉丝的账号其分页查询在第5页之后响应时间呈指数级增长。1.1 原始架构的致命缺陷当时的系统设计非常教科书-- MySQL分页查询 SELECT follower_id FROM user_relations WHERE followee_id ? ORDER BY create_time DESC LIMIT 10 OFFSET 1000000配合最简单的Redis缓存策略def get_followers(user_id, page): cache_key ffollowers:{user_id} if not redis.exists(cache_key): db_results query_db(user_id) redis.zadd(cache_key, db_results) return redis.zrevrange(cache_key, page*10, (page1)*10-1)这套方案存在三个致命问题热点数据雪崩当大V粉丝突破千万级ZSET结构的内存占用超过6GB分页查询陷阱OFFSET 100万相当于MySQL要先扫描100万行双写一致性难题缓存更新延迟导致粉丝数显示不一致1.2 性能瓶颈的数学原理通过EXPLAIN分析发现当OFFSET超过10万时查询成本符合公式成本 (索引扫描成本) (数据行扫描成本) × OFFSET在我们的InnoDB配置下千万级分页查询的IOPS消耗达到惊人的(10ms索引扫描) (0.1ms/行 × 1,000,000) 100秒2. 分层缓存体系设计经过两周的压测和方案验证我们最终实现了响应时间稳定在50ms内的新架构。2.1 三级缓存解决方案缓存层级存储内容过期时间命中率本地缓存前5页粉丝ID1分钟92%Redis集群活跃粉丝区间数据1小时85%MySQL分区全量数据--关键实现代码def get_followers_v2(user_id, page): # 第一级本地缓存 if page 5 and local_cache.exists(user_id): return local_cache.get(user_id)[page] # 第二级Redis区间缓存 range_key frange:{user_id}:{page//100} if redis.exists(range_key): return redis.lrange(range_key, page%100*10, (page%1001)*10-1) # 第三级MySQL游标查询 return query_by_cursor(user_id, page) def query_by_cursor(user_id, page): last_id redis.get(fcursor:{user_id}:{page}) sql SELECT id, follower_id FROM user_relations WHERE followee_id ? AND id ? ORDER BY id DESC LIMIT 10 return execute_sql(sql, [user_id, last_id or MAX_INT])2.2 冷热数据分离策略对于粉丝数超过100万的热点用户我们采用动态分片策略最近3个月活跃粉丝存入Redis Sorted Set历史粉丝按季度归档到单独MySQL分区表使用BloomFilter快速判断粉丝是否存在// 热点用户判断逻辑 if (userService.isHotUser(userId)) { // 从热数据集群查询 return hotDataClient.queryFollowers(userId, page); } else { // 从常规集群查询 return normalDataClient.queryFollowers(userId, page); }3. 双写一致性的终极方案缓存与数据库的一致性问题我们最终采用异步校验最终一致的混合模式写操作流程graph TD A[用户关注动作] -- B[写MySQL binlog] B -- C[发MQ消息] C -- D[更新Redis计数器] D -- E[异步更新粉丝列表]一致性保障措施每小时全量校验Top 1000热点用户数据使用版本号解决ABA问题关键业务线启用强一致性读模式重要提示在粉丝数暴增场景下建议临时开启只读缓存模式牺牲部分一致性保证可用性4. 性能优化效果对比优化前后的关键指标对比指标优化前优化后提升幅度平均响应时间1200ms48ms25倍99分位耗时15s200ms75倍MySQL QPS3500120减少97%Redis内存占用68GB9GB节省87%特别在极端场景下如顶流明星官宣恋情系统表现峰值QPS从原来的1200提升到9500错误率从8.7%降至0.02%服务器成本减少40%5. 实战中的经验结晶在这次优化过程中有几个反直觉的发现值得分享ZSET不是万金油当成员超过500万时ZRANGE时间复杂度从O(log(N))退化为O(N)分页缓存的最佳区间经过测试每100页作为一个缓存区间最经济内存与命中率平衡点冷数据归档的魔法数字粉丝最后一次互动时间超过180天后读取概率下降至0.3%我们团队最后将这套方案抽象为通用组件在用户关系、商品收藏、消息列表等场景都得到了验证。特别是在一次电商大促中某爆款商品的收藏列表查询量达到每秒3万次依然保持了45ms的稳定响应。