文章目錄
- 問題現象
- 棧溢出(不斷的重連)
- 讀取超時
- 未知響應
- 嘗試讀取損壞的鏈接
- 讀取到的數據和自己要讀的無關,導致空指針、類型轉換錯誤,數據讀取錯亂
- 問題寫法
- 問題分析
- 修復
- 注意點
問題現象
棧溢出(不斷的重連)
at redis.clients.jedis.Connection.sendCommand(Connection.java:163)at redis.clients.jedis.Connection.sendCommand(Connection.java:154)at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:815)at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:145)at redis.clients.jedis.Connection.sendCommand(Connection.java:163)at redis.clients.jedis.Connection.sendCommand(Connection.java:154)at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:815)at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:145)
讀取超時
未知響應
嘗試讀取損壞的鏈接
讀取到的數據和自己要讀的無關,導致空指針、類型轉換錯誤,數據讀取錯亂
問題寫法
對Redis操作封裝了一個Redis類
同事對pipeline加了這個方法
使用的時候
public Map<String, AnswerModel> batchGetAnswersPipeline(List<String> answerIdList) {Map<String, AnswerModel> resultMap = new HashMap<>();try (Pipeline pipelined = redis.pipelined()) {List<Response<Map<String, String>>> responses = new ArrayList<>();for (String answerId : answerIdList) {String key = String.format(MOMENT_ANSWER_INFO, answerId);responses.add(pipelined.hgetAll(key));}pipelined.sync();for (Response<Map<String, String>> response : responses) {Map<String, String> map = response.get();AnswerModel answers = BeanUtil.mapToBean(map, AnswerModel.class);if (answers != null){resultMap.put(answers.getAnswerId(), answers);}}} catch (Exception e) {log.error("執行batchGetAnswersPipeline發生異常, redis pipeline error", e);throw new CatVillageException();}return resultMap;}
問題分析
在redis這個工具類中調pipelined時獲取一個Pinelined對象,而獲取這個方法里用了try with ,with了一個jedis鏈接,當try結束時鏈接會被歸還jedispool連接池。而返回的這個pipeline仍然在用這個鏈接,當其他線程去拿鏈接的時候可能拿到的正好是這個鏈接,導致多個線程共用一個鏈接,一個線程在執行pipeline多條命令,另一個線程也在用這個鏈接。而jedis底層執行命令的時候是使用的OutputStream流式去執行命令,使用二進制流讀取結果。當出現這種線程不安全問題的時候,讀取寫入就會有問題。
修復
刪除Redis工具類中的pipelined方法,使用getJedis方法獲取鏈接對象,再獲取管道Pineline對象,自行close管道,最后執行完釋放管道,鏈接。
public Map<String, QuestionModel> batchGetQuestionPipeline(List<String> questionIdList) {if (CollectionUtils.isEmpty(questionIdList)){return new HashMap<>();}Map<String, QuestionModel> resultMap = new HashMap<>();try (Jedis jedis = redis.getJedis()) {Pipeline pipelined = jedis.pipelined();List<Response<Map<String, String>>> responses = new ArrayList<>();for (String questionId : questionIdList) {String key = String.format(MOMENT_QUESTION_INFO, questionId);responses.add(pipelined.hgetAll(key));}pipelined.sync();for (Response<Map<String, String>> response : responses) {Map<String, String> map = response.get();QuestionModel question = convertFromMap(map);if (question != null) {resultMap.put(question.getPostId(), question);}}pipelined.close();} catch (Exception e) {log.error("執行batchGetQuestionPipeline發生異常, redis pipeline error", e);throw new CatVillageException();}return resultMap;}
注意點
若jedis在3.7版本以下的版本也會有管道二進制讀取異常問題,請升級到3.7.0+版本