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.mzph.cn/pingmian/14383.shtml

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

相关文章

深度学习之基于MTCNN+Facenet的人脸识别身份认证系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着信息技术的快速发展&#xff0c;身份认证技术在日常生活和工作中的重要性日益凸显。传统的…

民国漫画杂志《时代漫画》第19期.PDF

时代漫画19.PDF: https://url03.ctfile.com/f/1779803-1248634637-c04860?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

使用vue和element_ui搭建后端页面

使用vue和element_ui搭建后台管理页面 效果顶部和左侧内容固定&#xff0c;中间内容滚动 <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…

汽车生产线中的工业机器人应用HT3S-PNS-ECS(EtherCAT/Profinet)协议转换通讯方案案例分析

汽车生产线中的工业机器人应用HT3S-PNS-ECS(EtherCAT/Profinet)协议转换通讯方案案例分析 ——北京中科易联科技有限公司供稿—— 一、摘要 随着工业自动化的快速发展&#xff0c;汽车生产线对工业机器人的依赖日益增加。HT3S-PNS-ECS作为工业机器人中的关键组件&#xff0c;其…

GPIO模拟spi时序点亮数码管

目录 spi.h spi.c main.c 实验效果 spi.h #ifndef __SPI_H__ #define __SPI_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h"//spi初始化 void spi_init(); //spi写入数据 void spi_write(unsigned char data);#endif spi.c #include…

git分支常用命令

最近在用git提交代码的时候&#xff0c;发现有些命令不是很会&#xff0c;先记录几个常用分支命令&#xff0c;后续再补充&#xff0c;在执行git push命令提交代码的时候遇到报错&#xff0c;一并记录下。 1.git常用命令 新建分支&#xff1a; git branch <分支名称> 比…

Python——基于共享单车使用量数据的可视化分析(1)

目录 &#x1f9fe; 1、数据集&#xff08;部分数据&#xff09; ✏️ 2、导入数据集与必要模块 1️⃣ 2.1 导入库以及字体包 2️⃣ 2.2 读取数据集 3️⃣ 2.3 查看数据集基本信息 ⌨️ 3、数据预处理 1️⃣ 3.1删除无关字段 2️⃣ 3.2对各字段进行中文标识 3️⃣ 3.3…

EI会议的社交活动有哪些?

EI会议&#xff08;Engineering Index会议&#xff09;不仅是一个展示最新研究成果的平台&#xff0c;也为与会者提供了丰富的社交活动机会。以下是一些常见的社交活动形式及其内容&#xff1a; 常见社交活动 1. 欢迎酒会&#xff08;Welcome Reception&#xff09; 时间和地…

图像超分辨率重建相关概念、评价指标、数据集、模型

1、图像超分辨率概念 1.1 基本定义 超分辨率&#xff08;Super-Resolution&#xff09;&#xff0c;简称超分&#xff08;SR&#xff09;。是指利用光学及其相关光学知识&#xff0c;根据已知图像信息恢复图像细节和其他数据信息的过程&#xff0c;简单来说就是增大图像的分辨…

光照模型技术在AI去衣中的重要作用

引言&#xff1a; 在数字图像处理和计算机视觉领域&#xff0c;AI去衣技术正逐渐成为研究和应用的热点。这项技术依赖于人工智能算法&#xff0c;尤其是深度学习模型&#xff0c;来识别和处理图像或视频中的衣物。在这个过程中&#xff0c;光照模型技术扮演着至关重要的角色。本…

派可数据助力制造企业数字化生产管理新能力提升

生产管理是现代企业运营的核心之一&#xff0c;它决定了产品的质量、生产效率和企业的竞争力。在一个日益竞争激烈、市场需求多变的商业环境中&#xff0c;如何高效地组织和管理生产过程成为了企业不容忽视的重要课题。 过去&#xff0c;生产管理可能主要侧重于物理工厂的运作…

2024-5-6-从0到1手写配置中心Config之实现配置中心客户端

