sharded jedis pipelined 执行后 数据并未存入redis

前言

因为历史原因,在某个同步菜单操作的方法中先清除缓存,然后在初始化缓存。本来很正常的逻辑,但是这个清除是db查询获取所有的菜单 然后循环一条条删除 然后在db查询有效的菜单操作 在循环一条条插进去 经统计这个菜单操作大概有个7千个 执行 耗时过久 大概50s -60s 不等

优化

因为一些体验问题 也自然而然 想到优化

第一种 使用并行 插入或者删除

使用到stream的parallelStream 来并行执行 由于redis本身的单线程执行限制 时间来到了 10-15秒左右 体验效果还不是很好

第二种 使用pipeline 来批量执行命令

由于并行执行 提升的效果有限,我们换个思路来解决问题,减少与redis的交互 将命令批量执行 这样就会大大减少执行耗时 时间来到了 1- 2秒这个优化效果还是比较理想的 但是也发现了新的问题

问题

虽然执行效果很快 但是在初始化缓存的时候 发现并没有成功初始化缓存

先看下 示例代码

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class Test{@Autowiredprivate ShardedJedis shardedJedis;@Testpublic void test(){ShardedJedisPipeline pipelined1 = shardedJedis.pipelined();//模拟业务逻辑for (int i = 0; i < 50; i++) {String key = "key:"+i;pipelined1.set(key,String.valueOf(i));pipelined1.expire(key,-1);}pipelined1.sync();}
}

排查定位

这代码看着 好像也没啥问题 批量执行50个key的set 以及expire 操作
最后获取pipeline所有命令的执行结果

期间以为和使用pipelined的set方法 String 入参有关 于是更换为支持byte的方法 未果

后续还以为使用用法不对,经查询多方资料后 发现用法没问题

省略其他的尝试步骤。。。。。

最后将把expire 的设置注释掉 果然可以了

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class Test{@Autowiredprivate ShardedJedis shardedJedis;@Testpublic void test(){ShardedJedisPipeline pipelined1 = shardedJedis.pipelined();//模拟业务逻辑for (int i = 0; i < 50; i++) {String key = "key:"+i;pipelined1.set(key,String.valueOf(i));//pipelined1.expire(key,-1);}pipelined1.sync();}
}

最后问题定位到 是因为 pipelined1.expire(key,-1) 命令执行导致数据无法存入redis

分析

pipelined1.expire(key,-1)  

这个命令看似很正常 想法是设置一个 -1 来表示这个缓存无过期时间 但实际上 好像并没有生效
查看源码后 并无没有什么特殊操作

@Deprecated
default Response<Long> expire(String key, int seconds) {return expire(key, (long) seconds);
}Response<Long> expire(String key, long seconds);

由于未使用 long类型的时间 ,默认调用时间类为 int类型的方法 最后实际上调用的还是 long类型的时间方法
再往下就直接设置命令了

@Override
public Response<Long> expire(final String key, final long seconds) {getClient(key).expire(key, seconds);return getResponse(BuilderFactory.LONG);
}# redis.clients.jedis.BinaryClient#expire(byte[], long)
public void expire(final byte[] key, final long seconds) {sendCommand(EXPIRE, key, toByteArray(seconds));
}

于是找到redis client 执行了命令 发现也很快失效
在这里插入图片描述
于是猜测 -1 这个过期时间会被设置 可能失效时间很短 有可能是 1毫秒 或者1 毫秒
带着问题 去找了下官方文档 看到这样一句描述
在这里插入图片描述
好像只写到了 会将过期时间戳存储为 绝对值 至于传入的时间 为负数 该如何处理并未说明

那就再来看下源码的逻辑
过期命令的实现类在 https://github.com/redis/redis/blob/unstable/src/expire.c

/* EXPIRE key seconds [ NX | XX | GT | LT] */
void expireCommand(client *c) {expireGenericCommand(c,commandTimeSnapshot(),UNIT_SECONDS);
}//核心调用方法
void expireGenericCommand(client *c, long long basetime, int unit) {robj *key = c->argv[1], *param = c->argv[2];long long when; /* unix time in milliseconds when the key will expire. */long long current_expire = -1;int flag = 0;/* checking optional flags */if (parseExtendedExpireArgumentsOrReply(c, &flag) != C_OK) {return;}//解析我们传入的时间参数 并赋值给when 这里我们传入的是-1if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)return;/* EXPIRE allows negative numbers, but we can at least detect an* overflow by either unit conversion or basetime addition. */if (unit == UNIT_SECONDS) {if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) {addReplyErrorExpireTime(c);return;}when *= 1000;}if (when > LLONG_MAX - basetime) {addReplyErrorExpireTime(c);return;}// 时间戳计算 这里相当于是 当前时间戳 -1 when += basetime;/* No key, return zero. */if (lookupKeyWrite(c->db,key) == NULL) {addReply(c,shared.czero);return;}if (flag) {current_expire = getExpire(c->db, key);/* NX option is set, check current expiry */if (flag & EXPIRE_NX) {if (current_expire != -1) {addReply(c,shared.czero);return;}}/* XX option is set, check current expiry */if (flag & EXPIRE_XX) {if (current_expire == -1) {/* reply 0 when the key has no expiry */addReply(c,shared.czero);return;}}/* GT option is set, check current expiry */if (flag & EXPIRE_GT) {/* When current_expire is -1, we consider it as infinite TTL,* so expire command with gt always fail the GT. */if (when <= current_expire || current_expire == -1) {/* reply 0 when the new expiry is not greater than current */addReply(c,shared.czero);return;}}/* LT option is set, check current expiry */if (flag & EXPIRE_LT) {/* When current_expire -1, we consider it as infinite TTL,* but 'when' can still be negative at this point, so if there is* an expiry on the key and it's not less than current, we fail the LT. */if (current_expire != -1 && when >= current_expire) {/* reply 0 when the new expiry is not less than current */addReply(c,shared.czero);return;}}}//检测设置的过期时间 是否已经过期 if (checkAlreadyExpired(when)) {// 过期执行删除逻辑robj *aux;int deleted = dbGenericDelete(c->db,key,server.lazyfree_lazy_expire,DB_FLAG_KEY_EXPIRED);serverAssertWithInfo(c,key,deleted);server.dirty++;/* Replicate/AOF this as an explicit DEL or UNLINK. */aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;rewriteClientCommandVector(c,2,aux,key);signalModifiedKey(c,c->db,key);notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);//删除后 回复了一个 1 和我们之前测试的情况相符addReply(c, shared.cone);return;} else {setExpire(c,c->db,key,when);addReply(c,shared.cone);/* Propagate as PEXPIREAT millisecond-timestamp* Only rewrite the command arg if not already PEXPIREAT */if (c->cmd->proc != pexpireatCommand) {rewriteClientCommandArgument(c,0,shared.pexpireat);}/* Avoid creating a string object when it's the same as argv[2] parameter  */if (basetime != 0 || unit == UNIT_SECONDS) {robj *when_obj = createStringObjectFromLongLong(when);rewriteClientCommandArgument(c,2,when_obj);decrRefCount(when_obj);}signalModifiedKey(c,c->db,key);notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);server.dirty++;return;}
}//只有在非加载数据和非从实例的情况下,当 when 小于等于当前时间戳时,checkAlreadyExpired 函数才会返回 true,表示该过期时间已经过期,可以立即删除该键。
int checkAlreadyExpired(long long when) {/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past* should never be executed as a DEL when load the AOF or in the context* of a slave instance.** Instead we add the already expired key to the database with expire time* (possibly in the past) and wait for an explicit DEL from the master. */return (when <= commandTimeSnapshot() && !server.loading && !server.masterhost);
}

看了代码后 思路也清晰了, 这个设置时间过期的逻辑 我们简单梳理下
这个当执行过期时间命令时,我们会传入 key 以及 过期时间(单位秒 或者 毫秒值) 以及 flag 参数 例如 nx xx 等等
核心的逻辑

  • 判断时间参数
  • 计算过期时间 = 当前时间戳 + 传入的过期时间参数 (单位秒/毫秒)
  • 执行 flag参数 逻辑
  • 执行 checkAlreadyExpired 判断时间是否已经过期 只有在 只有在非加载数据和非从实例的情况下,当 when 小于等于当前时间戳时,checkAlreadyExpired 函数才会返回 true 就会走到删除key的逻辑 并返回
  • 没过期则进行设置新的过期时间 并返回

回到我们的执行操作中,我们执行expire 命令传入的时间参数为-1, 那过期时间就设置为当前时间戳 - 1000 。最后又因为设置的过期时间满足过期条件 (when 小于等于当前时间戳 非加载数据和非从实例),所以我们key 立刻会被删除 。这就导致了虽然我们方法执行完成,但是缓存却没有。

解决

当需要设置一个没有过期时间的key的话 无需要调用expire方法 因为默认没有设置过期时间的话 就是永久不失效
在这里插入图片描述
参考官方文档: 官方文档地址: https://redis.io/docs/latest/commands/expire/


good day !!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/330180.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Application Development using Large Language Models笔记

诸神缄默不语-个人CSDN博文目录 这是2023年NeurIPS Andrew Ng和Isa Fulford做的tutorial&#xff0c;关于如何用LLM来开发新产品的技术和思路&#xff1a;NeurIPS Tutorial Application Development using Large Language Models 文章目录 1. LLM基础2. 提示工程技巧3. 微调4.…

编程基础:掌握运算符与优先级

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、运算符的基石&#xff1a;加减乘除 二、比较运算符&#xff1a;判断数值大小 三、整除…

C++语言学习(五)—— 类与对象(一)

目录 一、类类型的定义 二、类成员的访问控制 2.1 什么是"类内"和"类外" 2.2 对于访问控制属性的说明 三、类类型的使用 3.1 进行抽象 3.2 声明类 3.3 实现类 3.4 使用类 四、构造函数的引入 五、析构函数的引入 六、重载构造函数的引入 6.1 …

Java类和对象(五)—— 抽象类、接口、Object类和内部类

抽象类 在继承体系下&#xff0c;父类有些方法可能是要被重写的&#xff0c;如果我们事先就知道某些方法需要重写的话&#xff0c;我们可以不用在父类里面具体实现这个方法&#xff0c;这时候我们会用到抽象方法&#xff0c;这时候我们会用到关键字abstract关键字来修饰 publ…

【人工智能】模型性能评估

模型性能衡量介绍 混淆矩阵 混淆矩阵(Confusion Matrix&#xff09; TP(真阳性)&#xff1a;预测为阳性&#xff0c;且预测正确。 TN(真阴性)&#xff1a;预测为阴性&#xff0c;且预测正确。 FP(伪阳性)&#xff1a;预测为阳性&#xff0c;但预测错误&#xff0c;又称型一误…

Vue3骨架屏(Skeleton)

效果如下图&#xff1a;在线预览 APIs 参数说明类型默认值必传animated是否展示动画效果booleantruefalsebutton是否使用按钮占位图boolean | SkeletonButtonPropsfalsefalseavatar是否显示头像占位图boolean | SkeletonAvatarPropsfalsefalseinput是否使用输入框占位图boolea…

2024.05.24 学习记录

1、面经复习&#xff1a; js基础、知识深度、js垃圾回收 2、代码随想录刷题&#xff1a;动态规划 完全背包 all 3、rosebush 完成 Tabs、Icon、Transition组件

ENVI光谱识别指导采矿管理者监测铜矿分布

圣地亚哥SRGIS的GIS专家Chile需要利用影像光谱信号勘察Chuquicamata的铜矿分布。 解决方案 Chuquicamata是世界上最大的斑岩铜矿分布区。SRGIS发现西部地区只有有限的矿物和贫瘠的岩石&#xff0c;但东部有铜矿分布。为了进一步测定矿藏的情况&#xff0c;他们开发出一套程序&a…

【算法】双指针

1、移动零 1.1 题目解析 可以发现&#xff0c;这道题的本质就是通过某一个标准&#xff0c;将数组划分成不同区间(数组划分、数组分块)&#xff0c;此时可以用到双指针算法 1.2 算法原理讲解 1.3 代码实现 class Solution { public:void moveZeroes(vector<int>& …

强化学习算法

从上图看出&#xff0c;强化学习可以分成价值/策略、随机策略/确定策略、在线策略/离线策略、蒙特卡洛/时间差分这四个维度。这里分析了基础算法中除了在线策略/离线策略以外的其他维度。 &#xff08;一&#xff09;基础知识 一、基础概念 重点概念&#xff1a;状态S、动作A、…

【全网最全】2024电工杯数学建模A题21页初步参考论文+py代码+保奖思路等(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片链接&#xff0c;那是获取资料的入口&#xff01; 【全网最全】2024电工杯数学建模A题21页初步参考论文py代码保奖思路等&#xff08;后续会更新成品论文&#xff09;「首先来看看目前已有的资料&#x…

每周刷题第三期

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏&#xff1a;Python 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 目录 题目一&#xff1a;环形链表 题目二&#xff1a;删除有序数组中的重复项 题目三&#xff1a;有效的括号 题…

HCIP【VRRP、MSTP、VLAN综合实验】

目录 一、实验拓扑图&#xff1a; ​编辑二、实验要求 三、实验思路 四、实验步骤 &#xff08;1&#xff09; eth-trunk技术配置 &#xff08;2&#xff09;vlan 技术配置 &#xff08;3&#xff09;配置SW1、SW2、AR1、ISP的IP地址 &#xff08;4&#xff09;在交换机…

Java+Spring+ MySQL + MyCat云HIS有哪些优势?智慧医疗云(HIS)低成本与安全保障的完美结合

JavaSpring MySQL MyCat云HIS有哪些优势&#xff1f;智慧医疗云(HIS)低成本与安全保障的完美结合 云HIS的优点包括节省成本、便捷高效、稳妥安全等。通过云HIS&#xff0c;医疗机构无需在本地建立机房、购买服务器和应用软件&#xff0c;降低了硬件和人力成本。同时&#xff0…

图片、视频画质增强变清晰工具分享(免费)

生活中可能会修一下模糊图片那么这就有一款用来修图片的管理工具&#xff0c;也有可能会修一下模糊的视频&#xff0c;在吾爱上有大佬开发了这么一款工具&#xff0c;免费的&#xff0c;不需要开任何VIP&#xff0c;我试了一下&#xff0c;好用&#xff0c;分享出来&#xff0c…

antd-vue a-tree 当两个不同一级下二级key相同的时候就会导致两个同时选择, 拿到node.parent的数据也会出问题, 解决办法

一、问题如下图&#xff1a; 当两个不同一级下二级key相同的时候就会导致两个同时选择&#xff0c; 同时拿到node.parent的数据也会出问题, 出现一下问题的原因是因为数据treeData 的key出现相同的了 然后如下图、因为我的查询条件 第二层是给 cloud , 第二层是给 relatedPool…

树洞陪聊系统源码/陪聊/陪玩/树洞/陪陪/公众号开发/源码交付/树洞系统源码

独立版本源码交付&#xff0c;自研UI和前后端代码 平台自带店员&#xff0c;无需自主招募&#xff0c;搭建直接运营 支持三方登录&#xff0c;官方支付、虎皮椒、易支付/码支付 支持首单体验、盲盒订单、指定下单等多个模式 支持钱包预充值、店员收藏、订单评价等功能 支持…

什么样的数据摆渡设备,可以满足不同网间数据的安全传输需求?

数据摆渡设备是用来在不同的网络环境间安全地传输数据的硬件或软件解决方案。它们通常用于确保在具有不同安全级别的网络&#xff08;如内网和外网&#xff09;之间进行数据交换时的安全性和合规性。以下是一些常见的数据摆渡设备和方法&#xff1a; 移动介质拷贝&#xff1a;使…

Python模块、包和异常处理

大家好&#xff0c;在当今软件开发领域&#xff0c;Python作为一种简洁、易读且功能强大的编程语言&#xff0c;被广泛应用于各种领域。作为一名测试开发工程师&#xff0c;熟练掌握Python的模块、包和异常处理是提高代码可维护性和错误处理能力的关键。本文将和大家一起探讨Py…

第七节 ConfigurationClassParser 源码分析

tips&#xff1a; ConfigurationClassParser 是 Springframework 中的重要类。 本章主要是源码理解&#xff0c;有难度和深度&#xff0c;也枯燥乏味&#xff0c;可以根据实际情况选择阅读。 位置&#xff1a;org.springframework.context.annotation.ConfigurationClassPars…