7月中间件优化回顾Redis与MySQL在生产环境的关键调优经验中间件调优的难点不在参数本身而在什么时候改、改到什么程度、改完怎么验证。一、开篇中间件问题的滞后性7月处理了两次中间件相关故障一次是Redis Cluster的脑裂导致数据不一致另一次是MySQL慢查询堆积导致主从延迟超过30秒。这两个问题的共同特征是故障发生时一切指标都正常等发现异常时已经是灾难级别。本文提炼7月在Redis和MySQL优化上反复验证有效的实践经验——不做面面俱到的参数罗列只讲生产环境中真正起作用的调优决策。二、Redis调优的核心经验2.1 Redis问题排查全景2.2 Redis生产配置模板# redis.conf 生产环境核心配置 # 适用Redis 7.x、64GB内存、高可用集群 # 网络 bind 0.0.0.0 # 绑定内网IP生产环境务必指定具体IP port 6379 tcp-backlog 511 # TCP连接队列 timeout 300 # 空闲连接超时 300s tcp-keepalive 60 # TCP keepalive 60s # 内存管理 maxmemory 48gb # 物理内存的 75%留余量给系统和fork maxmemory-policy allkeys-lru # 推荐淘汰最近最少使用的Key # 注意如果用作缓存选 allkeys-lru如果存储重要数据选 noeviction 监控告警 # 持久化 # RDB适合灾备不适合实时恢复 save 900 1 # 900秒内至少1次修改则触发RDB save 300 10 save 60 10000 stop-writes-on-bgsave-error yes # bgsave失败时拒绝写入 rdbcompression yes # 压缩RDBCPU换磁盘空间 rdbchecksum yes # AOF适合实时持久化 appendonly yes appendfsync everysec # 每秒fsync性能与安全的平衡点 auto-aof-rewrite-percentage 100 # AOF增长100%时触发rewrite auto-aof-rewrite-min-size 64mb # 最小64MB才触发 # 慢查询日志 slowlog-log-slower-than 10000 # 超过10ms10000微秒记录 slowlog-max-len 128 # 保留最近128条 # 客户端 maxclients 10000 # 最大客户端连接数 # 集群 cluster-enabled yes cluster-node-timeout 5000 # 节点超时5秒不要设太短 cluster-require-full-coverage no # 部分slot不可用时仍可服务2.3 连接池配置的关键参数// Lettuce 连接池配置Spring Data Redis 默认客户端 Configuration public class RedisPoolConfiguration { Bean public LettuceClientConfigurationBuilderCustomizer customizer() { return builder - builder .poolConfig(new GenericObjectPoolConfig() {{ // 最大连接数 最大并发请求数 / 每个连接可处理的并发数 // 假设最大并发1000每个连接处理50个请求Pipeline setMaxTotal(20); // 1000 / 50 20 setMaxIdle(10); // 空闲连接数 maxTotal / 2 setMinIdle(5); // 最小空闲数保证预热 // 连接获取等待 setMaxWait(Duration.ofMillis(200)); // 最多等200ms setBlockWhenExhausted(true); // 连接耗尽时阻塞等待 // 连接健康检查 setTestOnBorrow(true); // 借出时检查 setTestOnReturn(false); // 归还时不检查性能考虑 setTestWhileIdle(true); // 空闲时检查 setTimeBetweenEvictionRuns(Duration.ofSeconds(30)); // 30s检查一次 // 连接超时配置 setMinEvictableIdleDuration(Duration.ofMinutes(5)); // 5分钟空闲回收 }}); } }连接池常见配置错误maxTotal设太大如1000Redis是单线程连接多并不增加吞吐反而增加上下文切换maxWait设太长或为0无限等待请求堆积时会导致线程池耗尽没有开启testOnBorrow故障节点上的连接不会被剔除持续报错2.4 集群模式选择决策模式容量上限可用性一致性运维复杂度推荐场景单机~64GB无强低开发/测试环境主从~64GB中等最终一致低读多写少场景Sentinel~64GB高最终一致中对高可用有要求ClusterTB级高最终一致高大数据量 高并发Codis/ProxyTB级高最终一致很高需要平滑扩容7月案例一个日活500万的应用用了3主3从的Cluster单节点内存16GB。核心经验是——不要因为以后可能扩容就提前上ClusterSentinel在64GB以下场景的管理成本远低于Cluster。三、MySQL调优的核心经验3.1 慢查询治理从发现到根治-- 慢查询日志配置my.cnf slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 0.1 -- 100ms以上记录云环境默认1s太宽松 log_queries_not_using_indexes 1 -- 记录未使用索引的查询 log_slow_admin_statements 1 -- 记录DDL慢操作 -- 慢查询分析使用 pt-query-digest -- pt-query-digest /var/log/mysql/slow.log slow_report.txt -- 重点关注 -- 1. Response time 占比最高的前5条SQL -- 2. Rows examine / Rows sent 比值过大的SQL扫描了大量数据但返回很少 -- 3. 执行频率最高的慢SQL即使单次不慢累积影响大慢查询治理标准流程发现慢查询 → EXPLAIN分析执行计划 → 确定优化方案 优化方案选择优先级 1. 加索引成本最低、效果最明显 ├─ 覆盖索引 联合索引 单列索引 └─ 注意索引不是越多越好写入性能下降 存储空间 2. 改写SQL ├─ SELECT * → SELECT 具体字段 ├─ OR条件 → UNION ALL ├─ 子查询 → JOIN ├─ LIKE %xxx% → 全文索引Elasticsearch分流 └─ 大分页 OFFSET → 游标分页WHERE id last_id 3. 表结构调整 ├─ 垂直拆分大字段分离 ├─ 水平分表按时间/ID范围 └─ 归档历史数据 4. 参数调优最后手段 ├─ join_buffer_size ├─ sort_buffer_size └─ tmp_table_size3.2 索引设计原则-- 索引设计检查清单 -- 原则1高选择性字段优先 SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders; -- 0.0001 不适合建索引 SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM orders; -- 0.85 适合建索引 -- 原则2联合索引遵循最左前缀 -- 查询条件WHERE user_id ? AND status ? AND created_at ? -- 正确索引 CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at); -- 错误索引 CREATE INDEX idx_created_status_user ON orders(created_at, status, user_id); -- 原因范围查询(created_at ?)会中断索引使用放在最后 -- 原则3覆盖索引消除回表 -- 查询SELECT id, user_id, amount FROM orders WHERE user_id ? AND status ? -- 好索引覆盖 CREATE INDEX idx_cover ON orders(user_id, status, amount); -- 差索引需回表 CREATE INDEX idx_partial ON orders(user_id, status); -- 原则4避免冗余索引 -- 冗余示例(a) 和 (a, b) — (a) 是冗余的所有用到(a)的查询都能用(a, b) -- 保留 (a, b)删除 (a)3.3 连接池配置# HikariCP 连接池配置Spring Boot 默认 spring: datasource: hikari: # 连接数计算 # connections ((core_count * 2) effective_spindle_count) # 但MySQL建议不超过 20-30避免MySQL线程切换开销 maximum-pool-size: 20 minimum-idle: 10 # 连接超时30秒默认值在云原生环境下偏长 connection-timeout: 5000 # 5秒 # 空闲超时10分钟 idle-timeout: 600000 # 连接最大生命周期30分钟应小于MySQL wait_timeout max-lifetime: 1800000 # 连接测试 connection-test-query: SELECT 1 # 泄漏检测 leak-detection-threshold: 10000 # 连接持有超过10s记录日志3.4 数据迁移避坑清单7月执行了一次8亿行的大表迁移分表重构踩过的坑坑1直接使用 ALTER TABLEMDL锁阻塞所有读写 ✅ 正确使用 pt-online-schema-change不会长时间持锁 坑2迁移过程中没有暂停定时任务 ✅ 正确迁移前暂停所有写入定时任务迁移后恢复 坑3新表没有预热 ✅ 正确迁移完成后手动执行 COUNT(*) 或全表扫描 让数据进入 buffer pool 坑4只迁移数据不迁移索引统计信息 ✅ 正确迁移后执行 ANALYZE TABLE 更新统计信息 坑5一次性切流量回滚困难 ✅ 正确灰度切流量1%→10%→50%→100%每步观察5分钟-- pt-online-schema-change 安全迁移命令 pt-online-schema-change \ --alter PARTITION BY RANGE (TO_DAYS(created_at)) ( PARTITION p20240701 VALUES LESS THAN (TO_DAYS(2026-07-01)), PARTITION p20240801 VALUES LESS THAN (TO_DAYS(2026-08-01)), PARTITION p_future VALUES LESS THAN MAXVALUE ) \ --execute \ --max-load Threads_running50 \ --critical-load Threads_running100 \ --chunk-size 1000 \ --chunk-time 0.5 \ --progress percentage,5 \ hdb-master,P3306,Dmyapp,torders四、Redis与MySQL的协同优化Redis和MySQL在生产环境中不是孤立的它们的配置会相互影响// 缓存策略选型的量化决策 public class CacheStrategySelector { public CacheStrategy select(CacheScenario scenario) { // 1. 数据一致性要求 if (scenario.consistencyLevel() ConsistencyLevel.STRONG) { // 强一致性不使用缓存直接读MySQL return CacheStrategy.NO_CACHE; } // 2. 数据变更频率 double changeRate scenario.writesPerMinute() / (double) scenario.totalRecords(); if (changeRate 0.1) { // 每分钟超过10%的数据会变更 // 变更太频繁缓存价值低 return CacheStrategy.NO_CACHE; } // 3. 查询热点 double hotKeyRate scenario.topPercentQueries() / (double) scenario.totalQueries(); if (hotKeyRate 0.5) { // 50%查询集中在少量数据 // 热点明显Cache Aside 效果好 return CacheStrategy.CACHE_ASIDE; } // 4. 读写比例 if (scenario.readWriteRatio() 10) { // 读多写少适合缓存 return CacheStrategy.CACHE_ASIDE; } return CacheStrategy.WRITE_THROUGH; } }五、总结7月中间件优化的核心教训慢查询是万恶之源——Redis的CPU飙高、MySQL的主从延迟根因80%是慢查询。把慢查询治理做到极致大部分性能问题自动消失。配置参数要知其所以然——maxmemory-policy选allkeys-lru还是volatile-lru取决于你的业务能否接受非过期Key被淘汰。不理解就抄配置早晚要还技术债。迁移永远留后路——数据迁移方案设计时回滚方案比迁移方案更重要。7月的大表迁移灰度流量切换救了至少两次。8月计划在MySQL的自动索引推荐基于慢查询日志的聚集分析和Redis的Key治理大Key/热Key自动发现迁移两个方向上做工具化尝试。