系统设计题 — 知识详解
更新: 5/15/2026 字数: 0 字 时长: 0 分钟
1. 系统设计方法论
面试中的系统设计题,按以下步骤回答:
- 需求分析:明确功能需求和非功能需求(性能、可靠性、扩展性)
- 模块拆分:划分核心模块,明确职责
- 接口设计:定义模块间的 API
- 数据流:数据从输入到输出的完整流转
- 异常处理:降级、重试、兜底策略
- 扩展性:如何支持未来需求变化
2. 设计一个图片加载框架
2.1 需求分析
- 从网络/本地加载图片到 ImageView
- 三级缓存(内存/磁盘/网络)
- 生命周期感知(页面销毁取消加载)
- 图片变换(圆角、裁剪、模糊)
- 占位图和错误图
- 采样压缩,避免 OOM
2.2 架构设计
ImageLoader(入口,Builder 模式配置)
├── RequestManager(生命周期管理)
├── Engine(调度核心)
│ ├── MemoryCache(LruCache)
│ ├── DiskCache(DiskLruCache)
│ ├── Fetcher(网络/本地/资源加载器)
│ └── Decoder(BitmapFactory 解码 + 采样)
├── TransformationPipeline(图片变换链)
└── Target(ImageView 绑定 + 占位图管理)2.3 核心流程
load(url).into(imageView)
→ 生成缓存 key(url + 尺寸 + 变换参数 的 hash)
→ 查内存缓存 → 命中则直接显示
→ 查磁盘缓存 → 命中则解码 + 写入内存缓存 + 显示
→ 网络请求 → 下载 → 写入磁盘缓存 → 解码 → 变换 → 写入内存缓存 → 显示2.4 关键设计点
生命周期管理:
- 注入空 Fragment(类似 Glide 的 RequestManagerFragment)监听生命周期
- onStop 暂停加载,onStart 恢复,onDestroy 取消并释放资源
线程调度:
- 网络请求和磁盘 IO 在后台线程池
- 图片解码在 CPU 密集型线程池
- 显示切回主线程
防止图片错位:
- ImageView 设置 tag 为当前请求的 url
- 加载完成后检查 tag 是否匹配,不匹配则丢弃
采样压缩:
- 先
inJustDecodeBounds=true获取原始尺寸 - 根据目标 ImageView 尺寸计算
inSampleSize - 再解码,避免加载超大图片 OOM
3. 设计一个消息推送系统
3.1 需求分析
- 服务端实时推送消息到客户端
- 消息可靠性(不丢失、不重复)
- 离线消息
- 省电省流量
3.2 架构设计
服务端
├── 推送网关(维护长连接)
├── 消息队列(Kafka/RabbitMQ)
└── 离线消息存储
客户端
├── 连接管理(长连接 + 心跳 + 重连)
├── 消息接收与 ACK
├── 本地消息存储
└── 通知展示3.3 长连接方案
- WebSocket:基于 TCP,全双工通信,适合实时推送
- 自定义 TCP 协议:更灵活,可以优化协议头大小
- MQTT:轻量级发布/订阅协议,适合 IoT 和移动端
3.4 心跳机制
客户端每 N 秒发送心跳包 → 服务端回复 ACK
连续 M 次无 ACK → 判定连接断开 → 触发重连心跳间隔自适应:
- WiFi 环境:较长间隔(如 5 分钟)
- 移动网络:较短间隔(如 2 分钟)
- NAT 超时前发送心跳保活
3.5 消息可靠性
服务端发送消息(带 msgId)
→ 客户端收到 → 发送 ACK(带 msgId)
→ 服务端收到 ACK → 标记已送达
→ 超时未收到 ACK → 重发
客户端去重:本地维护已处理的 msgId 集合3.6 离线消息
客户端上线后,拉取离线消息:
- 客户端发送最后收到的 msgId
- 服务端返回该 msgId 之后的所有消息
- 分页拉取,避免一次返回过多
4. 设计一个路由框架
4.1 需求分析
- 通过 URL/path 跳转到目标页面
- 支持参数传递
- 拦截器(登录检查、权限验证)
- 降级策略(目标页面不存在时的处理)
4.2 架构设计
Router
├── RouteTable(path → Class 映射表)
├── Interceptor Chain(拦截器链)
├── Navigator(实际跳转执行)
└── DegradeHandler(降级处理)4.3 路由表生成
编译期方案(推荐):
- 自定义注解
@Route(path = "/user/detail") - APT 扫描注解,为每个模块生成路由表
- 应用启动时加载所有模块的路由表
运行时方案:
- 手动注册:
Router.register("/user/detail", UserDetailActivity::class) - 缺点:容易遗漏,不够自动化
4.4 拦截器
kotlin
interface RouteInterceptor {
fun intercept(chain: RouteChain): RouteResult
}
class LoginInterceptor : RouteInterceptor {
override fun intercept(chain: RouteChain): RouteResult {
if (needLogin(chain.route) && !isLoggedIn()) {
return RouteResult.Redirect("/login") // 重定向到登录页
}
return chain.proceed() // 继续
}
}4.5 降级策略
- 目标页面未注册 → 打开 H5 兜底页
- 目标页面崩溃 → 打开错误页
- 服务端下发路由配置,动态控制跳转目标
5. 设计一个埋点系统
5.1 需求分析
- 采集用户行为数据(页面浏览、点击、曝光)
- 高性能,不影响主线程
- 数据可靠,不丢失
- 支持实时和批量上报
5.2 架构设计
采集层
├── 页面埋点(自动化:Lifecycle 监听)
├── 点击埋点(AOP / 注解)
├── 曝光埋点(RecyclerView 可见性检测)
└── 自定义埋点(手动调用)
处理层
├── 数据格式化(统一 schema)
├── 数据聚合(合并相同事件)
└── 本地缓存(SQLite / 文件)
上报层
├── 实时上报(关键事件立即发送)
├── 批量上报(定时 + 条数阈值)
└── 降级策略(网络异常时本地缓存,恢复后补报)5.3 关键设计点
不影响主线程:
- 采集在主线程(获取 UI 信息),但只做最小操作
- 格式化、缓存、上报在后台线程
数据可靠性:
- 先写本地缓存,上报成功后删除
- 应用崩溃时数据不丢失(已写入本地)
- 上报失败自动重试
曝光埋点:
- 监听 RecyclerView 的滚动事件
- 计算 item 可见面积比例(>50% 且停留 >500ms 算有效曝光)
- 去重:同一个 item 在同一页面只上报一次
6. 设计一个日志系统
6.1 架构
Logger API(统一接口)
├── 日志分级(VERBOSE/DEBUG/INFO/WARN/ERROR)
├── 日志格式化(时间/线程/TAG/内容)
├── 输出策略
│ ├── Console(Logcat,Debug 模式)
│ ├── File(本地文件,Release 模式)
│ └── Remote(上报服务端,ERROR 级别)
└── 文件管理
├── 按天/大小分文件
├── mmap 写入(高性能)
├── 压缩(gzip)
└── 定期清理(保留 7 天)6.2 高性能写入
使用 mmap 内存映射写入日志文件:
- 写入内存即完成,OS 异步刷盘
- 即使应用崩溃,已写入的数据不会丢失(OS 会刷盘)
- 比 FileOutputStream 快 10 倍以上
美团 Logan、微信 xlog 都采用 mmap 方案。
7. 设计一个网络层框架
7.1 架构
NetworkClient(统一入口)
├── 拦截器链
│ ├── 日志拦截器
│ ├── 认证拦截器(Token 管理 + 自动刷新)
│ ├── 缓存拦截器
│ ├── 重试拦截器(指数退避)
│ └── 监控拦截器(耗时/成功率/错误码统计)
├── 请求队列(优先级调度)
├── 连接管理(连接池复用)
└── 数据解析(JSON/Protobuf)7.2 Token 自动刷新
kotlin
class AuthInterceptor(private val tokenManager: TokenManager) : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${tokenManager.accessToken}")
.build()
val response = chain.proceed(request)
if (response.code == 401) {
synchronized(this) {
// 双重检查:可能其他线程已经刷新了
val newToken = tokenManager.refreshToken()
val newRequest = request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
return chain.proceed(newRequest)
}
}
return response
}
}7.3 重试策略
指数退避:第 1 次重试等 1 秒,第 2 次等 2 秒,第 3 次等 4 秒...加上随机抖动避免惊群效应。
只对幂等请求重试(GET),非幂等请求(POST)需要业务层决定。
