Skip to content

项目 ​

最近负责的项目 ​

假如是个考勤系统:

员工每天的考勤管理,包括上下班时间、休假、加班、班次、勤怠申請和审批等。

负责项目中的哪些模块 ​

我主要负责勤务相关功能的开发和维护。

后端主要负责 Java 的业务逻辑、SQL 和数据库处理,有时候也需要修改存储过程。

前端主要使用 Vue 和 JavaScript,负责页面逻辑、接口调用、数据处理以及一些既有功能的 Bug 修复。

项目中最难的问题是什么 ​

理解既有系统的数据流和业务规则。

怎么解决的 ​

先尝试复现问题,然后根据用户操作确定问题发生在哪个环节。

如果是前端问题,我会通过浏览器的 Developer Tools 查看事件、Network、请求参数和返回值。

如果是后端问题,我会从接口开始追踪 Java 的业务逻辑,再确认 SQL 和数据库数据。

如果涉及 Oracle,我会直接执行 SQL 对比操作前后的数据,确认到底是哪一步导致了异常。

所以我的排查方式通常是:

页面 → JavaScript → API → Java → SQL → 数据结果。

项目中有没有出现过性能问题 ​

判断是前端、接口、Java 业务逻辑还是数据库的问题,然后重点检查 SQL 执行时间和查询数据量。

如果确定是数据库问题,就会进一步检查 SQL 条件、JOIN、索引以及是否存在不必要的数据查询。

减少不必要的查询字段、增加合理的查询条件、避免重复查询,以及针对查询条件检查索引是否合理。优化之后再通过实际数据对比 SQL 执行时间。

有没有遇到过慢SQL ​

我排查慢 SQL 时,一般首先确认 SQL 本身的执行时间,然后检查 WHERE 条件、JOIN、排序以及是否使用了索引。

Oracle 的话还会通过执行计划确认数据库实际采用了什么执行方式,比如是否进行了全表扫描,以及索引有没有真正被使用。

如何排查线上问题 ​

我排查线上问题一般会按照“现象 → 日志 → 请求 → 代码 → 数据库 → 定位原因 → 修复验证”的顺序进行。

首先确认问题现象,比如是接口超时、页面报错还是数据异常。

然后查看浏览器 Network 和后端日志,确认请求参数、返回结果以及异常堆栈。

如果确定是后端问题,就继续追踪 Java 业务代码和 SQL;如果涉及数据库,就检查实际数据和 SQL 执行情况。

最后确定根因后进行修复,并通过测试和日志确认问题已经解决。

如果接口突然变慢,怎么排查 ​

先确认是所有接口都变慢,还是某一个接口变慢。

如果所有接口都变慢,我会优先检查服务器资源、数据库连接池、网络等基础设施。

如果只有一个接口变慢,我会查看这个接口的日志和执行时间,然后进一步判断是 Java 业务逻辑还是 SQL 导致的。

如果发现 SQL 慢,就查看执行计划、索引和数据量。

如果 SQL 正常,再检查 Java 中是否存在循环查询、重复调用接口或者大量数据处理等问题。

如果CPU突然100%,怎么排查 ​

首先确认是不是数据库导致的,然后查看当前执行中的 SQL,找到 CPU 占用比较高的 SQL。

接下来查看这些 SQL 的执行计划,判断是否存在全表扫描、错误的 JOIN、索引失效或者一次查询处理了过多数据。

如果是异常 SQL,可以先采取限流、停止异常任务等方式降低影响,再对 SQL 本身进行优化。

怎么判断到底是前端还是后端慢? ​

通过浏览器 Network 来判断。

如果页面本身渲染很慢,但是接口已经很快返回,那么问题更可能在前端。

如果 Network 中接口的 Waiting 时间很长,那么继续查看后端日志。

后端如果整体处理时间很长,就继续判断是 Java 业务逻辑还是 SQL。

如果内存不断上涨,怎么排查 ​

