如何高并发下解决超卖现象
高并发秒杀/抢购场景中,多个用户同时下单,会导致库存减成负数,这就是 超卖 。
解决超卖,本质是 保证库存扣减的原子性 + 正确的并发控制。
方案 1:数据库悲观锁(for update)
BEGIN; SELECT stock FROM product WHERE id = 1001 FOR UPDATE; -- 应用层判断 stock > 0 UPDATE product SET stock = stock - 1 WHERE id = 1001; COMMIT;
- 优点:简单直观,强一致性。
- 缺点 :
- 行锁阻塞,并发性能差;
- 长事务易导致死锁或 DB 压力大;
- 不适合秒杀等极高并发场景。
适用:中低并发、对一致性要求极高的场景。
方案 2:数据库乐观锁(推荐常用)
表结构增加
version字段:
UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = 1001 AND stock >= 1 AND version = #{oldVersion};
- 若返回影响行数为 0,说明并发冲突,重试或提示"手慢了"。
- 配合有限次数重试(如 3 次),提升成功率。
优点:无阻塞,并发性能好;适合读多写少的场景
缺点:高并发下重试可能加剧 DB 压力。
Redis 层解决
方案 3:Redis + Lua 脚本(高性能首选)
利用 Redis 单线程特性,用 Lua 脚本保证原子性:
① Redis 单线程原子操作(强一致)
把判断 + 扣减写成一个 Lua 脚本:
Lua-- check_and_decr_stock.lua local stock = redis.call('GET', KEYS[1]) if tonumber(stock) > 0 then return redis.call('DECR', KEYS[1]) else return -1 endJava 调用:
javaLong result = redisTemplate.execute( script, Collections.singletonList("stock:1001") ); if (result != null && result >= 0) { // 扣减成功,异步下单/落库 } else { throw new RuntimeException("库存不足"); }
- 优势 :
- 性能极高(微秒级);
- 天然支持高并发;
- 可结合消息队列削峰。
- 注意 :
- Redis 与 DB 的最终一致性需通过补偿机制(如定时对账、Binlog 同步)保障;
- 需处理 Redis 宕机降级方案。
适用:秒杀、抢购等超高并发场景。
方案 4:消息队列削峰 + 异步处理
- 用户请求先入 MQ(如 RocketMQ/Kafka);
- 后台消费者单线程/限流消费,串行处理扣库存;
- 保证同一商品 ID 的消息路由到同一队列(顺序消费)。
优点:系统解耦、抗峰值;
缺点:延迟高,不适合实时反馈场景
jwt 令牌原理
jwt 简介 全称:JSON Web Token 定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全的传输信息。 由于数字签名的存在,这些信息是可靠的。
JWT(JSON Web Token)令牌的原理主要基于 "前端携带签名令牌,后端负责校验,不再存储会话状态" 的机制。
1. JWT 的结构
JWT 一共分为三部分,用
.拼接:
第一部分:Header(头),记录令牌类型、签名算法等。
例如:{"alg":"HS256","type":"JWT"}
**Payload(负载)**第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例 如:{"id":"1","username":"Tom")
存用户信息,如
userId、role存标准字段,如
exp(过期时间)**Signature(签名)**第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 header、payload, 并加入指定秘钥,通过指定签名算法计算而来。
用 Header + Payload + 秘钥 进行签名
保证令牌不被篡改
jwt的无状态指什么?
JWT 的无状态特性是指服务端不需要在服务器本地存储会话信息或状态信息,而是通 过令牌本身来验证用户身份和权限。
JWT 的工作流程(以身份验证为例)
用户登录
客户端发送用户名/密码到服务器。
服务器验证成功后生成 JWT
服务器使用密钥(secret 或私钥)对 header + payload 进行签名,生成完整的 JWT。
返回 JWT 给客户端
通常放在响应体中,或通过
Authorization: Bearer <token>返回。客户端存储 JWT
一般存于 localStorage、sessionStorage 或 Cookie(注意安全策略)。
后续请求携带 JWT
每次请求在 HTTP Header 中带上:
Authorization: Bearer <token>服务器验证 JWT
- 解码头部和载荷(Base64Url 解码)
- 使用相同密钥重新计算签名,与第三段比对
- 检查
exp是否过期等声明- 验证通过则处理请求
JWT 的优缺点
优点:
- 无状态(Stateless):服务端无需存储会话信息,适合分布式系统。
- 自包含:所有必要信息都在 token 中。
- 跨域支持良好
- 标准化,广泛支持
缺点:
- 无法主动失效:除非设置较短过期时间或引入黑名单机制。
- 体积较大:相比 session ID,JWT 更大。
- 安全性依赖实现:若密钥泄露或使用弱算法,易被伪造。
- 不适合存储敏感数据:Payload 只是编码,不是加密。
为什么要用 JWT 而不是 Session?
JWT 相比 Session 更适合微服务、前后端分离、高并发场景,因为它无状态、跨域友好、性能高,同时通过签名保证数据不可篡改;Session 适合单体应用和简单场景。
1️⃣ 分布式和扩展性
Session:有状态,需要存储在服务器内存或 Redis
多台服务器 → session 共享或 sticky session
维护成本高,增加网络开销
JWT:无状态,所有信息都在 token 内
- 任意节点都可验证 token → 天然适合微服务/分布式架构
✅ 结论:分布式系统更适合 JWT
2️⃣ 无状态(Stateless)
Session:服务端保存用户状态
依赖 sessionId
服务端重启/扩容需要同步状态
JWT:每次请求自包含信息,服务端无需保存状态
- 简化架构,便于水平扩展
3️⃣ 跨域和前后端分离
Session:依赖 Cookie
- 跨域访问复杂,需要 CORS + cookie 配置
JWT :通过
Authorization: Bearer <token>传递
- 不依赖 cookie,支持前后端分离、移动端和多端统一登录
4️⃣ 性能
Session:高并发时,每次请求都要访问内存/Redis → 增加压力
JWT:只需解析 token + 验签,无需查服务器存储 → 高性能
5️⃣ 安全性
Session:如果服务器被攻破,session 数据泄露
JWT:token 自包含签名
不能被篡改
可设置短期有效 + refresh token
缺点:被盗用需要依赖黑名单或过期策略
JWT 和 Session 的区别?
1. 存储位置不同
项目 Session JWT 状态存储 服务端内存/Redis 浏览器本地(token) 服务端是否保存状态 ✔️ 有状态 ❌ 无状态 2. 校验方式不同
Session
登录后服务端生成
sessionId服务端必须保存一份 session 数据
请求带
sessionId,服务器查 session 才知道是谁JWT
登录后生成 JWT(包括 payload + 签名)
服务端 无需保存 用户会话
请求带 token → 服务端只需校验签名即可确认身份
3. 分布式架构下适配度不同
Session
多台服务器要共享 session → 必须依赖 Redis 或 session sticky
增加架构复杂度和网络 IO
JWT
完全无状态,天然适配微服务
任何节点仅通过 token 就能鉴权
session和cookie有什么区别?
① Cookie 存在浏览器,Session 存在服务器。这是最大区别。
② Cookie 只能存字符串,Session 能存任意 Java 对象。
③ Session 更安全,因为数据不在客户端;Cookie 容易被劫持或伪造。
④ Session 占用服务器内存,用户量大时会增加服务器压力。
⑤ Cookie 大小有限制(约 4KB),数量有限(约 20 个/域)。Session 理论上没有限制。
什么是WebSocket?
WebSocket 是一种基于 TCP 的全双工通信协议,它允许服务器和客户端之间建立持久连接,实现实时、双向的数据通信。
一句话总结:
WebSocket = 客户端和服务端长连接 + 双向通信 + 实时推送
WebSocket 的核心特点1)全双工通信(Full-Duplex)
客户端和服务器可以 随时相互发送数据,不用轮询。
2)单一 TCP 连接,长连接
建立一次连接后,不需要每条消息都重新建立 HTTP 连接。
3)实时性非常强
适用于消息推送、Chat、Web 实时系统。
4)降低网络和服务器压力
不需要像 HTTP 轮询那样不断发 GET 请求浪费资源。
5)支持跨域
与 HTTP 不同,WebSocket 本身不会被同源策略限制。
为什么要使用 ThreadLocal 保存用户信息?
核心原因:避免重复解析 JWT,提高性能,同时方便在整个请求生命周期中共享用户信息。
在每次请求中,我们通常要:
从请求头解析 JWT
验证签名
解析 payload,获取用户信息(userId、角色等)
如果在业务逻辑或多个拦截器里多次访问用户信息,每次都解析 JWT 会浪费 CPU。
解决方法:
在请求开始时解析一次 JWT
将用户信息缓存到
ThreadLocal在请求结束后清理
这样后续业务层或拦截器可以直接从
ThreadLocal获取,无需重复解析
ThreadLocal 是什么?有什么作用?
ThreadLocal是 Java 提供的一种线程局部变量,每个线程都拥有自己的独立副本,互不干扰。作用:
为同一线程内的多个方法或类提供共享数据
避免将数据传递给方法参数或全局变量
与请求绑定的场景下(如用户信息、TraceId)非常方便
面试可以加一句:
"在 web 场景下,
ThreadLocal很适合存储当前请求的用户信息、日志 traceId 等数据。"
使用 ThreadLocal 缓存用户信息,这样做有什么性能提升?
使用 ThreadLocal 缓存用户信息,可以让同一线程在整个请求生命周期内共享用户数据,避免重复解析 JWT,提高性能和系统吞吐量。"
性能提升来源:
减少重复解析 JWT
原本每次需要 Base64 解码 + 签名校验
解析一次后缓存到 ThreadLocal → 后续直接取用户对象
减少 CPU 消耗和对象创建
避免每次请求业务逻辑创建新的 user 对象
高并发下显著降低 CPU 和 GC 压力
提升业务层访问效率
Service、Controller、Interceptor 都可以直接访问 ThreadLocal 的用户信息
避免从请求头中反复解析 token
ThreadLocal 中缓存用户信息安全吗?
在请求线程内,ThreadLocal 是线程隔离的,每个线程都有独立副本,互不干扰,所以在单次请求生命周期中缓存用户信息是安全的。
JWT 被盗用或篡改怎么办?
JWT 本身是无状态的,安全性依赖于签名和密钥管理。
防护措施:
签名校验
JWT 解析时必须校验签名(HMAC 或 RSA)
如果签名被篡改 → token 无效,拒绝访问
HTTPS
- 避免 token 被中间人抓包
设置短期有效期 + Refresh Token
Access Token 短期有效
Refresh Token 可用于续期,并且可以存储在服务端便于撤销
黑名单机制 / 版本号机制
- 遇到用户被踢下线或 token 被盗 → 将 token 加入黑名单,或者更新版本号使旧 token 失效
如果线程复用,ThreadLocal 会带来什么问题?
核心问题:
- 在 Web 容器(Tomcat、Netty 等)中,线程池会复用线程处理多个请求。
可能出现的问题:
内存泄漏
- 如果请求结束后不清理 ThreadLocal → 数据仍然挂在复用线程上 → 长时间占用内存
数据污染
- 下一个请求复用同一线程 → 可能访问到上一个请求的用户信息 → 安全风险
多线程请求时如何保证 ThreadLocal 数据隔离?
ThreadLocal是 线程局部变量,每个线程都有独立副本多线程请求互不干扰 → 数据天然隔离
WebSocket 和 HTTP 长轮询有什么区别?
| 特性 | HTTP 长轮询 | WebSocket |
|---|---|---|
| 通信方式 | 客户端发请求,服务端延迟返回(轮询) | 双向全双工,客户端和服务端可主动推送 |
| 连接类型 | 每次请求都是新的 HTTP 连接(短连接) | 单一 TCP 长连接,持续不断 |
| 实时性 | 依赖轮询间隔,延迟高 | 实时性强,服务端可立即推送消息 |
| 网络开销 | 每次请求/响应都携带 HTTP 头,开销大 | 连接建立后数据帧小,开销低 |
| 服务器压力 | 高并发下需维护大量请求阻塞 | 高并发下只维持少量长连接即可 |
| 适用场景 | 简单实时通知,消息量低 | 聊天、实时监控、游戏、行情推送等高实时场景 |
HTTP 长轮询实现简单、兼容性好,但延迟高、开销大;WebSocket 实时性高、双向通信、节省资源,但实现复杂,需要管理长连接和安全。选择方案取决于业务需求和场景。
HTTP 长轮询
客户端发请求到服务器
服务器如果没有新数据 → 等待(阻塞或挂起)
有数据就返回 → 客户端立即再次发请求
形成"循环轮询"
优点
兼容性强
纯 HTTP 请求,不依赖特殊协议
任何浏览器或网络环境都支持
实现简单
服务端只需要支持阻塞/延迟响应
不需要维护持久连接
适合小规模、低实时需求
- 消息量少、延迟要求不高的场景
缺点
延迟高
- 依赖轮询间隔,服务端有消息也要等待请求
带宽和资源消耗大
每次请求都要带 HTTP 头 → 开销大
高并发时服务端线程可能被大量阻塞
双向通信困难
- 服务端无法主动推送消息,需要客户端继续轮询
WebSocket
初次请求用 HTTP 升级(Upgrade)协议
建立 TCP 长连接
双方都可以随时发送数据
不需要重复建立连接,消息到达立即发送
优点
实时性高
- 建立长连接后,客户端和服务端都可立即发送消息
全双工通信
- 支持双向数据流,客户端与服务器可主动通信
节省带宽和服务器资源
- 不用重复建立 HTTP 连接,消息只传最小数据帧
适合高并发、实时场景
- 聊天、游戏、股票行情、监控等
缺点
实现复杂
- 需要处理长连接的生命周期、断线重连、心跳机制
兼容性受限
- 老旧浏览器或防火墙可能屏蔽 WebSocket
服务器需要管理连接
- 高并发下需要考虑连接管理、内存和线程优化
安全性注意
长连接存在被滥用或 DoS 攻击风险
需要做好认证、权限校验和加密(wss)
阿里云OSS
阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商 。
SpringBootWeb案例-2(day11)_华东1为例 csdn-CSDN博客
阿里云对象存储服务(Object Storage Service,简称OSS)为您提供基于网络的数据 存取服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频 等在内的各种非结构化数据文件。 阿里云OSS将数据文件以对象(object)的形式上传到存储空间(bucket)中。
Nginx 反向代理
什么是反向代理?
反向代理是一种网络服务器架构,它接受客户端的请求,并将这些请求转发到内部的 多个服务器上。这样做的主要目的是实现负载均衡、提高性能、提高访问速度、增强 安全性和灵活地管理请求流量。(反向代理其代理对象是服务器,而正向代理的对象是 客户端。
反向代理的主要功能
1. 负载均衡:反向代理可以将客户端请求分发到多个内部服务器上,以平衡服务器 的负载。
2. 隐藏内部服务器: 反向代理可以隐藏后端服务器的真实 IP 地址和信息,从而增加 了服务器的安全性。
3. SSL 终端: 反向代理可以用作 SSL 终端,负责处理客户端和服务器之间的加密通信。
反向代理和正向代理的联系与区别?
- 正向代理(Forward Proxy) :代表客户端 去访问服务器,隐藏客户端身份。
- 反向代理(Reverse Proxy) :代表服务器 接收客户端请求,隐藏服务器身份
维度 正向代理 反向代理 代理对象 客户端 服务器 谁配置 客户端主动配置(如浏览器设代理) 服务端部署(用户无感知) 目的 访问被限制资源(如翻墙)、匿名访问、缓存加速 负载均衡、安全防护、SSL 终止、动静分离、高可用 对客户端透明性 不透明(需手动设置) 透明(用户以为直接访问目标服务器) 典型工具 Squid、Charles、Fiddler Nginx、Apache、HAProxy、Cloudflare IP 暴露情况 服务器看到的是代理 IP,不知道真实客户端 客户端看到的是代理 IP,不知道后端真实服务器
什么是 RESTful 风格?
RESTful 是一种基于资源(Resource)和 HTTP 语义的 API 设计风格,使用统一的 URL 和 HTTP 方法来表达对资源的操作。它不是协议,也不是标准,而是一组设计约束和原则
RESTful 的六大核心原则
- 客户端-服务器分离(Client-Server)
- 前后端解耦,各自独立演进。
- 无状态(Stateless)
- 每个请求必须包含处理所需全部信息,服务端不保存会话状态(Session 不符合严格 REST,JWT 更契合)。
- 可缓存(Cacheable)
- 响应需明确标示是否可缓存(如
Cache-Control),提升性能。- 统一接口(Uniform Interface) ← 最关键!
- 资源通过 URI 唯一标识;
- 通过标准 HTTP 方法操作资源;
- 使用标准状态码反馈结果;
- 资源以标准格式(JSON/XML)表示。
- 分层系统(Layered System)
- 客户端无需知道是否直接连最终服务器(可经过网关、代理、负载均衡等)。
- 按需代码(Code on Demand,可选)
- 服务器可临时扩展客户端功能(如返回 JavaScript),但 Web API 中很少用。