1. 问题背景在前端页面上传图片时,接口直接返回 500 Internal Server Error。
开发环境:macOS (Apple Silicon) / Spring Boot / Nginx 1.29
业务场景:前端通过 Nginx 反向代理上传图片到后端服务器。
前端请求地址:http://localhost:8080/api/upload/blog (Nginx 监听端口)
后端真实地址:http://localhost:8081/upload/blog (Spring Boot 服务端口)
2. 故障现象第一反应是去检查 IDEA 控制台的 Sprin...
Redis 实战:从零手写分布式锁(误删问题与 Lua 脚本优化)在单体架构中,我们习惯使用 synchronized 或 Lock 来解决并发安全问题。但在分布式集群架构下,不同的服务运行在不同的 JVM 中,本地锁也就失效了。
本文将复现如何基于 Redis 实现一个分布式锁,并一步步解决死锁、误删、原子性等经典问题。
一、 初级版本:利用 SETNX 实现互斥Redis 的 SETNX (Set if Not Exists) 命令天生具备互斥性:只有 Key 不存在时才能设置成功。
为了防止获取锁的服务器宕机导致锁永远无法释放(死锁),我们需要在使用 SETNX 的同时设置过期时间...
一、 业务背景在“优惠券秒杀”场景中,为了防止用户恶意刷单、保障活动公平性,业务规则强制要求:同一个用户 ID 对同一张优惠券只能下单一次。
在低并发场景下,这是一个简单的“查询校验 -> 扣减库存 -> 创建订单”的串行逻辑。但在高并发场景下,如果两个线程同时进入“查询”阶段,都会判定当前用户未下单,从而同时执行后续的创建订单逻辑,导致数据库中产生同一用户的多条订单,违背了业务规则。
本文将复盘该功能在单体架构下的实现细节,以及随着架构升级为集群部署后,并发问题是如何再次出现并最终通过分布式锁解决的。
二、 单体架构下的解决方案在项目初期,服务采用单节点部署(单个 JVM)...
一、 什么是缓存击穿?缓存击穿(Cache Breakdown) 是指一个热点 Key(比如某次秒杀活动的商品详情),在某个时间点过期了。恰好在这个时间点,有大量的并发请求访问这个 Key。这些请求发现缓存过期,瞬间全部打到数据库上,就像在防线上凿穿了一个洞,导致数据库压力激增甚至宕机。
核心特征:
高并发:访问量巨大。
热点 Key:大家都在查同一个数据。
瞬间失效:缓存 TTL 到期,数据物理消失。
二、 互斥锁&逻辑过期面对缓存击穿,通常有两种解法:
1. 互斥锁(Mutex Lock)
思路:谁发现缓存过期了,谁就去抢一把锁。抢到锁的人去查数据库写缓存,其他人排队等待。...
1. 设计思路在分布式系统或前后端分离的架构中,我们需要一种无状态的登录方案。核心设计思路如下:
凭证机制:使用 Token(随机字符串)作为用户身份的唯一标识,替代传统的 Cookie。
数据存储:将用户的登录状态(Token 与用户信息的映射)存储在 Redis 中,利用其高性能和自动过期机制。
状态管理:
Redis:作为服务端共享的“会话存储中心”。
ThreadLocal:作为单次请求内的“上下文容器”,方便 Controller 和 Service 层获取用户信息。
请求拦截:采用 双拦截器模式,分离“Token 刷新”与“登录鉴权”的职责。
2. 代码实现2.1 步...
1. 背景与问题在无状态的 HTTP 协议下,Web 应用通常使用 Session 或 Token 机制来维持用户的登录状态。在 Spring Boot 后端开发中,面临两个核心问题:
统一校验:如何在请求到达业务逻辑之前,统一拦截未登录请求,避免在每个 Controller 方法中重复编写校验代码?
上下文共享:在 Controller、Service 甚至 Dao 层中,如何优雅地获取当前登录用户的信息,而不需要层层传递 User 对象参数?
这里介绍登录 + 拦截器 (Interceptor) + 线程本地变量 (ThreadLocal)”
2. 架构设计该方案的流程如下:...
我在 VMware 虚拟机中安装了最新的 Rocky Linux 9。 遇到了一个非常奇怪的问题:在虚拟机的黑窗口(控制台)中,使用 root 账号和密码可以正常登录。但是当使用 FinalShell 或 Xshell 等 SSH 工具进行远程连接时,输入了正确的密码,却一直提示“认证失败”或无限循环要求输入密码。
经过排查,发现这是 Rocky Linux 9(以及较新的 Linux 发行版)默认的安全策略导致的。这里记录一下解决方案。
进入配置文件 在虚拟机的终端中,输入以下命令编辑 sshd_config 文件:
1vi /etc/ssh/sshd_config
修改配置...
本次部署采用经典的 反向代理(Reverse Proxy) 模式。
流量入口:Nginx 监听宿主机的 80 端口。
前端应用:Vue 构建后的静态资源(HTML/CSS/JS),托管于 Nginx 本地。
后端服务:Spring Boot 应用运行于 8081 端口,不再直接对外暴露。
通信链路: HTTP 请求:由 Nginx 进行路径重写后转发。
WebSocket 连接:由 Nginx 处理握手并建立全双工 TCP 隧道。
1. Nginx 完整配置代码这是我目前正在使用的配置。Nginx 监听 80 端口,后端 Tomcat 运行在 8081 端...
在做Spring Boot 的 Web 项目时,在 Controller 或 Service 层经常会看到这样一行代码:
12// 在 Service 层直接获取当前登录用户的ID Long userId = BaseContext.getCurrentId();
这就很神奇了:
没有传参:Controller 调用 Service 时,并没有把 userId 作为参数传进来
没有查库:这一行代码也没有去查询数据库
数据准确:它总是能精准地拿到当前发送请求的那个用户的 ID,张三发请求拿到张三,李四发请求拿到李四,互不干扰
它是怎么做到的?
有两个核心概念:ThreadLocal...
1. 问题现象Database 面板里已经成功连接了数据库,表都能看得到。
SQL 代码(MyBatis XML 或 @Select 注解)本身没有语法错误,在数据库里执行也能跑通。
但是IDEA 编辑器里,提示 Unable to resolve table 'xxx'。
2. 快速解决方案这个问题的根源在于 IDEA 不知道当前的代码文件应该对应哪个数据库连接。我们需要手动设置 SQL Resolution Scope(SQL 解析作用域)。
3. 为什么会有这个问题?我明明设置了 SQL Dialect(方言),也连接了数据库,而且我只有一个数据库,IDEA 为...