21.后端场景题Part1
# 01.场景题Part1
# 1、订单取消时刚好付款
一笔订单,在取消的那一刻用户刚好付款了,怎么办?
- 分布式锁,若已支付,则拒绝取消
- 若订单取消和支付同时发生,则优先处理取消逻辑,支付操作触发退款
- 若支付成功,订单状态需回滚为
已支付
,取消逻辑自动失效
# 2、避免重复下单
- 如何避免用户重复下单(多次下单未支付,占用库存)
- 给每次下单请求生成唯一标识(如
UUID
),确保同一请求只被提交一次 - 当用户发起下单请求时,检查 Redis 是否存在未完成的订单
SET user:order:<user_id>:<product_id> true NX EX 300
# 3、Redis 机器爆了
线上发现 Redis 机器爆了,如何优化?
**原因1:**缓存数据超出内存容量,Redis 触发 OOM
- 将
数据分片
(Sharding)到多个 Redis 实例,分担单节点内存压力 - 调整
淘汰策略
,使用volatile-lru
,确保热点数据优先保留
- 将
**原因2:**大量慢查询导致 CPU 处理过载,突发流量导致网络带宽耗尽
- 开启慢查询日志,优化耗时较长的命令(如
ZUNIONSTORE
或KEYS
) - 主节点处理写请求,从节点分担读流量
- 开启慢查询日志,优化耗时较长的命令(如
**原因3:**某些 key 的访问频率过高,导致单节点资源耗尽
- 使用一致性哈希将热点数据分布到多个节点
- 将热点数据缓存在客户端
- 在 Redis 节点启动时预加载热点数据,避免高并发初始加载
# 4、导出excel慢
项目上有个导出 excel 场景发现很慢,怎么优化?
原因1:
数据库查询速度慢?例如:未加索引、查询过多冗余数据等- 优化SQL查询,并结合redis缓存
原因2:
数据处理逻辑复杂?例如:内存占用过高、循环操作导致性能问题- 将 Excel 导出改为异步任务,用户提交导出请求后,后台生成文件,完成后通知用户下载
# 5、百万数据导入数据库
项目上需要导入一个几百万数据 excel 文件到数据库中,有哪些注意点?
- 将 Excel 数据按
1,000 行为一批解析,逐步加载到内存
- 在解析过程中
检查数据格式
,并将不合法的数据写入日志,生成错误报告 - 每 1,000 行使用
LOAD DATA INFILE
提交到数据库,保证写入效率 - 用户提交导入任务后,通过
消息队列将任务下发至后台服务
,完成后通知用户 记录导入进度及错误数据行号,支持任务失败时重新导入
# 6、1000亿数据插入HashMap
如果没有内存限制,如何快速、安全地将 1000 亿条数据插入到 HashMap 中?
去重
- 将数据分割成1000份,1000个线程并发对数据hash并对1000取余,均分到1000个map中
直接初始化一个足够大的容量,避免多次扩容
myMap := make(map[int]string, 100_000_000_000)
数据上可以选择使用 concurrent-map(concurrent-map 将一个
map
分为多个小map
(分片),每个分片有自己的读写锁)- 将这1000个map并发写入到 concurrent-map 对象中
# 7、数据库连接池爆满
线上数据库连接池爆满问题排查
- 查看数据库的慢查询日志:是否存在大量慢查询
- 是否存在非预期的大量查询、复杂查询或死锁
- 检查代码逻辑,是否有连接未关闭(如使用了连接池但忘记释放
conn.close()
) - 是否因异常流量或特定用户行为导致(某些 IP 突增请求)
- 查看 CPU、内存、磁盘 I/O 是否是瓶颈
- 最大连接数(
max_connections
)是否过小?是否适应当前的流量? - 最大连接数(
max_connections
)是否过小?是否适应当前的流量?
# 8、消息队列故障兜底
线上消息队列故障,兜底改造方案
- 短期内,通过 Redis 缓存消息,批量重试写入 RabbitMQ,同时暂停非核心业务流量,缓解系统压力
- 长期来看,我们升级 RabbitMQ 到镜像队列模式,确保节点高可用
- 同时增强消费者的幂等机制,避免重复消费
- 还增加了监控告警,防止类似问题复发
# 9、CPU 飙高排查
- 通过
top
定位高 CPU 的进程,并使用pprof
采集 CPU Profiling 数据 - 分析 Profiling 火焰图发现某个 Goroutine 在执行死循环,且未能正确退出
- 检查代码,发现问题出在任务分片处理逻辑中,循环条件遗漏了退出判断
# 10、Redis 内存溢出 排查
排查过程
- 使用
info memory
确认内存使用超出maxmemory
限制 - 使用
--bigkeys
和memory stats
命令分析大 Key,发现一个 List 类型 Key 超过 500MB - 检查过期策略,发现部分 Key 未设置 TTL,导致长期堆积
- 检查碎片率,
fragmentation_ratio
超过 1.5,表明存在内存碎片
解决方案:
- 删除超大 Key,并对集合数据进行分片存储
- 调整淘汰策略为
allkeys-lru
,确保超内存时自动淘汰低频 Key - 批量为无 TTL 的 Key 设置合理过期时间
- 扩展 Redis 实例内存至 4GB,并计划迁移到 Redis Cluster
预防措施:
- 引入 Prometheus + Grafana 监控 Redis 内存使用情况
- 定期清理冷数据,并设定内存使用报警阈值
- 在代码层面优化数据结构和存储策略,避免大 Key 和过量 Key 问题
# 11、select 百万行 内存会飙升么
- 当我们执行
SELECT * FROM
一个有 1000 万行的表时,MySQL 并不会直接将所有数据加载到内存,而是使用流式处理机制逐行读取结果 - 然而,以下两种情况可能导致内存飙升
- 客户端一次性缓存了整个结果集
- 查询中包含复杂操作(如排序、聚合)导致临时表溢出
- 优化措施
- 避免
SELECT *
,只查询必要字段 - 通过
LIMIT
和OFFSET
分批加载数据 - 为查询添加索引,减少扫描行数
- 在客户端使用流式读取逐行处理结果
- 避免
# 12、10亿个数据中找出最大的10000个
如果含较多重复值,先用
hash / 依图法去重
,可大大节省运算量使用Hash方法将数据划分成n个partition,每个partition交给一个线程处理
线程的处理逻辑可以采用最小堆,最后一个线程将结果归并
进一步优化:每个线程的处理速度可能不同,快的线程需要等待慢的线程
- 将数据划分成c×n个partition(c>1),每个线程处理完当前partition后主动取下一个partition继续处理
# 13、几亿日志 搜索热度最高十个
- 拆分成n多个文件,通过哈希函数
hash(query) % N
将词分成 N 组 - 对于每个文件,使用
map字典进行词频统计
- 小顶堆排序,依次处理每个文件,并逐渐更新最大的十个词
# 14、推送已读和未读消息
消息内容表 (
messages
),存储每条站内信的基本信息(消息 ID、内容、发送时间等)消息偏移表 (
message_offset
),维护每个用户的消息读取进度(即最后一次已读的消息 ID)用户获取未读消息时,只需从
messages
表中查询message_id > last_read_message_id
的记录更新
message_offset
表,将last_read_message_id
更新为用户已读的最新消息 ID
# 15、一个文件快速下发到100w个服务器
- 推荐方案:P2P + 树形分发
初始分发:
- 文件分片,通过中心服务器向少数(如 1000 台)一级种子服务器分发
树形扩散:
- 一级种子服务器继续分发文件到二级种子(如 10,000 台)
- 各节点只需分发文件到下一层,形成分发树,逐步扩散到 100 万台服务器
P2P 加速:
- 二级种子服务器形成 P2P 网络,服务器间共享文件分片
- 各节点完成文件后可作为新种子,提高分发速度
优化细节
- 使用 文件哈希值(如 MD5 或 SHA-256) 校验,确保文件传输无误
- 限制单节点的并发连接数和传输速率,避免带宽过载
- 对丢包或未完成的服务器任务,定期重试
- 允许部分节点通过中心服务器重新获取文件