前言

作为一名普通的 Java 程序开发者,日常开发中难免会遇到一些看似简单但实际排查起来非常棘手的问题。在最近的一个项目中,我遇到了一个 Redis 缓存穿透的问题,导致系统在高并发下性能急剧下降,甚至出现服务响应超时的情况。这个问题虽然不是特别复杂,但排查过程让我对缓存机制有了更深入的理解,也积累了一些实战经验。

本文将从问题现象、排查思路、代码分析和最终的解决方案几个方面来记录这次真实的 bug 排查经历,希望对同样使用 Spring Boot 和 Redis 的开发者有所帮助。

问题现象

我们的系统是一个基于 Spring Boot 的微服务架构,其中有一个订单查询接口,为了提升性能,我们引入了 Redis 缓存来存储用户的历史订单数据。正常情况下,这个接口运行良好,但某天早上突然收到监控系统的告警,提示该接口的响应时间异常增加,且错误率上升。

初步观察发现,当请求某些不存在的订单 ID 时,接口返回了错误信息,并且这些请求直接绕过了 Redis,直接访问了数据库。这种现象明显不符合预期,因为按照设计,即使缓存中没有数据,也应该通过空值缓存(如设置 TTL 为 1 分钟)来防止频繁访问数据库。

问题分析

首先,我想到的是缓存穿透的可能性。缓存穿透是指查询一个不存在的数据,由于缓存中没有,而数据库也没有,导致每次请求都去访问数据库,从而造成数据库压力过大。

进一步查看日志发现,很多请求的 key 是无效的订单 ID,比如 order:123456789,而这些订单 ID 并不存在于数据库中。这说明确实存在缓存穿透的问题。

接下来,我检查了 Redis 的配置和代码逻辑,确认了以下几点:

  1. Redis 缓存未设置过期时间:在某些场景下,如果缓存中没有数据,我们没有设置任何 TTL,导致缓存永远不会失效,但也不会被填充。
  2. 未对非法请求进行过滤:对于一些恶意请求或非法参数,系统没有做拦截,导致大量无意义的请求直接打到数据库。
  3. 缓存策略不合理:在查询不到数据时,没有使用“空值缓存”来防止重复查询。

排查步骤

步骤一:验证缓存行为

我首先在本地启动了 Spring Boot 应用,并模拟了多个请求,测试 Redis 是否真的能正确缓存数据。使用 Jedis 客户端连接 Redis,执行 GET order:123456789,发现返回结果是 nil,即缓存中没有该 key。

public Order getOrderById(String orderId) {String cacheKey = "order:" + orderId;Order order = redisTemplate.opsForValue().get(cacheKey);if (order != null) {return order;}// 如果缓存中没有,则查询数据库order = orderRepository.findById(orderId);// 如果数据库中也没有,则不缓存if (order != null) {redisTemplate.opsForValue().set(cacheKey, order, 10, TimeUnit.MINUTES);}return order;
}

从这段代码可以看出,只有当数据库中有数据时,才会将数据写入 Redis,否则不会有任何缓存操作。这就导致了缓存穿透问题。

步骤二:分析 Redis 数据结构

我使用 Redis 的命令行工具查看了相关的 key,发现大量的 order:* 类型的 key 都是空的,也就是说,这些请求根本没有命中缓存。

步骤三:添加空值缓存逻辑

为了防止缓存穿透,我在代码中加入了空值缓存逻辑。即当数据库中没有数据时,也向 Redis 缓存一个空值,设置较短的 TTL,避免频繁访问数据库。

public Order getOrderById(String orderId) {String cacheKey = "order:" + orderId;Order order = redisTemplate.opsForValue().get(cacheKey);if (order != null) {return order;}// 查询数据库order = orderRepository.findById(orderId);// 如果数据库中也没有,则缓存一个空值if (order == null) {redisTemplate.opsForValue().set(cacheKey, "", 1, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(cacheKey, order, 10, TimeUnit.MINUTES);}return order;
}

这样,即使请求的是不存在的订单 ID,Redis 中也会缓存一个空值,后续相同的请求就会直接从缓存中获取,避免了对数据库的频繁访问。

步骤四:增加请求过滤机制

为了进一步减少无效请求,我还增加了请求参数校验逻辑,确保传入的订单 ID 符合业务规则。

public ResponseEntity<Order> getOrder(@PathVariable String orderId) {if (!isValidOrderId(orderId)) {return ResponseEntity.badRequest().body(null);}Order order = orderService.getOrderById(orderId);return ResponseEntity.ok(order);
}private boolean isValidOrderId(String orderId) {return orderId != null && orderId.matches("\d{8}");
}

这样可以有效过滤掉一些非法请求,减少不必要的数据库查询。

总结

这次缓存穿透问题的排查过程让我深刻认识到缓存设计的重要性。在实际开发中,不能只关注缓存的命中率,还要考虑如何处理缓存未命中的情况,尤其是针对非法请求的处理。

通过添加空值缓存和请求过滤机制,我们成功解决了缓存穿透问题,提升了系统的稳定性和性能。此外,我也意识到,在高并发环境下,合理的缓存策略和防御机制是保障系统健壮性的关键。

总的来说,这次 bug 排查不仅帮助我修复了一个实际问题,也让我对 Redis 和 Spring Boot 的缓存机制有了更深的理解。

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

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

相关文章

Ubuntu下配置并远程连接MySQL

1、安装mysql-serverapt update apt install mysql-server2、修改配置文件/etc/mysql/mysql.conf.d/mysqld.cnfbind-address 0.0.0.0 mysqlx-bind-address 0.0.0.03、启动并设置服务为开机自启动systemctl enable mysql.service --now4、查看服务状态systemct…

开源 C++ QT Widget 开发(九)图表--仪表盘

文章的目的为了记录使用C 进行QT Widget 开发学习的经历。临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 C QT Widget 开发&#xff08;一&#xff09;工程文件结构-CSDN博客 开源…

怎么为服务器设置或重置服务器密码?

创建服务器后&#xff0c;您可以设置服务器的登录密码&#xff0c;如果你忘记了密码&#xff0c;可以重新设置实例的密码。本文讲一下如何重置阿里云服务器密码。使用限制&#xff1a;离线重置密码仅支持在控制台设置或重置服务器管理员账号的密码。•Windows 实例的默认用户名…

【线性代数入门 | 那忘算8】洛谷P3389 高斯消元(内附行列式教学)

想了想还是单开了一篇&#xff0c;数学王子值得&#xff01; 专栏指路&#xff1a;《再来一遍一定记住的算法&#xff08;那些你可能忘记了的算法&#xff09;》 前置知识&#xff1a; 矩阵&#xff1a;数的集合&#xff0c;一般是方程的系数。 题面&#xff1a; 洛谷P3389 …

GEM5学习(3):如何快速创建一个组件

通过一个图并行计算的测试用例&#xff0c;来学习如何快速构建一个目标组件 其核心思想是通过继承现有组件再拓展自定义参数 创建脚本 如何创建脚本&#xff0c;具体还可以看官方说明&#xff1a;gem5: Adding cache to configuration script mkdir configs/tutorial/part1/…

数据血缘中的图数据库如何选择

Neo4j 和 ArangoDB 都是非常优秀的图数据库&#xff0c;但它们的设计哲学、核心架构和适用场景有显著的区别。 简单来说&#xff0c;核心区别在于&#xff1a; Neo4j 是原生图数据库&#xff0c;专为处理图数据和图查询而设计和优化。ArangoDB 是多模型数据库&#xff0c;同时支…

第27章学习笔记|学无止境:从“会用命令”到“会做工具”的进阶路线

第27章学习笔记|学无止境:从“会用命令”到“会做工具”的进阶路线 你已经能用 PowerShell 解决很多日常问题了。接下来最重要的,就是把零散命令升级为可复用的工具,并在真实场景中不断打磨。 一、为什么下一步是“工具化(Toolmaking)” 当任务开始“重复、多人用、可移…

C++编程语言:标准库:第37章——正则表达式(Bjarne Stroustrup)

第 37章 正则表达式(Regular Expressions) 目录 37.1 正则表达式(规范表达式)(Regular Expressions) 37.1.1 正则表达式相关符号(Regular Express Notation) 37.2 regex 37.2.1 匹配结果(Match Results) 37.2.2 格式化(Formatting) 37.3 正则表达式函数 37.3.1 …

sciml包scixgboost函数发布,轻松完成机器学习xgboost分析

Xgboost是Boosting算法的其中一种&#xff0c;Boosting算法的思想是将许多弱分类器集成在一起&#xff0c;形成一个强分类器。因为Xgboost是一种提升树模型&#xff0c;所以它是将许多树模型集成在一起&#xff0c;形成一个很强的分类器。 我目前整合了多个R包&#xff0c;编写…

Ubuntu中配置JMmeter工具

1、检查是否已安装Java 环境java -version若未安装&#xff0c;执行以下命令安装 OpenJDKsudo apt update sudo apt install openjdk-11-jdk # 或 openjdk-17-jdk2、用wget直接下载JMeter压缩包wget https://dlcdn.apache.org/jmeter/binaries/apache-jmeter-5.6.3.tgz将下载的…

LeetCode 925.长按键入

你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&#xff08;其中一些字符可能被长按&#xff09;&#…

9.3 模拟

lc190 颠倒二进制ret (ret << 1) (n >> i & 1);class Solution { public:uint32_t reverseBits(uint32_t n) {uint32_t ret 0;for (int i 0; i < 32; i)ret (ret << 1) (n >> i & 1);return ret;} };lc14 flag checkclass Solution {…

esp32小智ai对话机器人

ESP-IDF 环境搭建与问题解决 下载与安装 ESP-IDF 官方下载地址&#xff1a;https://dl.espressif.com/dl/esp-idf建议使用稳定版本&#xff0c;避免开发版可能存在的兼容性问题 中文编码问题解决方案 $env:PYTHONIOENCODING "utf-8" $env:PYTHONUTF8 "1&q…

11.类与对象

目录 1. 创建类与对象示例 1.1 __init__ 初始化器: 1.2 __new__构造器 1.3 什么时候需要重写 __new__? 1.4 init和new对比 2. 属性 2.1 实例属性 2.2 类属性 3. 作用域命名约定 3.1 非公共属性 3.2 公共属性 3.3 名称修饰 3.4 一眼看懂三种“可见性” 4. 方法 …

【js】Promise.try VS try-catch

前言JavaScript 正为 Promise 添加一个新的方法&#xff0c;使得处理异步函数更加清晰和安全。Promise.try 允许将任何函数包装在 Promise 中&#xff0c;无论它是否异步。使用 Promise.try 可避免传统 try/catch 结构与 Promise 链的混合使用&#xff0c;代码更简洁。try-catc…

MySQL-表的约束(上)

表的约束在 MySQL 中&#xff0c;表的约束&#xff08;Constraints&#xff09;用于确保数据库中数据的完整性和一致性。它们定义了对表中数据的规则和限制&#xff0c;防止无效或不一致的数据被插入、更新或删除。常见的 MySQL 表约束包括主键约束&#xff08;PRIMARY KEY&…

Frida + FART 联手:解锁更强大的 Android 脱壳新姿势

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ Frida FART 联手能带来什么提升&#xff1f; 增强 FART 的脱壳能力&#xff1a;解决对抗 FART 的壳、动态加载的 dex 的 dump 和修复&#xff1b; 控制 FART…

TLS/SSL(传输层安全协议)

文章目录一、核心概念二、为什么需要 TLS/SSL&#xff1f;三、工作原理与详细流程握手步骤详解&#xff1a;1.ClientHello & ServerHello&#xff1a;2.服务器认证 (Certificate, ServerKeyExchange)&#xff1a;3.客户端响应 (ClientKeyExchange, Finished)&#xff1a;4.…

什么是 AWS 和 GCE ?

AWS 和 GCE 是两种不同厂商提供的云计算服务&#xff0c;主要区别在于提供商和产品定位。AWS全称&#xff1a;Amazon Web Services提供商&#xff1a;亚马逊 (Amazon)简介&#xff1a;全球最大的云计算平台之一&#xff0c;提供完整的云服务&#xff0c;包括&#xff1a; 计算&…

水电站电动机绝缘安全 “不掉线”!在线监测方案筑牢发电保障

对水电站而言&#xff0c;消防水泵、深井水泵等辅助电动机是安全运行的 “关键配角”—— 它们常年处于备用状态&#xff0c;又受潮湿环境影响&#xff0c;绝缘值降低易引发烧毁故障&#xff0c;而传统定期检测难以及时捕捉绝缘劣化趋势&#xff0c;一旦启动时出问题&#xff0…