配置加载原理 在Spring中PropertySource类实现了所有属性的实例化。 启动赋值&#xff1a; 定义自定义属性配置源&#xff0c;从config-server获取全局属性&#xff1b;Spring启动时&#xff0c;插入自定义属性配置源&#xff1b;绑定属性会优先使用&#xff0c;给自定义属性…

宁盾与深信服发布联合方案,解决云桌面及微软AD完整替代

自 Citrix 退出中国市场后&#xff0c;不少中大型企业关心国产云桌面脱离微软 AD 域是否还能正常工作。在2024年3月初&#xff0c;宁盾身份目录与深信服桌面云完成兼容互认证&#xff0c;对于企业的疑问给出了官方回应。 5月10日&#xff0c;在深信服《Citrix离场背景下&#…

【设计模式】JAVA Design Patterns——Balking(止步模式)

&#x1f50d;目的 止步模式用于防止对象在不完整或不合适的状态下执行某些代码。 &#x1f50d;解释 真实世界例子 洗衣机中有一个开始按钮&#xff0c;用于启动衣物洗涤。当洗衣机处于非活动状态时&#xff0c;按钮将按预期工作&#xff0c;但是如果已经在洗涤&#xff0c;则…

科技产业园3D探秘:未来科技之城的奇幻之旅

在数字时代的浪潮中&#xff0c;科技产业园区成为了推动城市经济发展、科技创新的重要引擎。 当我们打开科技产业园的3D可视化模型&#xff0c;仿佛穿越时空&#xff0c;来到了一个充满奇幻色彩的科技世界。在这里&#xff0c;高楼大厦鳞次栉比&#xff0c;绿色植被点缀其间&am…

【文末附gpt升级方案】革新多模态学习:哈工大团队推出“Uni-MoE”统一多模态大模型的跨域MoE研究

革新多模态学习&#xff1a;哈工大团队推出“Uni-MoE”统一多模态大模型的跨域MoE研究 摘要&#xff1a;随着人工智能技术的飞速发展&#xff0c;多模态学习已成为机器学习领域的重要研究方向。然而&#xff0c;传统的多模态学习方法往往存在信息融合困难、模型复杂度高等问题…

深度学习模型keras第二十三讲:在KerasCV中使用SAM进行任何图像分割

1 SAM概念 ###1.1 SAM定义 Segment Anything Model&#xff08;SAM&#xff09;是一种基于深度学习的图像分割模型&#xff0c;其主要特点包括&#xff1a; 高质量的图像分割&#xff1a;SAM可以从输入提示&#xff08;如点、框、文字等&#xff09;生成高质量的对象掩模&am…

我爱我家:租赁下位替代买房,能行吗?

我爱我家&#xff0c;凭什么五天四板&#xff1f; 上周五的楼市组合拳出台后&#xff0c;地产板块迎来高潮。 这其中最火的不是我们常说的“招宝万金”&#xff0c;而是——我爱我家。 五天四板&#xff0c;一个月不到&#xff0c;股价轻松翻翻。 公司有什么变化吗&#xff1…

Flutter 页面布局 Flex Expanded弹性布局

题记 —— 执剑天涯&#xff0c;从你的点滴积累开始&#xff0c;所及之处&#xff0c;必精益求精&#xff0c;即是折腾每一天。 什么是弹性布局&#xff08;Flex&#xff09;&#xff1f; 弹性布局&#xff08;Flex&#xff09;是一种基于弹性盒子模型的布局方式&#xff0c;类…

C语言例题46、根据公式π/4=1-1/3+1/5-1/7+1/9-1/11+…,计算π的近似值,当最后一项的绝对值小于0.000001为止

#include <stdio.h> #include <math.h>int main() {int fm 1;//分母double sign 1;//正负号double fzs 1;//分子式double sum 0;while (fabs(fzs) > 0.000001) {sum fzs;sign * -1; //变换正负号fm 2; //分母3、5、7、9...增长fzs sign / fm;//分子式…