jedis+redis pipeline诡异的链接损坏、数据读取异常问题解决

文章目录

问题现象

栈溢出(不断的重连)

shell 复制代码
		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加了这个方法

使用的时候

java 复制代码
   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管道,最后执行完释放管道,链接。

java 复制代码
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+版本

相关推荐
IT大白鼠4 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz4 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙4 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除4 天前
redis--018
redis
两锭子4 天前
Redis基础
redis
烟沙九洲4 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索4 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞4 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路5 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog5 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis