项目技术点总结--四大核心技术+缓存

内容;Seata AT、Sentinel、QLExpress、DockerCompose 四大核心技术 + Redis 底层、Caffeine+Redis 二级缓存、缓存三大问题

Seata AT 分布式事务

AT 模式三阶段完整执行流程

一阶段(执行业务本地事务)

执行业务 SQL(新增 AI 短信记录、扣商户额度),同步生成 undo 回滚日志存入库,提交本地 MySQL 事务,给对应业务行加锁;此时数据入库,但全局事务未结束,外部其他服务看不到本次修改。

二阶段提交

TC 事务协调器通知分支提交,服务删除本地 undo 日志,释放行锁,数据对外完全可见。

二阶段回滚

TC 通知分支回滚,读取 undo 日志生成反向补偿 SQL,撤销一阶段所有增删改数据,释放行锁,恢复到执行前状态。

项目落地业务场景

批量 AI 短信生成、定时营销任务需要同时操作三张数据:短信生成记录表、商户额度表、Redis 缓存额度。

若不加 Seata AT,会出现:短信记录插入成功、商户额度扣减 SQL 报错,导致商户免费下发短信,数据不一致。

添加@GlobalTransactional全局事务注解,保证多库操作原子性,要么全成功、要么全撤销。

AT / TCC / SAGA 三种分布式事务对比

方案 代码侵入度 锁机制 适用场景 项目选用原因
AT 极低,仅注解 行锁,同步锁 MySQL 同步数据库操作 短信平台以 MySQL 为主,开发成本低
TCC 极高,需手写 Try/Confirm/Cancel 无数据库锁,业务自定义 对接第三方支付、非关系型存储 项目几乎不用,开发工作量大
SAGA 中等,手动写补偿逻辑 无锁,最终一致性 超长异步流程、跨异构系统 批量异步任务极少使用,一致性弱

线上高频失效踩坑点

  1. 方法不是 public、同类内部调用全局事务失效;
  2. 多数据源未配置 Seata 代理数据源;
  3. MQ 异步、子线程无法传递全局事务上下文;
  4. undo 日志长期堆积,需定时清理 undo 表。

项目排查和问题处理

1.使用 Seata AT 分布式事务,出现「短信记录入库成功,商户额度扣减失败」数据不一致,请写出完整排查思路。

第一步:检查业务方法上是否添加 @GlobalTransactional 全局事务注解,无注解则完全不生效;

第二步:校验当前事务方法访问修饰符,必须是 public;如果是 private /protected/ 同类内部自调用,Spring AOP 无法生成代理,全局事务直接失效;

第三步:查看日志,定位扣减额度 SQL 抛出的异常;确认异常是否被 try-catch 捕获且没有主动抛出,被吞掉的异常不会触发 Seata 回滚;

第四步:核对项目多数据源配置,是否全部使用 Seata 代理数据源,未代理的库操作不参与全局事务;

第五步:查询 undo_log 回滚日志表,确认一阶段是否正常生成回滚记录;

第六步:排查是否存在异步 MQ、新开子线程执行扣减逻辑,子线程无法传递全局事务上下文,事务隔离;

第七步:观察 TC 事务协调器日志,查看二阶段是提交指令还是回滚指令,定位异常分支。

2.简述 Seata AT 一阶段、二阶段提交、二阶段回滚分别做了哪些核心操作?

一阶段

执行业务增删改 SQL,同步生成 undo 日志(里面存反向补偿 SQL),提交本地 MySQL 事务,同时对操作的数据加行锁,此时外部看不到本次修改的数据。

二阶段提交

TC 事务协调器收集所有 RM 分支执行状态,全部无异常,则通知各分支删除本地 undo 日志、释放行锁,修改后的数据对外可见。

二阶段回滚

只要任意分支执行异常,TC 下发回滚指令;RM 读取 undo 日志里的反向 SQL 执行数据恢复,完成后释放行锁,undo 日志会做归档清理。

3.结合咱们营销短信平台业务,说一说项目为什么选择 Seata AT,而不用 TCC、SAGA?

我们平台核心业务都是 MySQL 同步落库场景,所以优先选用 Seata AT:

  1. AT 侵入极低 ,仅需在方法上加@GlobalTransactional注解,框架自动生成 undo 日志实现事务控制,几乎不用改动原有业务代码,开发维护成本低;
  2. TCC 侵入性极强,需要手动编写 Try、Confirm、Cancel 三段逻辑,代码改动量大,适合对接第三方支付、非数据库异构场景,我们项目没有这类需求;
  3. SAGA 无锁、依靠手动补偿,适合超长异步业务流程,一致性偏弱;我们短信下发、AI 文案生成都是短同步事务,追求强一致,完全不匹配 SAGA 适用场景。

Sentinel 限流熔断

熔断三大状态流转

  1. 关闭 Closed:接口正常运行,持续统计 QPS、超时率、异常比例;指标未超阈值,请求直接放行。
  2. 打开 Open:异常 / 超时占比超过阈值,熔断器开启,直接拒绝所有请求,执行降级逻辑,防止雪崩拖垮服务。
  3. 半开 Half-Open:冷却窗口期结束,放行少量探测流量;探测请求正常→切回关闭;探测仍报错→重回打开。

三种限流算法对比

算法 特点 项目使用场景
滑动窗口 精准统计固定时间窗口流量,粒度细 Sentinel 单机限流、Redis Lua 集群限流(主力)
漏桶 流出速率固定,削峰,无法应对突发流量 MQ 消费者批量消费控并发
令牌桶 可积累令牌,允许瞬时突发高并发 大促活动临时扩容流量场景

项目落地场景

  1. 网关层:后台/admin/**接口全局粗粒度限流,防止运营并发压垮服务;
  2. AI 大模型、第三方短信通道接口:配置熔断,设计三级降级;
  3. 验证码接口:手机号、IP 防刷限流;
  4. 配置持久化:限流熔断规则统一存 Nacos,支持热更新,控制台仅用来监控,不存储生产规则。

AI 接口三级降级策略

1 级降级:切换备用大模型;

2 级降级:读取 MySQL 预制短信模板;

3 级降级:返回通用 "系统繁忙" 兜底提示。

集群限流兜底方案

Redis Lua 分布式滑动窗口实现全集群统一限流;若 Redis 宕机,自动切换 Sentinel 本地内存限流兜底,避免裸跑无防护。

项目排查和问题处理

1.项目对接第三方 AI 大模型频繁超时,如何使用 Sentinel 做服务保护?

首先我们会给 AI 生成接口单独定义 Sentinel 资源标识,持续统计接口的响应超时率、异常比例;

  1. 限流管控:配置接口整体 QPS 限流,也支持热点参数限流,限制单商户、单 IP 的调用频次,避免单个客户疯狂调用占用资源;限流阈值统一放在 Nacos 可动态调整;
  2. 熔断保护:当大模型接口超时、报错占比超过设定阈值,熔断器直接打开,不再持续调用不稳定的第三方 LLM,避免大量超时请求堆积拖垮我们自身服务;
  3. 三级降级兜底:熔断触发后依次执行降级策略,优先切换备用大模型;备用模型不可用就读取 MySQL 预制合规短信模板;两种方案都失效则返回 "系统繁忙,请稍后重试" 友好提示;
  4. 故障兜底:分布式限流依赖 Redis Lua,一旦 Redis 宕机,自动切换 Sentinel 本地内存限流,保证接口始终有防护,不会裸跑。

2.简述滑动窗口、漏桶、令牌桶三种限流算法的特点,以及项目各自使用场景。

  • 滑动窗口 特点:将时间切分为多个小段滚动统计流量,统计粒度细,能精准识别短时间突发流量,避免临界流量误放行; 项目场景:Sentinel 单机本地限流、Redis Lua 集群统一限流,平台主流限流方案。

  • 漏桶算法 特点:流出速率恒定,无论请求涌入多快,都以固定速度处理,强制削峰,无法应对瞬时突发流量; 项目场景:RabbitMQ 消费者批量拉取消息,控制批量 AI 生成任务的消费速度,防止一次性压垮向量库与大模型。

  • 令牌桶算法 特点:系统匀速生成令牌存入桶内,空闲时段令牌可累积,突发高并发时能一次性取出存量令牌,支持瞬时流量峰值; 项目场景:营销大促活动,允许短时间爆发式请求,适配活动流量突增场景。

3.我们项目 Redis 宕机后,限流不会完全失效,依靠什么兜底方案?逻辑是什么?

项目采用双层限流兜底架构:正常情况下通过 Redis Lua 滑动窗口实现集群统一限流,保证多实例流量管控标准一致;一旦 Redis 宕机、无法访问,系统自动降级切换到 Sentinel 本地内存限流,单机独立统计接口流量,避免服务完全裸跑无防护,保障接口基础限流能力不丢失。

QLExpress 规则引擎

硬编码 if/else 人群筛选的痛点

如果客户分群、营销筛选逻辑写死在代码里,新增、修改筛选条件必须改代码、打包发版、重启服务,迭代周期长,运营无法自主配置规则,开发工作量大。

QLExpress 核心能力

  1. 可视化页面拖拽配置表达式,运营不用写代码就能自定义人群筛选条件;
  2. 内置安全沙箱,拦截危险执行语法,杜绝表达式注入安全风险;
  3. 表达式本地缓存,重复执行不用重复解析,提升批量圈选人群性能;
  4. 支持逻辑判断、数值区间、多条件组合,完全匹配客户分层业务。

项目落地场景

批量定时营销任务中,运营在后台配置人群筛选表达式,执行定时推送时,通过 QLExpress 动态解析规则,批量圈选出符合条件的目标客户,无需开发介入发版迭代。

项目排查和问题处理

1.项目为什么不用 if-else 硬编码做人群筛选,选择 QLExpress?

业务里营销人群筛选规则变动十分频繁,如果用 if-else 硬编码实现,每次新增、修改筛选条件都需要开发改代码、打包、发版、重启服务,迭代周期长,占用大量开发人力。 引入 QLExpress 后解决了这些痛点:

  1. 后台提供可视化拖拽页面,运营可以自主配置筛选表达式,不用依赖开发、无需发版就能调整人群规则;
  2. 内置安全沙箱,限制危险语法执行,防止表达式注入带来安全漏洞;
  3. 表达式做本地缓存,批量圈选人群时不用重复解析,提升批量筛选性能;
  4. 支持多条件逻辑、区间判断等各类筛选语法,完全适配我们客户分层、营销圈人业务。

2.QLExpress 安全沙箱有什么作用,对应项目什么安全风险?

QLExpress 内置安全沙箱机制,会拦截危险、非法语法,通过黑白名单管控可执行方法与操作;

我们项目运营自主编写筛选表达式,如果没有沙箱限制,恶意表达式可能执行文件读写、反射等高危操作,造成表达式注入漏洞,威胁服务安全;

沙箱能隔离这类风险,只开放业务允许的逻辑判断、数值计算语法,从源头避免恶意表达式攻击。

DockerCompose 容器编排

核心作用

依靠一份 yml 配置文件,一条命令就能一键拉起项目全套中间件:MySQL、Redis、RabbitMQ、PgVector、ES;自动完成端口映射、容器网络创建、数据持久化挂载。

落地业务价值

  1. 统一团队所有人本地开发环境,规避中间件版本不一致引发的各种诡异 bug;
  2. 新人上手成本极低,无需手动下载安装、配置各类中间件,拉取项目后直接启动 compose 即可;
  3. 区分环境隔离,开发、测试环境可通过不同 compose 配置快速切换。

Docker / DockerCompose / K8s 三者定位区分表格

工具 核心定位 使用场景
Docker 容器引擎,打包单个应用镜像 打包业务服务、单独启动单个中间件
DockerCompose 单机多容器编排工具 本地开发、小型测试环境,单机管理一组容器
K8s 分布式集群容器编排平台 线上生产大规模集群服务部署、扩缩容、自愈

项目排查和问题处理

1.本地开发环境,为什么不直接在 Windows 系统手动安装 MySQL、Redis,而是选用 DockerCompose?

如果团队成员在 Windows 本地手动安装 MySQL、Redis 等中间件,很容易出现中间件版本、配置参数不统一的问题,经常出现本地运行正常,提交代码到测试环境就报错的环境差异 bug。 使用 DockerCompose 有几点核心优势:

  1. 统一约束所有中间件版本与配置,团队所有人本地环境完全一致,规避环境差异类 bug;
  2. 新人上手简单,不用手动下载、安装、配置各类中间件,一条命令就能启动整套依赖;
  3. 支持多套 yml 配置,快速隔离开发、测试两套环境,切换方便;
  4. 自动完成端口映射、数据持久化、容器网络配置,省去大量手动配置工作。

2.简述 DockerCompose 核心作用,以及它的配置文件是什么?

DockerCompose 是单机多容器编排工具,核心作用:通过一份统一配置文件,一键批量启动、管理项目所需全部中间件容器,自动处理网络、端口、数据持久化,统一团队开发环境。 配置文件固定名称为 docker-compose.yml

Redis 全套底层 + Caffeine+Redis 二级缓存

Redisson 分布式锁核心原理

  1. 底层通过 Lua 脚本实现加锁、解锁,保证操作原子性;
  2. 支持可重入锁:记录持有锁的线程标识,同一线程多次加锁不会死锁;
  3. 看门狗自动续期:长任务后台自动延长锁过期时间,防止任务没执行完锁提前释放;
  4. RedLock 红锁机制:过半 Redis 节点加锁成功才算拿到锁,解决主从切换锁丢失问题,项目用于 Quartz 集群定时任务防重复执行。

原生 SETNX 锁四大缺陷:不可重入、无自动续期、解锁非原子操作、主从切换锁丢失,项目全部用 Redisson 替代原生方案。

Lua 脚本原子性项目落地场景

  1. Redis Lua 滑动窗口多维度限流(手机号、商户 ID、IP);
  2. RedLock 分布式锁加解锁逻辑;
  3. 批量更新商户额度缓存,多条缓存操作保持原子执行。

Redis 在项目中的使用场景

分布式锁、分布式限流、JWT 黑名单、布隆过滤器、二级分布式缓存、接口幂等校验、计数器。

缓存三大问题(穿透 / 击穿 / 雪崩)完整方案

缓存穿透

现象:查询数据库不存在的数据,缓存无记录,请求全部打穿到 MySQL,压垮数据库 业务场景:恶意请求不存在的商户 ID、无效手机号 解决方案:前置布隆过滤器拦截无效 key;空值短期缓存;接口参数合法性校验

缓存击穿

现象:热点 key 过期瞬间,大量并发请求同时查询数据库 业务场景:高热度商户额度配置高频查询 解决方案:热点 key 永不过期;分布式锁控制单线程查库回填缓存

缓存雪崩

现象:大量 key 同一时刻过期;Redis 整体宕机,流量全部涌向 MySQL 解决方案:过期时间随机偏移;Caffeine+Redis 多级缓存;接口限流熔断;Redis 集群高可用

Caffeine+Redis 二级缓存完整查询链路

查询顺序:优先读取 Caffeine 本地缓存(进程内存,无网络 IO,速度最快)→未命中查询 Redis 分布式缓存→两级都未命中才查询 MySQL;查询成功双向回填两级缓存。 优势:

  1. Caffeine 拦截大部分热点请求,大幅减少 Redis 网络请求;
  2. Redis 保证多服务实例之间缓存数据一致;
  3. 双重兜底,Redis 故障时本地缓存仍可提供基础数据访问。

生活化举例

Redisson 分布式锁

把锁想象成卫生间钥匙:

SETNX 是一次性钥匙,进去之后没法再开门,中途超时钥匙自动消失;

Redisson 可重入锁支持同一个人反复进出,看门狗相当于定时续时,防止洗澡没结束门锁自动打开;

RedLock 相当于多间卫生间,超过一半房间拿到钥匙才算占用,避免主卫生间故障导致锁失效。

缓存三大问题

  • 穿透:不停查不存在的物品,仓库没有就全都去翻数据库库房,库房被挤爆;布隆过滤器相当于门卫,不存在的物品直接拦在门外。
  • 击穿:爆款商品标签刚好过期,所有人同一时间冲去库房;给标签永久保存,或者只放一个人去库房更新。
  • 雪崩:所有商品标签同时过期,瞬间所有人冲进库房;每个商品过期时间错开,同时办公桌存一份备份。

项目排查和问题处理

1.原生 SETNX 实现分布式锁存在哪些缺陷,项目为什么选用 Redisson?

原生 SETNX 分布式锁四大缺陷:

  1. 不可重入:同一个线程多次加锁会直接死锁,不支持嵌套调用;
  2. 无自动续期:任务执行时长超过锁过期时间,锁会提前释放,并发错乱;
  3. 解锁非原子性:先判断持有者再删除锁,并发场景下极易误删别人的锁;
  4. 主从切换锁丢失:主节点拿到锁还未同步给从节点就宕机,新主节点不存在该锁,出现并发安全问题。

Redisson 全部补齐以上痛点:自带可重入锁、看门狗自动续期、Lua 脚本保证加解锁原子性、提供 RedLock 红锁规避主从锁丢失风险,开发开箱即用,稳定性更强,因此项目统一使用 Redisson。

2.分别说明缓存穿透、缓存击穿、缓存雪崩三者区别,以及各自对应的解决办法。

1. 缓存穿透

现象:请求一直查询 Redis、MySQL 都不存在的数据,缓存永远无法留存数据,大量请求持续直达数据库,压垮 MySQL;大多是恶意刷无效 ID、手机号造成。

解决方案

  1. 前置布隆过滤器,提前拦截不存在的 key;
  2. 查询为空时,在 Redis 写入短期空值缓存;
  3. 接口前置参数校验,过滤非法参数。
2. 缓存击穿

现象:单个超高热度缓存 key 到期一瞬间,海量并发请求同时绕过缓存涌向数据库。

解决方案

  1. 热点 key 设置永不过期;
  2. 使用分布式锁,仅放行一个请求去数据库查询并回填缓存,其余请求等待缓存刷新完成。
3. 缓存雪崩

现象分两种 ① 大批量缓存 key 集中同一时间过期,流量瞬间全部打入数据库; ② Redis 集群整体宕机,缓存彻底失效,请求全部访问 MySQL。

解决方案

  1. 给过期时间加上随机偏移值,错开批量过期时间;
  2. 搭建 Caffeine+Redis 二级缓存,Redis 故障时本地缓存兜底;
  3. Redis 做主从集群保证高可用;
  4. 搭配 Sentinel 限流熔断,限制打库流量。

三者核心区分: 穿透:查不存在的数据;击穿:单个热点 key 过期 ;雪崩:大批 key 集体过期 / Redis 整体瘫痪

3.项目引入 Caffeine+Redis 二级缓存有什么好处?

1.访问速度大幅提升

Caffeine 属于进程内本地内存缓存,无网络 IO 开销,绝大多数热点请求可直接命中本机缓存,响应速度远快于 Redis,大幅降低接口耗时。

2.削减 Redis 压力与网络开销

高频热点数据常驻本地缓存,不必频繁和 Redis 交互,有效减轻 Redis 集群负载,节省内网网络传输损耗。

3.多重防护,抵御缓存异常场景
  • 缓解缓存雪崩:大量 key 集中过期或 Redis 宕机时,本地 Caffeine 依旧可以兜底提供数据访问;
  • 缓解缓存击穿:热点数据留存本地,规避热点 key 过期瞬间流量涌入数据库; 搭配限流策略一起,给数据库多层防护。
4.兼顾分布式数据一致

本地 Caffeine 负责高速访问,Redis 统一存储集群共享数据,服务实例间数据以 Redis 为准,兼顾性能与分布式一致性。

JVM 调优 + OOM 问题排查

JVM 内存区域划分

1.堆 Heap:存放所有对象实例,分新生代(Eden、S0、S1)+ 老年代;GC 主要回收区域,OOM 最常发生在这里。

2.虚拟机栈:每个线程私有,存放局部变量、方法调用栈帧;栈太深会栈溢出 StackOverflowError。

3.本地方法栈:支撑 native 本地方法调用。

4.程序计数器:记录线程执行字节码位置,唯一一个无 OOM 的内存区域。

5.元空间 Metaspace:存放类信息、常量、静态变量、字节码;JDK8 取消永久代,改用元空间,直接使用操作系统内存。

新生代 GC 流程(复制算法)

  1. 新对象优先分配在 Eden 区;
  2. Eden 满了触发 Minor GC,存活对象复制到空闲 Survivor 区;
  3. 两个 Survivor 来回交换存放存活对象;
  4. 对象熬过 15 次 Minor GC 晋升到老年代; 大对象直接进入老年代,避免新生代来回复制开销。

常见 OOM 四种场景 + 诱因

1.Java heap space 堆内存溢出

最普遍:集合无限添加对象未释放、大文件一次性读取加载、缓存无上限堆积。

2.Metaspace 元空间溢出

频繁动态创建类、动态代理大量生成 class、热部署频繁重载类。

3.栈溢出 StackOverflowError

递归深度过大、方法循环调用无终止条件。

4.直接内存 Direct Buffer 溢出

Netty、NIO 大量使用堆外内存,未主动释放。

线上 OOM 排查标准步骤

  1. 发生 OOM 时开启 -XX:+HeapDumpOnOutOfMemoryError 自动导出堆快照 dump 文件;
  2. 使用 MAT 内存分析工具导入 dump 文件;
  3. 查看内存占用最大对象、引用链,定位代码里未释放的集合 / 缓存 / 大资源;
  4. 修复代码逻辑、调整 JVM 参数、限制缓存容量。

项目 JVM 基础参数配置示例

  • Xms:初始堆内存;Xmx:最大堆内存;两者设一致,避免运行时堆扩容消耗性能
  • Xmn:新生代大小
  • SurvivorRatio:Eden 和 Survivor 比例
  • MetaspaceSize:元空间初始大小

生活化举例

JVM 内存好比办公仓库:

堆内存 = 开放式大储物区,绝大部分货物(对象)都放这里,堆满就要清理垃圾(GC);

Eden 区 = 临时置物桌,新东西先放桌面,满了收拾一遍,常用物品搬到储物架(Survivor);

老年代 = 固定储物柜,长期不用但还要保留的物品放这里;

虚拟机栈 = 每个人手边笔记本,记录当下工作步骤,写太多本子装不下就栈溢出;

元空间 = 产品说明书档案柜,存放各类产品图纸(类字节码)。

OOM 就是仓库彻底塞满再也放不下货物,必须清理无用货品、扩容仓库或者改掉疯狂囤货的代码。

项目排查和问题处理

1.JDK8 为什么废弃永久代,改用元空间 Metaspace?永久代存在哪些弊端?

永久代(PermGen)弊端
  1. 永久代属于 JVM 堆内存的一部分,内存上限固定,很难预估所需大小:设置过小容易频繁 Full GC 甚至溢出;设置过大又造成内存闲置浪费;
  2. 永久代内存回收复杂,类卸载条件严苛,热部署、动态代理、频繁加载类的场景极易出现永久代溢出;
  3. 不同 JDK 版本永久代大小参数不一致,兼容性差。
JDK8 替换为元空间 Metaspace 的原因
  1. 元空间直接使用本地操作系统内存,不再占用 JVM 堆内存,理论上限是物理内存,大幅降低溢出概率;
  2. 元空间会动态伸缩内存,无需人为精准预估容量,运维更省心;
  3. 优化了类加载与卸载机制,Spring 热部署、动态创建类、反射代理等场景更稳定;
  4. 统一了不同平台内存管理逻辑,跨系统兼容性更好。

2.线上出现 java.lang.OutOfMemoryError: Java heap space 堆内存溢出,完整排查流程是什么?

1.提前配置 JVM 启动参数

项目启动时预先加上参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/dump 一旦触发堆 OOM,JVM 会自动生成堆快照 dump 文件,留存现场内存数据。

2.下载 dump 文件,使用 MAT 工具解析

将 dump 文件拉取至本地,借助 Eclipse MAT 内存分析工具打开快照。

3.定位内存泄漏根源

  • 查看内存占用排行最高的对象;
  • 追踪对象引用链,找出长期持有对象却未释放的代码位置:常见为无限扩容的 List/Map 集合、无上限缓存、一次性读取超大文件、大对象常驻内存等。

4。落地优化方案

  • 代码层面:清理无用集合、读取文件采用流式分批加载、给本地缓存设置最大容量与淘汰策略;
  • JVM 参数层面:合理调整堆初始值 Xms、最大值 Xmx,新生代配比;
  • 架构层面:超大批量数据拆分处理,避免一次性加载至内存。

5.上线验证 优化后观察 GC 日志、堆内存走势、FullGC 频次,确认 OOM 不再复现。

3.简述 Minor GC 和 Full GC 的触发时机以及二者区别

一、触发时机

1.Minor GC(新生代 GC)

Eden 区内存被新对象占满时自动触发,只回收新生代(Eden + 两块 Survivor)。

2.Full GC

常见触发场景:

① 老年代空间不足,放不下晋升过来的对象;

② 主动调用System.gc()

③ 元空间内存耗尽;

④ 空间分配担保失败;

会回收整个堆:新生代 + 老年代,部分垃圾回收器还会顺带回收元空间。

二、核心区别

1.回收范围不同

Minor GC:仅新生代; Full GC:整堆(新生代 + 老年代)。

2.耗时与影响差距极大

Minor GC:频次高、速度快,停顿很短,对系统性能影响微弱;

Full GC:开销巨大、STW 停顿时间很长,频繁 Full GC 会严重拖慢接口响应,线上要极力避免。

3.对象流转逻辑不一样

Minor GC:存活对象复制到另一块 Survivor 区;熬过多次 Minor GC 后满足年龄阈值,晋升到老年代;

Full GC:全盘扫描存活对象,压缩整理老年代空闲空间,清理全部无效对象。

4.频次

Minor GC 日常频繁发生;Full GC 应当极少出现。

RabbitMQ 消息队列

MQ 三大核心作用

1.系统解耦

短信推送、日志记录、营销通知等下游业务剥离出去,主业务下单 / 提交请求完成即可返回,不用串行等待多个附属服务执行;上下游互不影响,改动互不干涉。

2.流量削峰填谷

大促、定时批量营销时段瞬间海量请求涌入,MQ 缓冲瞬时流量,消费端匀速拉取消息处理,避免瞬间流量压垮数据库。

3.异步提速

耗时业务转为异步执行,缩短主接口响应时长,提升用户体验。

消息可靠性保障(防止消息丢失)

消息丢失四个节点,逐一处理:

  • 生产者发送阶段丢失:开启生产者确认机制(publisher-confirm-type),消息投递到 Broker 收到回执才算发送成功,失败重试;
  • 交换机转发队列丢失:设置备份交换机;
  • Broker 服务器丢失:镜像队列、集群部署,消息多副本持久化磁盘;
  • 消费端丢失:关闭自动 ACK,业务处理完成后手动 ACK;处理失败不确认,消息重回队列。

消息重复消费问题(幂等性解决方案)

网络卡顿、ACK 超时会导致消息重复投递,必须做幂等:

  1. 每条消息携带唯一业务 ID;
  2. 消费前先查询数据库 / Redis 判断该 ID 是否已处理;
  3. 已处理直接跳过,未处理执行业务逻辑,执行完成标记已消费。

死信队列 DLX

消息满足以下条件会进入死信队列,不会一直重试阻塞正常队列:

  1. 消息被多次消费均失败;
  2. 消息过期;
  3. 队列达到最大长度被丢弃;

用处:存放异常消息,人工排查错误原因、重新补发,避免无效消息无限循环占用资源。

项目落地场景

批量营销短信异步推送、定时任务分发、日志异步存储、订单后续附属流程解耦。

项目排查和问题处理

1.项目使用 RabbitMQ 最核心的三个作用分别是什么?

RabbitMQ 三大核心用途:

  1. 系统解耦 把短信推送、消息通知这类附属业务从主业务剥离,主服务和下游消费服务互不依赖,一方修改、故障不会直接牵连另一方。
  2. 流量削峰填谷 大促、批量发短信等高并发瞬间流量先存入 MQ 缓冲,消费端平稳匀速处理,避免瞬时海量请求压垮数据库与业务服务。
  3. 异步提升响应速度 耗时的后置流程异步化处理,主接口完成核心逻辑就直接返回结果,大幅缩短接口耗时,优化使用体验。

2.RabbitMQ 消息丢失一共分为四个环节,分别是哪些?各自如何保障消息不丢失?

RabbitMQ 消息丢失四大环节及对应保障方案:

1.生产者投递阶段丢失

开启生产者确认机制(Confirm),Broker 成功接收消息后返回确认回执;未收到回执则触发重试投递,保证消息可靠送出。

2.交换机转发至队列时丢失

配置备份交换机,路由失败的消息会转发至备份交换机对应的队列,避免消息路由异常直接丢弃。

3.Broker 服务端消息丢失

开启消息、队列持久化,消息写入磁盘;搭建 RabbitMQ 集群 + 镜像队列,消息存储多副本,节点宕机数据不会丢失。

4.消费端消息丢失

关闭自动 ACK,改为手动 ACK 模式;业务逻辑完整处理完毕后,再手动发送确认指令;业务异常处理失败则拒绝 ACK,消息重新退回原队列等待再次消费。

3.说说幂等性在 RabbitMQ 里具体怎么落地实现?

1.生成全局唯一消息标识

每条消息封装唯一业务 ID(订单号、短信批次号、uuid 均可),生产者发送时把该 ID 一并塞入消息体。

2.消费前置校验

消费者拿到消息后,先去 Redis / 数据库查询这条消息 ID 是否已经处理完成:

  • 若已存在记录:判定为重复消息,直接丢弃,不再执行业务逻辑;
  • 若无记录:继续往下处理业务。

3.业务执行成功后做标记

业务逻辑完整执行完毕,写入标识:

  • 方案 1:Redis 存入该消息 ID,并设置过期时间;
  • 方案 2:数据库新建消息消费记录表,写入主键 id + 处理状态。

业务失败不打标

处理异常时不记录消费状态,消息重回队列重试,不会被误判为已消费。

补充两种常用方案
  1. 数据库唯一索引 针对入库类业务,给业务流水字段加唯一索引,重复消息插入直接报错,天然拦截重复数据;
  2. 状态机控制 依靠业务单据状态(待处理、已完成)判断,已完结单据不再二次处理。

校验逻辑放在消费端而非发送端:重复消费是 ACK 超时、网络抖动导致 MQ 重复推送消息引发的问题,生产者一般只会发送一次。

SkyWalking + ELK 链路追踪与日志监控

SkyWalking 核心定位

面向微服务分布式架构的 APM(应用性能监控)工具 核心三大能力:

1.分布式调用链路追踪

一次前端请求依次经过:网关 → 短信服务 → 营销服务 → 缓存 → MQ → 数据库

SkyWalking 会生成一条完整链路:统一 TraceId 串联全流程,每个服务分段生成 SpanId;

链路页面能看清:每一段耗时、调用顺序、哪个服务卡顿、哪一步抛出异常。

2.服务性能指标监控

自动采集各项数据:接口平均响应时间、QPS 吞吐量、错误率、JVM 运行状态、线程池占用、数据库耗时;

绘制曲线图,直观看到高峰期性能下滑、接口抖动。

3.告警推送

接口错误率飙升、响应超时、服务离线、JVM 内存占用过高,可配置邮件 / 消息告警,提早发现线上故障。

关键术语通俗解释

1.TraceId

一次完整请求全局唯一编号,整条链路所有服务共用同一个 TraceId,凭借它就能检索整条调用记录。

2.Span

链路里的每一小段调用:比如网关调用后端服务、服务查询 MySQL、发送 MQ 都单独算作一个 Span,会记录耗时、状态、异常信息。

3.采样率

线上请求量巨大,不会采集所有链路,配置采样比例(比如 10%)减轻服务与存储压力;排查问题时可临时调高采样。

SkyWalking Java Agent 核心优势

全程零业务代码修改,只需要在服务启动命令添加-javaagent探针参数即可接入监控;探针依附 JVM 运行,自动拦截接口调用、数据库访问、MQ 收发、Feign 远程调用等行为,自动采集链路耗时、异常信息,开发无需埋点编码。

探针分为两种采集模式:

  1. 字节码增强(默认):运行期修改字节码,无侵入,主流方案;
  2. SDK 埋点:手动编码埋点,极少场景才会使用。

ELK 是什么?和 SkyWalking 分工区别

ELK 由三款组件构成:Elasticsearch + Logstash + Kibana

作用:统一收集、存储、检索、可视化系统日志

  • 日志:代码打印的 info、error、异常堆栈、参数详情
  • 场景:报错后想看具体报错堆栈、入参、上下文日志,用 ELK

Logstash(日志采集管道)

负责日志收集、过滤、清洗、转换

  • 从各个微服务、服务器抓取零散日志;
  • 剔除无用空格、杂乱字符、拆分日志字段、过滤无效日志;
  • 处理完毕后,统一推送至 Elasticsearch。

补充:现在很多场景会用 Filebeat 替代 Logstash 做轻量采集,占用资源更低。

lasticsearch ES(日志存储 + 检索引擎)

日志最终存放仓库,核心能力:

  • 海量日志持久化存储;
  • 支持全文快速检索、按 TraceId、时间、报错关键词精准查询日志;
  • 日志分片分布式存储,量大也不会卡顿。

Kibana(可视化展示面板)

负责前端可视化:

  • 绘制日志量曲线图、报错统计饼图;
  • 提供检索页面,输入关键词、TraceId 就能查询日志;
  • 配置日志告警、仪表盘,直观查看系统日志健康状态。

三者流转链路

各个服务散落日志 → Logstash 采集清洗 → Elasticsearch 存储索引 → Kibana 查看检索

二者分工(重点区分)

工具 核心侧重点 适用场景
SkyWalking 调用链路、性能耗时、服务调用关系、慢接口定位 查接口为什么慢、哪个服务拖后腿、服务间调用报错
ELK 原始日志、异常堆栈、请求参数详情 定位具体代码报错、查看入参出参、详细错误日志

二者搭配使用流程:

  1. 发现接口卡顿 / 报错 → SkyWalking 查链路,锁定出问题的服务节点;
  2. 拿到该链路 TraceId → 去 ELK 根据 TraceId 检索整条链路的详细日志,查看具体异常堆栈、参数,定位代码 bug。

项目部署简单流程

  1. 服务启动挂载 SkyWalking Agent 探针(javaagent),无代码侵入,自动采集链路数据;
  2. 探针将数据上报 SkyWalking OAP 服务;
  3. UI 面板可视化展示调用链、性能报表;
  4. 日志统一采集推送至 ELK,链路与日志通过 TraceId 关联打通。

适用场景(短信营销项目举例)

批量发送短信接口缓慢:

  1. SkyWalking 查看链路:网关耗时很短,卡在 RabbitMQ 消息投递 + 数据库写入环节;
  2. ELK 根据 TraceId 检索日志:发现数据库索引缺失,批量插入耗时太久;
  3. 优化索引之后,响应速度明显下降。
相关推荐
大飞记Python4 小时前
AI大模型Token计费全解析:输入/输出、缓存命中、阶梯计价一文看懂
人工智能·缓存
Rain的Java大神之路5 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
天疆说9 小时前
04 稳定性与性能调优实录:OOM 崩溃、并发槽位、前缀缓存与 DSpark 调参
linux·运维·缓存
csdn2015_9 小时前
springboot +mybatis 查询myql开启程序级别缓存
spring boot·缓存·mybatis
隔窗听雨眠9 小时前
缓存增强生成CAG:用预加载KV缓存突破RAG实时检索瓶颈
缓存
光影少年11 小时前
react离线缓存、图片缓存方案
开发语言·前端·javascript·react native·react.js·缓存·前端框架
m0_3807438721 小时前
给大模型调用加一层本地缓存,让重复请求直接命中磁盘
缓存
FakeOccupational1 天前
【电路笔记 STM32】Cortex-M7 内核上的数据缓存(D-Cache)结构+MPU+DMA&Cache+STM32CubeMX配置
笔记·stm32·缓存
l1t1 天前
DeepSeek总结的在 pg_stat_statements 中诊断高基数工作负载
数据库·缓存·postgresql