使用Jedis执行MGET命令时遇SocketException: Broken pipe异常求助
问题分析与解决方案
核心问题根源
你遇到的SocketException: Broken pipe和Socket is closed异常,本质是Jedis连接复用失效:
- 手动通过
isConnected()判断连接状态不可靠,Redis服务器可能已主动关闭空闲连接(比如配置了timeout参数),但客户端还认为连接可用。 - 代码中重复获取连接的逻辑可能导致连接池资源泄漏,或者拿到的是已经失效的连接。
解决方案1:重构连接使用逻辑,避免手动管理连接状态
不要自己判断连接是否可用,直接从连接池获取新连接(连接池会自动管理连接有效性),并使用try-with-resources确保连接正确归还:
private Boolean isTokenExpired(long programId, String token, Jwt jwt) { int dbIndex = generalConfigHelper.getInt(REDIS_DB_INDEX); // 直接从连接池获取连接,try-with-resources自动关闭归还 try (Jedis jedis = redisHelper.getConnection()) { jedis.select(dbIndex); Claims claims = (Claims) jwt.getBody(); String tokenCacheKey = programId + ":Tokens:" + token; String expiryTimeKey = programId + ":InvalidateTokensBefore:" + claims.get("user_id"); List<String> values = jedis.mget(tokenCacheKey, expiryTimeKey); if (values.get(0) != null) { return true; } else if (values.get(1) != null) { log.info("redis: exp value:" + values.get(1)); long exp = Long.parseLong(claims.get("exp_ms").toString()); return exp < Long.parseLong(values.get(1)); } } catch (Exception ex) { log.error("Error while checking token expiration", ex); // 可选:添加重试逻辑,比如重试1-2次 // return retryIsTokenExpired(programId, token, jwt); } return false; }
关键改进点:
- 移除手动判断连接状态的逻辑,交给连接池管理连接生命周期。
- 使用
try-with-resources(Jedis实现了Closeable接口),确保无论是否异常,连接都会正确归还到池里,避免泄漏。
解决方案2:优化Jedis连接池配置,添加连接有效性检测
在你的连接池配置中添加以下参数,让连接池自动剔除失效连接:
// 从连接池获取连接时,先验证连接有效性 config.setTestOnBorrow(true); // 连接空闲时,定期验证有效性 config.setTestWhileIdle(true); // 空闲连接检测的时间间隔 config.setTimeBetweenEvictionRunsMillis(30000); // 30秒 // 每次空闲检测的连接数 config.setNumTestsPerEvictionRun(10); // 连接最小空闲时间,超过则被剔除 config.setMinEvictableIdleTimeMillis(60000); // 1分钟
参数说明:
testOnBorrow:获取连接时执行PING命令验证,确保拿到的是可用连接。testWhileIdle:后台线程定期检测空闲连接,剔除失效的连接。- 配合
timeBetweenEvictionRunsMillis和minEvictableIdleTimeMillis,可以及时清理Redis服务器主动关闭的空闲连接。
解决方案3:处理连接失效的重试逻辑
如果偶尔还是会遇到连接失效,可以添加简单的重试机制,避免单次异常导致业务失败:
private Boolean isTokenExpired(long programId, String token, Jwt jwt) { return retryIsTokenExpired(programId, token, jwt, 2); } private Boolean retryIsTokenExpired(long programId, String token, Jwt jwt, int retryCount) { int dbIndex = generalConfigHelper.getInt(REDIS_DB_INDEX); try (Jedis jedis = redisHelper.getConnection()) { jedis.select(dbIndex); Claims claims = (Claims) jwt.getBody(); String tokenCacheKey = programId + ":Tokens:" + token; String expiryTimeKey = programId + ":InvalidateTokensBefore:" + claims.get("user_id"); List<String> values = jedis.mget(tokenCacheKey, expiryTimeKey); if (values.get(0) != null) { return true; } else if (values.get(1) != null) { log.info("redis: exp value:" + values.get(1)); long exp = Long.parseLong(claims.get("exp_ms").toString()); return exp < Long.parseLong(values.get(1)); } } catch (SocketException | JedisConnectionException ex) { log.warn("Redis connection failed, retrying...", ex); if (retryCount > 0) { return retryIsTokenExpired(programId, token, jwt, retryCount - 1); } log.error("Redis connection failed after retries", ex); } catch (Exception ex) { log.error("Error while checking token expiration", ex); } return false; }
额外注意事项
- 确保Redis服务器的
timeout配置(如果有)不要短于连接池的minEvictableIdleTimeMillis,避免服务器提前关闭连接。 - 不要在多个线程间复用同一个Jedis实例,Jedis不是线程安全的,必须每个线程从池里单独获取。
内容的提问来源于stack exchange,提问作者Jayanth Naidu
相关产品推荐
相关产品推荐