先确认是 JVM 堆内存不断上涨,还是堆外内存上涨。 如果是堆内存,就通过 GC 日志、Heap Dump 等方式分析哪些对象一直没有被回收,判断是否存在内存泄漏。

然后结合代码检查是否存在静态集合持有对象、缓存没有过期、ThreadLocal 没有清理、监听器没有释放、大量对象长期被引用等问题。

最后修复后通过压测或者观察 GC 和堆内存曲线,确认内存能够正常回落。

内存持续上涨
      ↓
确认 JVM / 堆外
      ↓
查看 JVM 内存
      ↓
是否频繁 Full GC?
      ↓
生成 Heap Dump
      ↓
分析对象数量 / 占用
      ↓
查看 GC Roots
      ↓
找到是谁一直引用对象
      ↓
定位代码
      ↓
修复
      ↓
再次观察

如果数据库连接池耗尽,怎么排查 ​

先确认连接池是不是已经达到最大连接数,然后检查数据库连接是否长时间没有释放,以及哪些 SQL 执行时间比较长。

重点检查代码中是否存在连接没有关闭、事务没有及时提交、慢 SQL、连接池配置过小或者并发请求突然增加等问题。

如果使用 Spring Boot,一般还会查看连接池监控,例如 HikariCP 的 active、idle、pending 等指标。

秒杀系统如何设计 ​

秒杀系统的核心问题是高并发和库存有限。我会通过 Redis 做热点数据和库存预扣,MQ 做异步削峰,数据库做最终数据落库,同时配合限流、防重复提交和防超卖。

如何防止超卖 ​

核心是让库存扣减操作具有原子性,不能先查询库存再更新库存。

数据库方案

可以直接使用:

UPDATE product
SET stock = stock - 1
WHERE id = 100
AND stock > 0;

然后判断:

影响行数 = 1
→ 扣减成功

影响行数 = 0
→ 库存不足

这样数据库的更新操作本身就是原子的。

Redis 方案

秒杀场景通常可以使用 Redis Lua 脚本:

判断库存 > 0
      ↓
库存 - 1
      ↓
返回成功

这几个操作放在一个 Lua 脚本中原子执行。

防止超卖的关键是库存扣减必须是原子操作,可以通过数据库 UPDATE ... WHERE stock > 0,或者 Redis + Lua 实现。

如何设计一个分布式锁 ​

分布式锁主要用于多个服务器之间协调对同一资源的访问。

Redis 实现:

SET lock:product:100
    uniqueId
    NX
    EX 30

含义:

NX
→ Key不存在才能设置


EX 30
→ 30秒自动过期

Redis 分布式锁通常通过 SET NX EX 获取锁,用唯一 value 标识持有者,释放时通过 Lua 脚本保证“判断锁归属 + 删除锁”的原子性

如何实现接口幂等 ​

接口幂等:同一个请求执行一次和执行多次,最终结果应该一致。

例如:

用户点击支付
 ↓
请求发送两次

不能扣款两次

常见方案:

① 唯一业务 ID

请求携带:requestId = abc123

服务器记录:abc123 → 已处理

再次收到:

abc123
 ↓
已经处理
 ↓
直接返回

② 数据库唯一约束

例如订单号:

UNIQUE(order_no)

重复创建:

第一次 → 成功
第二次 → 唯一约束失败

③ Redis

SET request:abc123 1 NX EX 300

成功:→ 第一次请求

失败:→ 重复请求

接口幂等通常通过唯一业务 ID、数据库唯一约束或者 Redis 去重实现。

如何设计一个订单系统 ​

一个简单的订单系统可以拆成:

用户
 ↓
订单服务
 ↓
库存服务
 ↓
支付服务
 ↓
物流服务

订单状态:

待支付
 ↓
已支付
 ↓
待发货
 ↓
已发货
 ↓
已完成

整体架构:


                用户
                 ↓
              网关/Nginx
                 ↓
              订单服务
            ↙    ↓    ↘
         Redis   MQ    DB
                  ↓
          ┌───────┼───────┐
          ↓       ↓       ↓
        库存     支付     通知

创建订单

请求
 ↓
参数校验
 ↓
幂等检查
 ↓
创建订单
 ↓
扣库存
 ↓
发送消息
 ↓
返回订单

如何保证订单和库存最终一致 ​

例如:

订单创建成功
 ↓
库存扣减失败

就产生:

订单:成功
库存:没有扣

可以使用:

MQ + 本地事务 + 重试 + 幂等。

例如:

订单服务
 ↓
本地事务
 ├─ 创建订单
 └─ 保存消息记录
 ↓
提交
 ↓
MQ
 ↓
库存服务
 ↓
扣库存
 ↓
成功
 ↓
ACK

如果 MQ 或库存服务暂时失败:

失败
 ↓
重试
 ↓
再次消费
 ↓
成功

消费者必须保证幂等。

通过本地事务保证订单和消息记录的一致,再通过 MQ 异步通知库存服务,并结合重试和消费幂等,最终保证订单和库存达到一致状态。

如何设计一个高并发接口 ​

应该考虑:

用户
 ↓
Nginx
 ↓
限流
 ↓
应用集群
 ↓
Redis
 ↓
MQ
 ↓
数据库

主要措施:

  • ① 缓存
  • ② 限流
  • ③ 异步
  • ④ 削峰
  • ⑤ 数据库优化
  • ⑥ 水平扩展
  • ⑦ 连接池
  • ⑧ 避免重复查询

高并发接口通常通过缓存减少数据库压力,通过限流保护系统,通过 MQ 异步削峰,并结合应用集群和数据库优化提高整体吞吐能力。

如何实现接口限流 ​

常见算法:

  • 固定窗口
  • 滑动窗口
  • 漏桶
  • 令牌桶

实际项目中常见:

Redis + Lua / 网关限流 / 令牌桶。

如果 Redis 挂了怎么办 ​

Redis 挂掉之后,要根据 Redis 在系统中的角色决定处理方式。

如果 Redis 只是缓存:

Redis
 ↓
挂掉
 ↓
查询数据库

但是需要注意:

大量请求
 ↓
全部打数据库
 ↓
数据库可能被打垮

如果 Redis 是分布式锁

就不能简单绕过 Redis。

应该:

Redis故障
 ↓
暂时拒绝相关业务

如果 Redis 只是缓存,可以降级到数据库并配合限流;如果 Redis 承担分布式锁等核心能力,则需要高可用部署,不能直接绕过 Redis。

如果 MQ 挂了怎么办 ​

MQ 是核心链路还是异步非核心链路。

如果是非核心业务:

MQ挂了
 ↓
记录本地消息
 ↓
后台重试

MQ 挂掉时,不能让核心业务无限等待,可以通过本地消息表保存待发送消息,MQ 恢复后进行重试,同时做好告警和降级。

如果数据库挂了怎么办 ​

首先:

数据库挂了
 ↓
不能继续正常执行数据库写操作

应该:

数据库高可用
 ↓
主从 / 集群
 ↓
故障转移

应用层:

数据库异常
 ↓
快速失败
 ↓
限流
 ↓
降级
 ↓
避免请求不断重试把系统拖死

如果只是查询:

数据库挂了
 ↓
Redis有缓存
 ↓
可以返回缓存

如果是核心写操作:

数据库挂了
 ↓
不能假装成功
 ↓
返回系统繁忙

数据库故障首先依赖主从、集群等高可用方案进行故障转移;应用层需要超时、限流、降级和快速失败,避免数据库故障进一步扩散。

什么是降级 ​

当系统某个功能出现故障、超时或压力过大时,暂时关闭非核心功能,保证核心功能还能正常运行。

比如电商网站:

用户下单
 ↓
订单服务
 ↓
库存服务
 ↓
推荐服务
 ↓
优惠券服务

如果推荐服务挂了:推荐服务 ❌

不要让整个下单流程失败,而是:

推荐服务 ❌
      ↓
    降级
      ↓
不显示推荐商品
      ↓
订单正常创建

Last updated: