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: