經過:
項目上線后經常報
Unexpected end of stream.; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
白天平均半個小時報一次,晚上頻率低些,但是在測試環境和預發環境就沒有出現過這種問題。
當時我們項目是從公司的另一個項目拆分出來的,所有配置和另一個環境基本一樣
分析:
JedisConnectionException: Unexpected end of stream這個異常是由於redis服務器端設置了5分鍾關閉空閑連接,但是連接池還認為該鏈接有效,繼續使用導致的結果。
使用spring boot、spring data redis這些庫,默認會開啟空閑連接檢測,每30秒執行一次,如果PING命令沒有返回PONG,或者60秒內該連接還是空閑,就會被清理釋放,一次釋放一個(默認)所以正常情況下是不會出現這個異常的。
有一種可能是,一下新建了很多連接,一分鍾只釋放一個,5分鍾后空閑連接仍然沒有釋放完,這時就會報異常,因此需要調整每次釋放資源個數(參數:numTestsPerEvictionRun)
由於spring boot通過application.properties方式,只能設置max-active、max-wait、max-idle、min-idle這四個參數哈。如果要設置numTestsPerEvictionRun,需要通過代碼。
解決:
1、新增配置
spring.redis.numTestsPerEvictionRun = 5 spring.redis.maxActive = 50 spring.redis.maxIdle = 50 spring.redis.minIdle = 0
2、新增redis配置類
@Getter @Setter @Component @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private String host; private int port; private String password; private String maxActive; private String maxIdle; private String minIdle; private String timeout; private String numTestsPerEvictionRun; }
3、新增RedisConnectionFactory
@Configuration @RequiredArgsConstructor @Slf4j public class RedisConfig { @NonNull private RedisProperties redisProperties; /** * 連接池配置信息 * @return */ @Bean public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig poolConfig=new JedisPoolConfig(); //最大連接數 poolConfig.setMaxIdle(Integer.parseInt(redisProperties.getMaxIdle())); //最小空閑連接數 poolConfig.setMinIdle(Integer.parseInt(redisProperties.getMinIdle())); /*poolConfig.setTestOnBorrow(true); poolConfig.setTestOnReturn(true); poolConfig.setTestWhileIdle(true);*/ poolConfig.setNumTestsPerEvictionRun(Integer.parseInt(redisProperties.getNumTestsPerEvictionRun())); //poolConfig.setTimeBetweenEvictionRunsMillis(60000); //當池內沒有可用的連接時,最大等待時間 //poolConfig.setMaxWaitMillis(10000); //------其他屬性根據需要自行添加------------- return poolConfig; } /** * jedis連接工廠 * @param jedisPoolConfig * @return */ @Bean public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) { //單機版jedis RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); //設置redis服務器的host或者ip地址 redisStandaloneConfiguration.setHostName(redisProperties.getHost()); //設置密碼 redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword())); //設置redis的服務的端口號 redisStandaloneConfiguration.setPort(redisProperties.getPort()); //獲得默認的連接池構造器(怎么設計的,為什么不抽象出單獨類,供用戶使用呢) JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder().usePooling(); //指定jedisPoolConifig來修改默認的連接池構造器(真麻煩,濫用設計模式!) jpcb.poolConfig(jedisPoolConfig); //通過構造器來構造jedis客戶端配置 JedisClientConfiguration jedisClientConfiguration = jpcb.build(); //單機配置 + 客戶端配置 = jedis連接工廠 JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); return jedisConnectionFactory; } }
后記:
其實中間出現了一個小插曲,就是第一個改完,沒有加紅色部分代碼(usePooling()),雖然報錯頻率有了很大改善,但是仍然會有報錯出現,最后發現是沒有開啟redis連接池導致,后面加了開啟連接池(usePooling())后,經過一天的觀察,沒有再出現過該錯誤現象。
記錄一下沒有開啟連接池和開啟連接池時stringRedisTemplate的參數(忽略截圖中maxTotal等參數,上面說的50是線上的配置,截圖是本地的測試結果,本地配置的8)
開啟連接池后: