系统设计场景题
中高级面试的开放设计题,考架构思维而非标准答案。重点是展示分析过程:先问清需求/约束 → 分模块 → 定接口 → 讲权衡 → 提优化。你的底层 + 性能 + 架构知识能在这里综合发挥。
一、答题通用框架(套路)
任何“设计一个 X“都按这个结构答:
- 澄清需求:功能边界、量级(QPS/数据量)、性能/内存约束、平台。先问再答,体现工程素养。
- 整体架构:分几层/几个模块,各自职责。
- 核心模块详细设计:数据结构、关键算法、接口设计。
- 关键问题处理:并发、缓存、生命周期、异常、内存。
- 权衡与优化:为什么这么选,有哪些 trade-off,怎么扩展。
记住:面试官要看思路和沟通,不是要你写完整代码。 边画图边讲。
二、设计图片加载库(最高频,如自研 Glide)
需求澄清:支持网络/本地、缓存、列表滑动场景、防 OOM、生命周期安全。
整体架构:
请求(with/load/into) → 生命周期绑定 → 缓存查找(内存→磁盘)
→ 未命中则下载 → 解码(采样压缩) → 缓存写入 → 显示
核心设计:
- 三级缓存:内存(LruCache,弱引用存活动资源)→ 磁盘(DiskLruCache)→ 网络。
- 生命周期绑定:注入空 Fragment 监听宿主,onStop 暂停、onDestroy 取消请求(防泄漏)。
- 防 OOM:按目标 View 尺寸 inSampleSize 采样压缩;Bitmap 复用池(BitmapPool)。
- 线程模型:下载/解码在后台线程(线程池/协程),回调切主线程。
- 请求管理:列表复用时取消旧请求(ImageView tag 绑定请求)。
- 设计模式:建造者(请求构建)+ 单例(引擎)+ 观察者(回调)。
权衡:内存缓存大小(占内存 vs 命中率)、缓存淘汰策略(LRU)、采样精度 vs 质量。
三、设计网络请求框架(如自研 Retrofit + OkHttp)
- 分层:接口定义层(注解/动态代理)→ 请求封装层 → 拦截器链 → 连接/IO 层。
- 拦截器链(责任链):日志、重试、缓存、鉴权、加解密可插拔。
- 连接池:复用 TCP 连接,减少握手。
- 线程/异步:回调 / 协程 suspend / Flow。
- 可扩展:Converter(序列化)、CallAdapter(返回类型)可替换。
- 你的加分点:加解密拦截器、证书 Pinning、参数签名(结合你的安全背景)。
四、设计断点续传下载器
需求:大文件、断点续传、多线程加速、暂停/恢复、进度。
核心设计:
- 分片下载:按文件大小切成 N 段,每段用 HTTP
Range: bytes=start-end请求,多线程并发。 - 断点记录:持久化每段已下载偏移(数据库/文件),恢复时从断点续传。
- 合并:各段写入同一文件的对应偏移(
RandomAccessFile.seek)。 - 状态管理:等待/下载中/暂停/完成/失败,状态机管理。
- 完整性校验:下载后校验 MD5/SHA。
- 优化:动态调整线程数、失败重试、弱网降级、用 WorkManager 保证后台续传。
五、设计 IM / 即时通讯
需求与约束:支持单聊/群聊、保证消息不丢不重不乱序、支持弱网、省电省流。
整体架构:
- 客户端层:UI 层 → 本地存储层(SQLite) → 消息同步层 → 连接层。
- 服务端层:接入层(负载均衡/长连网关) → 逻辑层(单聊/群聊) → 存储层(MySQL存会话, Redis存状态)。
数据流与状态机 (消息生命周期):
- 发送:生成本地唯一 ClientMsgID,状态设为
发送中,存入本地数据库。 - 传输:通过长连接发送给服务端,服务端返回 ServerMsgID 确认接收。
- 确认:客户端收到 ACK 后,更新本地状态为
发送成功;若超时未收到,状态转为发送失败(显示红点)。 - 接收:接收方收到 ServerMsgID,发送 ACK 确认,然后渲染到界面并存库。
核心问题处理:
- 连接层选型:
- WebSocket / TCP:主流采用自研 TCP 长连接或 WebSocket,心跳保活(按网络状态智能调节心跳间隔,减少电量消耗)。
- 可靠性与去重 (ACK 与 Idempotency):
- 必须有双向 ACK 机制。发送端根据 ClientMsgID 去重,接收端根据 ServerMsgID 去重。
- 有序性:
- 依赖服务端的递增序列号 (Seq) 保证消息严格有序。
- 离线与同步机制:
- 采用 推拉结合:服务端推送新消息通知,客户端拿着本地最大 Seq 拉取增量消息(Sync)。
- 弱网与存储优化:
- 采用本地 Room 数据库持久化聊天记录;断网时可看历史记录,发消息仅改本地状态,恢复网络后后台重发。
六、设计短视频 / Feed 流
需求与约束:无限滑动、秒开、播放不卡顿、降低 OOM 概率与发热。
客户端架构:
- UI 控制层(RecyclerView) → 预加载策略层 → 播放器池(PlayerPool) → 本地缓存层。
核心数据流与 Paging3 集成:
- 使用
Paging3实现分页流:数据层通过PagingSource从网络或本地数据库拉取,ViewModel 将流转为PagingData喂给 UI,UI 层滑动触发下一页。
核心问题处理:
- 预加载策略 (Preload):
- 提前请求下 N 条(如 3 条)视频的元数据与首帧图片。
- 视频缓冲:提前下载当前视频后的 1-2 个视频的前 1MB 块(Moov Box 优先)。
- 播放器复用池 (Player Reuse):
- 绝不能给每个 Item 创建 MediaPlayer/ExoPlayer。全局维护 1-3 个播放器实例池,滑动切换时动态将 DataSource 挂载到目标 Surface 上,降低内存分配与重建开销。
- 缓存策略:
- 边下边播(通过本地代理如 AndroidVideoCache):未缓存部分请求网络并写入文件,已缓存部分直接读本地。
- 性能与内存隔离:
- 滑出屏幕立即停止解码、释放播放器资源到池中。
- 控制预加载深度:防止疯狂往下滑动导致内存暴增与大量废弃请求。
- 失败与降级:
- 网络波动时降级播放低码率;拉取失败时展示已缓存内容并提示网络异常。
七、其他常见设计题
- 设计本地缓存框架:LRU + 磁盘 + 过期策略 + 序列化。
- 设计埋点/APM SDK:数据采集 → 本地队列 → 批量上报 → 失败重试。和你 SDK 背景契合,可深入讲采集、性能影响最小化、上报策略。
- 设计组件化路由:APT 生成路由表 + 按路径分发 + 拦截器。
答题要点总结
- 先问后答:澄清需求和约束是第一步,直接写代码是大忌。
- 分层分模块:展示你能把复杂系统拆解。
- 讲权衡:每个选择说清 trade-off(内存 vs 速度、一致性 vs 可用性)。
- 结合你的优势:涉及性能、内存、安全、SDK 的设计题,主动用你的底层经验加分——比如设计图片库讲 Bitmap 内存复用、设计网络框架讲 Pinning 和加解密、设计埋点 SDK 讲对宿主性能影响最小化。这些是普通应用开发者讲不出的深度。
- 画图沟通:边画架构图边讲,面试官看的是思路和表达。
进阶补充:标准追问清单、状态机与可靠性设计
每道系统设计题的追问清单
回答完主方案后,主动补 5 类追问:
- 容量与性能:QPS、并发、缓存、线程池。
- 失败与重试:超时、重试、幂等、降级。
- 状态机:任务有哪些状态,如何迁移,异常如何恢复。
- 一致性:本地/服务端状态如何同步,冲突如何处理。
- 可观测性:日志、指标、trace、告警。
下载器状态机示例
Idle -> WaitingNetwork -> Downloading -> Paused -> Downloading -> Completed
-> Failed -> Retrying -> Downloading
每个状态迁移都要说明触发条件、持久化字段和失败恢复。
限流、熔断、降级、幂等
| 机制 | 解决什么 |
|---|---|
| 限流 | 防止瞬时流量打爆系统 |
| 熔断 | 下游持续失败时快速失败 |
| 降级 | 非核心能力失败时保核心路径 |
| 幂等 | 重试不会产生重复副作用 |
Android 特有追问
- 进程死亡后如何恢复任务?
- 前后台切换如何处理?
- 弱网/断网如何重试?
- 页面旋转或配置变更如何保存状态?
- 本地缓存和服务端数据冲突时谁优先?
面试表达模板
“我会先定目标和约束,再拆模块,然后补状态机和失败处理。比如这个下载器,核心不是只把文件下下来,还要保证断点续传、进程死亡恢复、重复点击幂等、弱网重试和可观测性。”
**追问:**为什么系统设计不能只画模块图?因为真实系统最容易出问题的是状态迁移、失败恢复和边界条件,这些必须在设计里说明。