Redis缓存落地总结
最近在优化电子签系统,涉及到缓存相关的也一并优化了,写个文档做个总结,防止以后开发时又考虑不全
1、避免大key
避免缓存大PDF文件:
💡 经验值:单个Redis Value不超过10KB,集合元素不超过5000个
// 反例:直接缓存整个PDF
@Cacheable(value = "contract_pdf", key = "#contractId")
public byte[] getContractPdf(String contractId) { /* 返回5MB文件 */ }// 正例:拆分为元数据+文件存储
@Cacheable(value = "contract_meta", key = "#contractId")
public ContractMeta getContractMeta(String contractId) { /* 返回1KB元数据 */ }public byte[] getContractPdf(String fileResId) {// 从对象存储按需获取return fileStoreUtil.getFileByResId(fileResId);
}
不是不能缓存大数据,而是避免用缓存替代存储系统,需要根据数据访问频率、读写比例、数据一致性要求进行分级管理。大对象缓存会导致内存碎片化,甚至触发Full GC。
2、动态TTL策略
问题场景还原
合同模板缓存的TTL固定为1小时,导致:
法律风险:法务紧急更新模板后,旧版本缓存未及时失效
穿透风险:缓存过期,引发批量缓存穿透
解决方案:三级TTL策略
// 合同模板缓存设置(Spring Boot实现)
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {return RedisCacheManager.builder(factory)// 基础TTL + 随机抖动(防集中失效).cacheDefaults(defaultCacheConfig().entryTtl(Duration.ofMinutes(30 + ThreadLocalRandom.current().nextInt(10))))// 特殊Key定制(高敏感数据缩短TTL).withInitialCacheConfigurations(Map.of("contract_template", CacheConfig.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)) // 模板变更频繁)).build();
}// 获取模板时动态续期(高频访问数据自动延长有效期)
public String getContractTemplate(String templateId) {String template = redisTemplate.opsForValue().get(templateId);if (template != null) {// 每次访问续期15分钟(LFU思想)redisTemplate.expire(templateId, 15, TimeUnit.MINUTES); }return template;
}
3、避免批量失效
血泪教训
项目压测期间,积分缓存设置相同TTL,导致凌晨集中失效,数据库瞬时被打爆
-
数据库瞬时QPS飙升:从2000飙升至12000+
-
线程池满载:大量查询堆积触发熔断
// 缓存加载时注入随机TTL(单位:秒)
public void cacheElectricData(String plantId, ElectricData data) {// 基础24小时 + 随机2小时分钟(3600 * 24 ± 3600 * 2秒)int baseTtl = 3600 * 24; int randomOffset = new Random().nextInt(3600 * 2); redisTemplate.opsForValue().set("plant_electric:" + plantId, data, baseTtl + randomOffset, TimeUnit.SECONDS);
}
失效时间均匀分布在 02:00~04:00(业务低谷)
4、增加熔断降级
防止万一缓存挂了,不能影响整个服务的可用性
这个就不仅仅是缓存了,例如合同签署过程中,CA机构服务异常,不能阻塞后续业务进行
// 1. 框架层熔断(Sentinel)
@SentinelResource(value = "caSignService", fallback = "localFallback", blockHandler = "systemBlock",exceptionsToIgnore = {InvalidSignatureException.class} // 业务异常不触发熔断
)
public SignResult callCaService(Certificate cert, byte[] pdf) {return caClient.sign(cert, pdf); // CA机构远程调用
}// 2. 降级策略 - 本地存证+异步补偿
private SignResult localFallback(Certificate cert, byte[] pdf, Throwable ex) {// 生成临时签名标识(法律允许72小时内补签)String tempSignId = "TEMP_" + UUID.randomUUID();// 存储待签文件至MinIOminioClient.putObject("pending-sign", tempSignId, pdf);// 发送延迟补偿消息(每5分钟重试)rocketMQTemplate.sendDelay("SIGN_RETRY_TOPIC", MessageBuilder.withPayload(tempSignId).build(),5, TimeUnit.MINUTES);return new SignResult(202, "签名延迟处理中", tempSignId);
}// 3. 系统级熔断 - 返回法律兜底模板
private SignResult systemBlock(Certificate cert, byte[] pdf, BlockException ex) {return new SignResult(503, "系统维护中,请使用纸质签约流程");
}
5、空值缓存
在用户请求并发量大的业务场景种,需要把空值缓存起来,防止大批量在系统中不存在的id,没有命中缓存,而直接查询数据库的情况。
测评时遇到非法合同ID扫描攻击
- 需记录审计日志
- 防止缓存污染
分层拦截方案
public Contract getContract(String contractId) {// 第一层:布隆过滤器(10亿数据占120MB)if (!bloomFilter.mightContain(contractId)) {auditService.logInvalidRequest(contractId); // 记录黑产行为throw new ContractNotFoundException();}// 第二层:空值缓存(带法律标识)Contract contract = redisTemplate.opsForValue().get(contractId);if (contract == NULL_LEGAL_OBJ) { // 特殊空值对象return null;}if (contract != null) {return contract;}// 第三层:分布式锁保护DB查询RLock lock = redisson.getLock("LOCK_CONTRACT:" + contractId);lock.lock(3, TimeUnit.SECONDS);try {// 双重检查contract = redisTemplate.get(contractId);if (contract != null) return contract;// 数据库查询contract = contractDao.findById(contractId);if (contract == null) {// 法律合规空值(带审计标记)LegalNullObject nullObj = new LegalNullObject("INVALID_CONTRACT", System.currentTimeMillis());redisTemplate.opsForValue().set(contractId, nullObj, 30, TimeUnit.SECONDS // 短TTL);return null;} else {redisTemplate.opsForValue().set(contractId, contract, 1, TimeUnit.HOURS);return contract;}} finally {lock.unlock();}
}
6、分布式锁用Redisson
签章流水号控制,防止同一份合同被重复签署
红锁(RedLock)实现
public boolean trySignContract(String contractId) {// 获取5个独立Redis节点的锁RLock[] locks = new RLock[5];for (int i = 0; i < 5; i++) {locks[i] = redissonClient.getLock("SIGN_LOCK:" + contractId + ":node" + i);}RLock redLock = new RedissonRedLock(locks);try {// 尝试加锁(等待300ms,锁持有1分钟)if (redLock.tryLock(300, 60_000, TimeUnit.MILLISECONDS)) {if (signLogDao.hasSigned(contractId)) {return false; // 已签署}caService.sign(contractId);return true;}return false;} finally {redLock.unlock();}
}
7、延迟双删策略
签署任务状态一致性保障
- 签署任务状态变更后必须确保缓存与数据库绝对一致
// 四阶段双删策略
@Transactional
public void updateContractStatus(String contractId, ContractStatus status) {// 阶段1:预删除缓存redisTemplate.delete(contractId);// 阶段2:DB更新(带事务)contractDao.updateStatus(contractId, status);// 阶段3:异步延迟双删rocketMQTemplate.sendAsync("CACHE_DELETE_TOPIC", MessageBuilder.withPayload(contractId).setHeader("DELAY_LEVEL", "3") // 5秒延迟.build());// 阶段4:区块链存证(独立事务)blockchainService.recordStatusChange(contractId, status);
}// MQ消费者
@RocketMQMessageListener(topic = "CACHE_DELETE_TOPIC")
public class CacheDeleteListener implements RocketMQListener<String> {@Overridepublic void onMessage(String contractId) {// 二次删除redisTemplate.delete(contractId);// 重建缓存(最终一致性)Contract contract = contractDao.findById(contractId);if (contract != null) {redisTemplate.opsForValue().set(contractId, contract, 1, TimeUnit.HOURS);}}
}
8、最终一致性方案
// 1. 可靠消息发送(防丢失)
public void updateContractStatus(String contractId, Status status) {// 在事务中保存状态变更记录contractDao.updateStatus(contractId, status); // 发送半消息(RocketMQ事务消息)TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction("contract_tx_group",MessageBuilder.withPayload(new StatusChangeEvent(contractId, status)).build(),null);// 事务回查(确保消息落地)if (sendResult.getState() != LocalTransactionState.COMMIT_MESSAGE) {throw new IllegalStateException("消息发送失败");}
}// 2. MQ消费者(保证幂等)
@RocketMQMessageListener(topic = "STATUS_CHANGE_TOPIC", consumerGroup = "contract_group")
public class StatusChangeConsumer implements RocketMQListener<StatusChangeEvent> {@Overridepublic void onMessage(StatusChangeEvent event) {// 幂等检查(防止重复消费)if (statusLogDao.isProcessed(event.getMessageId())) return;// 步骤1:删除缓存redisTemplate.delete("contract:" + event.getContractId());// 步骤2:区块链存证blockchainService.recordStatus(event.getContractId(), event.getStatus());// 步骤3:重建缓存(存证成功后)Contract contract = contractDao.findById(event.getContractId());redisTemplate.opsForValue().set("contract:" + event.getContractId(), contract,1, TimeUnit.HOURS);// 记录处理日志statusLogDao.markProcessed(event.getMessageId());}
}
9、热点数据预加载
合同模板缓存
- 上班时间(9:00)模板请求量激增
- 新模板上线时缓存穿透
// 1. 基于时间窗口的预测加载
@Scheduled(cron = "0 0 8 * * ?") // 每天8:00执行
public void preloadMorningTemplates() {// 预测今日热点(昨日TOP100模板)List<String> hotTemplates = statsDao.getYesterdayHotTemplates(100);// 并行预加载hotTemplates.parallelStream().forEach(templateId -> {ContractTemplate template = templateDao.load(templateId);redisTemplate.opsForValue().set("template:" + templateId,template,12, TimeUnit.HOURS // 覆盖日间高峰);});
}// 2. 实时热点探测(流式计算)
@KafkaListener(topics = "template_access_log")
public void handleAccessLog(AccessLog log) {// 滑动窗口计数(10分钟)long count = redisTemplate.opsForZSet().count("template_access:" + log.templateId(), System.currentTimeMillis() - 600_000, Long.MAX_VALUE);// 超过阈值触发预加载if (count > 1000) { executor.submit(() -> {if (!redisTemplate.hasKey("template:" + log.templateId())) {loadTemplateToCache(log.templateId());}});}
}
10、根据场景选择数据结构
错误案例
redis.set("template:123", JSON.toJSONString(template));
每次更新单个字段都需要反序列化整个对象。
导致问题:
- 序列化/反序列化开销大
- 更新单个字段需读写整个对象
- 内存占用高 正确实践:
正确案例
// 使用Hash存储
redis.opsForHash().putAll("template:123", templateToMap(template)); // 局部更新
redis.opsForHash().put("template:123", "resId", "31212");
缓存落地总结
- 避免大Key 合同PDF存储 元数据存Redis,文件存MinIO 内存降低80%,GC频率减少90%
- 动态TTL 合同模板动态ttl
- 防批量失效 每日积分缓存 TTL=24h±2h随机 数据库峰值压力降低92%
- 熔断降级 CA机构调用 Sentinel熔断+本地存证+异步重试 签署服务可用性99.99%
- 空值缓存 非法合同ID拦截 布隆过滤器+LegalNullObject+分布式锁 穿透查询减少100%
- 分布式锁 签章流水号控制 Redisson红锁(5节点) 重复签署事故归零
- 延迟双删 合同状态更新 预删除→DB更新→延迟消息→二次删除 状态不一致率<0.001%
- 最终一致性 区块链存证状态同步 RocketMQ事务消息+消费者幂等 存证延迟从3.2s降至0.8s
- 热点预加载 合同模板缓存 定时任务(8:00)+流式热点探测 9:00峰值响应从450ms→35ms
- 数据结构优化 签章记录存储 签章ID-ZSet + 详情-Hash 内存占用减少70%,分页性能提升6倍
相关文章:
Redis缓存落地总结
最近在优化电子签系统,涉及到缓存相关的也一并优化了,写个文档做个总结,防止以后开发时又考虑不全 1、避免大key 避免缓存大PDF文件: 💡 经验值:单个Redis Value不超过10KB,集合元素不超过500…...

Spring,SpringMVC,SpringBoot
1.Spring最核心包括aop和ioc概念 AOP 能够将将哪些于业务无关的,并且大量重复的业务逻辑进行封装起来,便于减少重复代码,降低模块之间的耦合度,给未来的系统更好的可用性和可维护性。 Spring中AOP是采用动态代理,JDK代…...
npm、pnpm、yarn使用以及区别
npm 使用 安装包:在项目目录下,npm install <包名> 用于本地安装包到 node_modules 目录,并添加到 package.json 的 dependencies 中;npm install -g <包名> 用于全局安装,适用于命令行工具等。初始化项目…...
flutter加载dll 报错问题
解决flutter加载dll 报错问题 LoadLibrary 报错 126 or 193 明确一点:flutter构建exe 时默认是MSVC的。 1. 先检查dll 的位数是否满足 file ***.dll output: PE32 executable (DLL) (console) x86-64, for MS Windows, 19 sections 这种是64位的机器。 满足的话可…...

数据分析学习笔记——A/B测试
目录 前言 A/B测试中的统计学方法 假设检验 Levenes Test莱文测试 t 检验(两组均值差异) 实战案例 数据来源及参考资料 代码详解 导入数据 计算ROI Request检验 GMV检验 ROI检验 结语 前言 什么是A/B测试?说白了就是中学生物实…...
【python深度学习】Day 41 简单CNN
知识回顾 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 卷积操作常见流程如下: 1. 输入 → 卷积层 → Batch归一化层…...

基于RK3568/RK3588/全志H3/飞腾芯片/音视频通话程序/语音对讲/视频对讲/实时性好/极低延迟
一、前言说明 近期收到几个需求都是做音视频通话,很多人会选择用webrtc的方案,这个当然是个不错的方案,但是依赖的东西太多,而且相关组件代码量很大,开发难度大。所以最终选择自己属性的方案,那就是推流拉…...

解决 Win11 睡眠后黑屏无法唤醒的问题
目录 一、问题描述二、解决方法1. 禁用快速启动2. 设置 Management Engine Interface3. 允许混合睡眠其他命令 4. 修复系统文件5. 更新 Windows 或驱动程序6. 其他1)更改电源选项2)刷新 Hiberfil.sys 文件3)重置电源计划4)运行系统…...

[ElasticSearch] RestAPI
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

Linux中的shell脚本
什么是shell脚本 shell脚本是文本的一种shell脚本是可以运行的文本shell脚本的内容是由逻辑和数据组成shell脚本是解释型语言 用file命令可以查看文件是否是一个脚本文件 file filename 脚本书写规范 注释 单行注释 使用#号来进行单行注释 多行注释 使用 : " 注释内容…...

dvwa3——CSRF
LOW: 先尝试change一组密码:123456 修改成功,我们观察上面的url代码 http://localhost/DVWA/vulnerabilities/csrf/?password_new123456&password_conf123456&ChangeChange# 将password_new部分与password_conf部分改成我们想要的…...

【学习笔记】Transformer
学习的博客(在此致谢): 初识CV - Transformer模型详解(图解最完整版) 1 整体结构 Transformer由Encoder和Decoder组成,分别包含6个block。 Transformer的工作流程大体如下: 获取每个单词的em…...

欢乐熊大话蓝牙知识12:用 BLE 打造家庭 IoT 网络的三种方式
🏠 用 BLE 打造家庭 IoT 网络的三种方式 不止是“蓝牙耳机”,BLE 还能把你家“点亮成精”! 👋 前言:BLE 不只是蓝牙耳机的“代名词” 蓝牙?很多人一听就联想到“耳机连接失败请重试”。但你知道吗?现在 BLE(Bluetooth Low Energy)在智能家居中已经偷偷搞起了大事情。…...

02.上帝之心算法用GPU计算提速50倍
本文介绍了上帝之心的算法及其Python实现,使用Python语言的性能分析工具测算性能瓶颈,将算法最耗时的部分重构至CUDA C语言在纯GPU上运行,利用GPU核心更多并行更快的优势显著提高算法运算速度,实现了结果不变的情况下将耗时缩短五…...

MES管理系统:Java+Vue,含源码与文档,实现生产过程实时监控、调度与优化,提升制造企业效能
前言: 在当今竞争激烈的制造业环境中,企业面临着提高生产效率、降低成本、提升产品质量以及快速响应市场变化等多重挑战。MES管理系统作为连接企业上层计划管理系统与底层工业控制之间的桥梁,扮演着至关重要的角色。它能够实时收集、分析和处…...

LeetCode算法题 (搜索二维矩阵)Day18!!!C/C++
https://leetcode.cn/problems/search-a-2d-matrix/description/ 一、题目分析 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 ta…...

VectorStore 组件深入学习与检索方法
考虑到目前市面上的向量数据库众多,每个数据库的操作方式也无统一标准,但是仍然存在着一些公共特征,LangChain 基于这些通用的特征封装了 VectorStore 基类,在这个基类下,可以将方法划分成 6 种: 相似性搜…...

HackMyVM-First
信息搜集 主机发现 ┌──(kali㉿kali)-[~] └─$ nmap -sn 192.168.43.0/24 Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-31 06:13 EDT Nmap scan report for 192.168.43.1 Host is up (0.0080s latency). MAC Address: C6:45:66:05:91:88 (Unknown) …...
30V/150A MOSFET 150N03在无人机驱动动力系统中的性能边界与热设计挑战
产品技术概述 150N03 是一款基于沟槽式工艺(Trench Technology)的N沟道功率MOSFET,其核心价值在于: 电压/电流规格:VDSS30V, ID150A (Tc25℃) 工艺特征:高密度元胞设计实现超低导通电阻 双面散热架构:顶部裸露铜架底…...
数据共享交换平台之数据资源目录
依据信息资源体系规范,构建多维度、多层级的资源目录体系,完整的展示和管理资源目录。资源目录提供以下功能: 多层级资源目录展示,能够将资源目录按照技术维度和管理维度进行分类管理,并能够将资源目录按照数据湖、基础…...

跨平台浏览器集成库JxBrowser 支持 Chrome 扩展程序,高效赋能 Java 桌面应用
JxBrowser 是 TeamDev 开发的跨平台库,用于在 Java 应用程序中集成 Chromium 浏览器。它支持 HTML5、CSS3、JavaScript 等,具备硬件加速渲染、双向 Java 与 JavaScript 连接、丰富的事件监听等功能,能处理网页保存、打印等操作,助…...

WEBSTORM前端 —— 第3章:移动 Web —— 第3节:移动适配
目录 一、移动Web基础 1.谷歌模拟器 2.屏幕分辨率 3.视口 4.二倍图 二、适配方案 三、rem 适配方案 四、less 1.less – 简介 2.less – 注释 3.less – 运算 4.less – 嵌套 5.less – 变量 6.less – 导入 7.less – 导出 8.less – 禁止导出 五…...
38.springboot使用rabbitmq
pom依赖 <!--amqp依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency> 配置文件添加 spring:application:name: message…...

弱光环境下如何手持相机拍摄静物:摄影曝光之等效曝光认知
写在前面 博文内容为一次博物馆静物拍摄笔记的简单总结内容涉及:弱光环境拍摄静物如何选择,以及等效曝光的认知理解不足小伙伴帮忙指正 😃,生活加油 我看远山,远山悲悯 持续分享技术干货,感兴趣小伙伴可以关注下 _ 采…...
Selenium Manager中文文档
1. 什么是 Selenium Manager(测试版) Selenium Manager 是 Selenium 官方提供的命令行工具(用 Rust 实现),用于自动管理浏览器及其驱动(chromedriver、geckodriver、msedgedriver 等)。从 Sele…...
WEB安全--SQL注入--MSSQL注入
一、SQLsever知识点了解 1.1、系统变量 版本号:version 用户名:USER、SYSTEM_USER 库名:DB_NAME() SELECT name FROM master..sysdatabases 表名:SELECT name FROM sysobjects WHERE xtypeU 字段名:SELECT name …...

【HTML】基础学习【数据分析全栈攻略:爬虫+处理+可视化+报告】
- 第 102 篇 - Date: 2025 - 05 - 31 Author: 郑龙浩/仟墨 文章目录 HTML 基础学习一 了解HTML二 HTML的结构三 HTML标签1 标题2 文本段落3 换行4 加粗、斜体、下划线5 插入图片6 添加链接7 容器8 列表9 表格10 class类 HTML 基础学习 一 了解HTML 一个网页分为为三部分&…...
SAP Business ByDesign:无锡哲讯科技赋能中大型企业云端数字化转型
云端ERP时代,中大型企业的智能化引擎 在数字经济高速发展的今天,中大型企业面临着全球化竞争、供应链复杂化、数据安全等多重挑战。传统的本地化ERP系统已无法满足企业快速响应市场变化的需求,而SAP Business ByDesign(ByD&…...
华为OD机考2025B卷 - 无向图染色(Java Python JS C++ C )
最新华为OD机试 真题目录:点击查看目录 华为OD面试真题精选:点击立即查看 题目描述 给一个无向图染色,可以填红黑两种颜色,必须保证相邻两个节点不能同时为红色,输出有多少种不同的染色方案? 输入描述 第一行输入M(图中节点数) N(边数) 后续N行格式为:V1 V2表示…...
计算机网络学习20250528
地址解析协议ARP 实现IP地址和Mac地址的转换 ARP工作原理: 每台主机或路由器都有一个ARP表,表项:<IP地址,Mac地址,TTL>(TTL一般为20分钟) 主机产生ARP查询分组,包含源目的IP地…...