Redisson集群连接池耗尽:从“Unable to write command”报错到读写模式调优的实战解析
1. 当Redisson突然罢工连接池耗尽背后的真相那天凌晨三点我被一阵急促的报警声惊醒。监控系统显示生产环境的分布式锁服务大面积超时日志里铺天盖地都是Unable to write command into connection!的报错。第一反应是连接池不够用了但当我看到后面跟着的SlaveConnectionPool no available Redis entries时突然意识到事情没那么简单。这个报错发生在使用Redisson 3.20.0的Redis集群环境三主三从架构中。表面看是连接池耗尽实际上却是Redis集群中某个从节点172.21.75.153:6379与它的主节点172.21.75.157:6379网络失联导致的连锁反应。有趣的是这个生病的从节点仍然能和其他集群节点正常通信所以没被集群自动剔除就像个混在健康人群中的无症状感染者。关键点在于Redisson的默认配置主节点写从节点读ReadMode.SLAVE。当执行EXISTS命令检查锁状态时客户端会优先选择从节点读取。而那个半瘫痪的从节点虽然能响应PING命令但实际处理命令时就会超时导致连接被卡住。随着这类僵尸连接不断堆积最终拖垮了整个连接池。2. 抽丝剥茧从错误日志到根因定位2.1 错误日志的三重密码第一段关键日志暴露了直接原因Unable to write command into connection! Node source: NodeSource [slot5073, addrnull], connection: RedisConnection1161754400 [addrredis://172.21.75.153:6379] command: (EXISTS), params: [redisLock_serviceLock]这告诉我们客户端在向172.21.75.153:6379发送EXISTS命令时失败该节点负责哈希槽5073。更致命的线索在后续日志SlaveConnectionPool no available Redis entries. Disconnected hosts: [172.21.75.157:6379] Hosts disconnected due to errors: [172.21.75.153:6379]这说明153节点作为157的从节点已经与主节点失联。但为什么集群没把它踢出去查看该节点的Redis日志发现了真相3759119:S 06 May 13:43:42.325 # Connection with master lost. 3759119:S 06 May 13:43:42.325 * Connecting to MASTER 172.21.75.157:6379 3759119:S 06 May 13:43:42.325 # Unable to connect to MASTER: Network is unreachable从节点不断重连主节点失败但与其他节点的通信却保持正常导致集群误判其健康状态。2.2 连接池耗尽的蝴蝶效应在主写从读模式下Redisson的工作流程是这样的写操作如加锁直接走主节点读操作如检查锁状态优先选择从节点当从节点响应超时客户端会重试默认3次每次重试都会占用连接池资源在我们的场景中客户端向问题从节点发起EXISTS命令节点实际已无法处理请求但未及时响应失败连接在等待超时期间被占用默认3000ms高并发下很快耗尽连接池这就像高峰期地铁闸机故障乘客请求卡在闸机口问题节点后面的人新请求无法进站获取连接但故障闸机仍显示绿灯节点未被剔除。3. 治标还是治本连接池调优的误区3.1 为什么增加pool size不是好方案很多人的第一反应是调大连接池参数.useClusterServers() .setMasterConnectionPoolSize(64) .setSlaveConnectionPoolSize(64)但这存在三个问题资源浪费正常情况不需要这么多连接延迟爆炸更多连接卡在问题节点会加剧超时掩盖问题节点网络问题始终存在实测中当把pool size从默认的24提升到64后错误从立即报出变为间歇性出现平均响应时间从200ms飙升到1500ms服务器负载上升30%3.2 Redis集群的读写真相很多人没注意到的是Redis集群本身就不建议从节点读。用redis-cli连接集群试试redis-cli -c -h 172.21.75.153 -p 6379 GET foo # 会自动重定向到主节点 - Redirected to slot [12182] located at 172.21.75.158:6379Redis集群的设计原则是所有写必须走主节点读建议走主节点从节点只用于故障转移从节点数据同步有毫秒级延迟4. 终极解决方案读写模式调优实战4.1 配置调整方案将ReadMode改为MASTER是最佳实践Config config new Config(); config.useClusterServers() .addNodeAddress(redis://172.21.75.153:6379, ...其他节点) .setReadMode(ReadMode.MASTER); // 关键配置 RedissonClient client Redisson.create(config);三种ReadMode的对比模式读请求路由优点缺点SLAVE优先从节点理论上减轻主节点负载从节点延迟可能导致脏读MASTER只走主节点强一致性避免节点故障影响主节点压力稍大MASTER_SLAVE主从轮询负载均衡一致性最差4.2 为什么MASTER模式更可靠在我们的生产环境切换后观察到连接池使用率下降40%99线延迟从1200ms降至300ms再未出现连接池耗尽报错原理在于所有请求走主节点避免问题从节点干扰主节点由集群自动维护故障转移更及时Redis本身针对主节点访问有优化4.3 进阶配置建议对于高并发场景还需要注意.setMasterConnectionMinimumIdleSize(8) // 保持最小空闲连接 .setMasterConnectionPoolSize(32) // 根据QPS调整 .setIdleConnectionTimeout(10000) // 适当调大超时 .setConnectTimeout(1000) // 连接建立超时 .setRetryAttempts(3) // 适当减少重试 .setFailedSlaveCheckInterval(30000) // 从节点检测间隔5. 防患于未然监控与应急预案5.1 关键监控指标建议监控这些Redisson指标redisson.connection.active活跃连接数redisson.connection.idle空闲连接数redisson.connection.pool.size连接池大小redisson.command.failure命令失败次数搭配Redis集群的redis_cluster_state集群状态0正常redis_repl_link_status主从连接状态5.2 应急预案清单当再次出现类似问题时立即检查集群状态redis-cli --cluster check临时隔离问题节点redis-cli --cluster forget动态调整ReadMode需客户端支持热更新降级为单节点模式极端情况那次事故后我们在所有Redisson客户端配置中强制设置了ReadMode.MASTER并在监控大屏上增加了从节点健康状态看板。三个月过去类似的连接池问题再未出现。有时候看似复杂的系统故障解决方案往往就藏在对基础原理的深刻理解中。