移动端系统设计题库
章节边界:本章是逐题口述练习卡, 不重复移动端系统设计方法与完整图片示范或业务算法实现. 所有数字均为练习假设, 答题时必须根据题干, 埋点和设备测量替换, 不能背成固定参数.
学习目标与使用法
每题用 8 分钟准备, 5 分钟口述: 先补题干缺失条件, 再讲接口, 状态 / 时序, 一个算式, 失败恢复和验收. 预期证据是一页设计草图及一条可证伪的指标定义.
题目练习卡
统一异步契约: Async<T> 由调用方持有的 operation/handle 所有, 最终且恰好交付一次, 通过 Completed(T), Failed(error) 或 Cancelled 交付; cancel() 由该 owner 发起, 终止本地等待和对已离开 owner 的回调, 不承诺撤销已送达服务端的副作用. 表中 Async<T> 的 T 仅表示成功值, 错误均走 Failed(error), 取消均走 Cancelled. 各题示例可能混用 callback 与 Async<T> 两种示意: callback 贴近传统 Java/OkHttp 习惯, Async<T> 是统一抽象; 面试时能讲清「底层 callback -> 上层统一异步契约」的映射即可, 不必强求所有题目同一种签名.
| 题目 | 关键接口与条件 | 状态 / 时序 | 示例估算 (均为假设) | 失败恢复与本题练习 |
|---|---|---|---|---|
| 1 图片加载 | bind(target, ImageRequest, callback): RequestHandle, handle 含 requestId/cancel(); callback 收 ImageResult.Success/Failure/Cancelled, wrapper 每次绑定生成 ID, 回调仅匹配当前 target, GET 可有限重试 (本卡为 callback 示意, 等价映射为 Async<ImageResult>) | bind -> cache -> fetch/decode -> ready/failed/cancelled; 复用先 cancel/detach 旧 handle | 360×240×4=345,600 B/图; 12 张约 3.96 MiB 像素数据; 尺寸来自布局测量, 预算按低端机峰值内存, 命中率和首图 P95 调整 | 切网, 半缓存, 错图, OOM; 固定测试集窗口内错图率 错 cacheKey 显示数 / 有结果显示的绑定数 = 0. 完整示范见 59. |
| 2 埋点 SDK | track(name, attrs, occurredAt): Accepted(queueId) / Rejected(reason) 仅入持久队列; flush(reason): Async<Uploaded(ackId)>, 429/磁盘/协议错误走 Failed(error), 事件 ID 去重, ACK 后删除 | event -> validate/redact -> dedup/sample -> durable queue -> batch/ack | 2,000 event/s × 500 B = 1,000,000 B/s, 约 0.95 MiB/s 原始写入; 采样率, 批量和压缩由丢失率, 队列水位, 耗电和网络实测调整 | 磁盘满, 429, schema 冲突, 进程死; 关键事件与普通事件按可接受丢失率分级. 算法见 55. |
| 3 断点下载 | enqueue(url, uri, etag): Task(id); pause/resume(id): Async<TaskState>; 成功值为 Completed(uri, checksum), RangeUnsupported/ETagChanged/Storage/Network 均走 Failed(error); 同一 taskId 的 resume 幂等 | PENDING->RUNNING->PAUSED/RETRYING->VERIFYING->DONE | 200 MiB / 4 MiB = 50 块; 块大小, 并发由吞吐, 随机写, 服务端连接限制, 电量测量调整 | ETag 变化, 磁盘满, URI 失效, 进程死; 画 Range, checkpoint, 原子 rename 时序. |
| 4 IM 同步 | send(clientMsgId, body): Async<Accepted(serverMsgId, sequence)>, sync(afterCursor): Async<Page(messages, nextCursor)>, ack(id,type): Async<Acknowledged>; 错误走 Failed(error), clientMsgId 幂等, serverMsgId 去重 | send -> durable log -> accepted -> deliver -> local transaction -> ack; 缺口补拉 | 假设 100 万 active users × 40 条/日 = 4,000 万 消息 / 日; 平均写入 4,000 万 / 86,400 ≈ 463 QPS, 峰值 463 × 15 ≈ 6,945 QPS; active users 和峰值系数由活跃埋点 / 峰值窗口调整 | ACK 丢失, 乱序, 重连, 重复; sequence 发现缺口. 完整时序见 59. |
| 5 离线缓存 | get(key, policy): Value/Freshness/Miss; mutate(opId, baseVersion): Async<Applied(version)>; sync(cursor): Async<Page(nextCursor)>; 冲突 / 网络走 Failed(error), opId 幂等 | read local -> stale/fresh -> refresh; write pending -> sync -> applied/conflict | 10,000 × 2 KiB = 20,000 KiB, 约 19.5 MiB 实体; 索引 / 操作日志预留比例由数据库页, 离线时长和写放大实测调整 | 过期, 冲突, 重复提交, 迁移失败; 为一种冲突定义权威端和人工介入点. |
| 6 Crash / 日志 SDK | recordBreadcrumb(): Stored/Dropped; capture(exception): reportId 只做最小安全写; upload(reportId): Async<Uploaded>, 重复 ACK 仍为 Completed(Uploaded), 其余上传错误走 Failed(error), reportId 幂等 | crash -> minimal safe write -> next launch validate/dedupe -> upload | 环形 200 × 300 B = 60,000 B, 约 58.6 KiB; 条数 / 字段以崩溃路径耗时, 磁盘和诊断完整度测量调整 | 损坏文件, 重复上报, mapping 不匹配; 写出 buildId 与符号化核验. |
| 7 权限治理 | request(capability, rationale): Async<PermissionDecision>, 成功值为 Granted/Denied/PermanentDenied/Unavailable; resolve(): PermissionState 从持久状态和系统状态重建; 重复调用同一未决 request 不重复弹窗 | feature intent -> rationale -> system result -> granted/denied/permanent-denied | 授权漏斗需最少 曝光数,发起数,系统弹窗数,授权数, 授权率为 授权数 / 系统弹窗数; 状态规模示例为 12 capabilities × 1 current-state record/capability/host × 64 B/record × 3 hosts × 2 retained policy versions = 4,608 B, 约 4.5 KiB; 记录大小按实际序列化和保留策略测量调整 | 旋转, 进程死, 永久拒绝, 策略变更; 持久化 capability × state × policyVersion, 并画拒绝后的降级旅程. |
| 8 Router | navigate(route,args): Async<Navigated(destination)>; resolve(route): Resolved/NotFound, 参数, 鉴权或未注册均走 Failed(errorCode), 解析只读幂等, 鉴权中断可重试 | parse -> validate -> intercept -> resolve -> navigate/fallback | 500 × 200 B = 100,000 B, 约 97.7 KiB 元数据; 生成表与运行时注册按启动耗时, 包体和扩展频率测量 | 未注册, 参数错, 登录取消, 旧链接; 给出可观测错误码与安全白名单. |
| 9 短视频 Feed | feed(cursor): Async<Page(items,nextCursor)>; play(item): Async<FirstFrame>; preload(item,bytes): RequestHandle 可取消, 播放 / 解码错误走 Failed(error), 重复 cursor 请求由服务端游标语义去重 | page -> local DB -> visible item owns player -> preload/cancel -> release | 1.5 Mbps × 3 s / 8 = 0.5625 MB, 约 0.54 MiB/条; 预取深度由废弃流量, 首帧 P95, 网络类型和缓存命中测量调整 | decoder 失败, 切后台, 网络降级, 滚动跳过; 画 Surface/player owner 迁移. Media3 细节见 51. |
| 10 登录刷新 | authorized(request): Async<Response>; refresh(refreshToken): Async<NewTokens>; logout(reason): Async<LoggedOut>; AuthRequired/InvalidGrant/Retryable 走 Failed(error), 同一 token epoch 的 401 合并为 single-flight, waiter 可取消 | 401 -> single-flight refreshing -> waiters retry/logout; token 更新原子化 | 峰值 100 个并发请求同时 401, 经合并应为 1 次 refresh, 最多 99 个 waiter; 合并窗口由 401 聚集度, 刷新 P95 和服务端限额调整 | refresh 失效, 响应丢失, 多进程竞态; 写出 waiters 取消和清理用户数据. 协议见 37. |
| 11 设备指纹 SDK | collect(consentedSignals): Signals/ConsentMissing; evaluate(timeout): Async<Decision>, Unknown 是成功降级值, 超时 / 网络走 Failed(error); 请求带 nonce, 服务端结果按 requestId 去重但 Unknown 不重试放大 | consent -> collect -> normalize -> signed request/cache -> result/unknown | 30 × 100 B = 3,000 B, 约 2.93 KiB 原始字段上限; 字段数由授权率, 字段缺失率, 误报成本和传输开销调整 | 缺字段, 超时, 篡改, 误报; 设计 unknown 降级, 不能宣称绝对识别. |
| 12 配置 / 实验 | fetch(etag): Async<FetchResult>, 成功值为 NotModified/Config(version); 网络 / 签名错误走 Failed(error); evaluate(flag, subject): Variant/Default; rollback(version): Async<Activated>, 同版本 activate 幂等 | bundled default -> cached -> async fetch -> validate -> activate/versioned rollback | 200 × 1 KiB = 200 KiB 配置; 是否启动同步取决于关键 flag 覆盖率, TTID 和缓存命中测量 | 签名失败, 过期, 分桶漂移, 服务不可用; 给出稳定 hash 与默认安全值. |
题目 11 高频追问 (设备指纹采集 SDK)
- 指纹熵 / 独特性评估: 如何度量采集字段的区分度与稳定性? 常用做法是算归一化熵与两两碰撞率, 并给出唯一设备识别比例; 单纯叠加字段会稀释信号质量.
- 字段漂移稳定性: App / OS 升级后字段名或取值变化会让同一设备指纹漂移, 需要定义字段版本号与归一化映射, 并用升级前后同一设备的匹配率验证.
- 反模拟器 / 群控 / 改机对抗: 模拟器与真机在传感器, 系统属性, 图形栈上存在差异, 采集侧可做特征校验与一致性检查; 服务端结合设备信任分数与行为信号, 不能只信单一字段.
Unknown降级的误报代价量化:Unknown是低置信度成功值而非失败, 要按业务口径估算放行后漏报与拦截后误报的代价, 用混淆 / 代价矩阵决定阈值, 不能宣称绝对识别.
追加题目: 设计一个风控决策系统 / 反欺诈引擎 (题目骨架)
- 需求: 客户端采集信号 (设备指纹, 行为序列, 位置, 环境特征) 上报, 服务端综合规则 / 风险评分 / 模型给出
Allow / Challenge / Deny; 决策按 requestId 去重且可审计. - 关键 trade-off: 误报 vs 漏报成本 (拦截一个真实用户 vs 放过一笔欺诈, 用代价矩阵定阈值); 决策降级 (规则引擎或模型超时 / 不可用时按兜底阈值放行或转入人工审核).
- 验收: 命中率 / 误报率 / 决策延迟 P95, 灰度分层上线, 事后复盘闭环 (申诉, 标签回流, 模型迭代).
统一答题检查
每题最后补: 指标定义 (分母, 窗口, 分位数), 故障注入矩阵, 灰度层级, 暂停阈值, 配置 / 协议版本和回滚 owner. 参数若没有设备, 网络, 服务端和内容数据支撑, 应明确回答 “先以此假设压测, 再按命中率, P95, 资源峰值调整”.
自测
任选两题, 限时 10 分钟各写一张卡. 合格证据: 接口有输入 / 输出边界; 状态图含至少一个异常分支; 算式带单位; 恢复方案覆盖进程死亡或响应丢失; 没有把示例数值说成生产事实.