線上redis問題修復:JedisConnectionException: Unexpected end of stream.


經過:  

項目上線后經常報

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)

 

 開啟連接池后:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM