每日面试题之 如何设计一个支持 10GB 以上文件上传的系统?

0 阅读17分钟

每日面试题之 如何设计一个支持 10GB 以上文件上传的系统?

这是一道系统设计里非常高频的场景题。
真正拉开差距的,不是会不会说“分片上传”,而是能不能讲清楚:服务端如何掌握上传状态、如何做 ETag 校验、如何保证 complete 幂等、如何清理僵尸分片,以及为什么生产上更推荐直传 OSS / S3。


面试对话

第一回合:初步方案

面试官: 请设计一个大文件上传系统,需要支持 10GB 以上的文件上传。你会如何设计?

候选人: 我考虑用分片上传的方式,把大文件切成多个小片段,客户端逐个上传,全部完成后再通知服务端合并。

面试官: 好,那具体说说流程。

候选人:

  1. 前端选择文件后,先计算 MD5
  2. 询问服务端是否已存在(秒传)
  3. 如果不存在,把文件切成 5MB 一个分片
  4. 并发上传这些分片
  5. 前端确认全部上传完成后,通知服务端合并
  6. 服务端把分片按顺序合并成完整文件

💡 ** 图 1:大文件上传的基础流程**

flowchart TD
    select_file([用户选择文件]) --> calc_md5[计算文件 MD5]
    calc_md5 --> check_exist{文件已存在?}

    check_exist -->|是| instant_upload([秒传成功])
    check_exist -->|否| query_chunks[查询已上传分片]
    query_chunks --> split_file[文件分片<br/>每片 2~5MB]
    split_file --> upload_chunk[并发上传分片]
    upload_chunk --> check_all{全部分片上传完成?}
    check_all -->|否| retry_chunk[重试失败分片]
    retry_chunk --> upload_chunk
    check_all -->|是| complete_upload[请求合并分片]
    complete_upload --> upload_success([上传完成])

    calc_md5 -.->|携带 MD5 查询| server_check[服务端查询文件记录]
    server_check -->|存在| return_exist[返回文件 URL]
    return_exist -.-> instant_upload
    server_check -->|不存在| get_uploaded[返回已上传分片列表]
    get_uploaded -.-> query_chunks

    classDef terminal fill:#DDF5E7,stroke:#1F2937,stroke-width:2px
    classDef decision fill:#FFE9B8,stroke:#1F2937,stroke-width:2px
    classDef service fill:#DBEAFE,stroke:#1F2937,stroke-width:2px
    class instant_upload,upload_success terminal
    class check_exist,check_all decision
    class calc_md5,query_chunks,split_file,upload_chunk,complete_upload,server_check,return_exist,get_uploaded,retry_chunk service

这张图先展示“最直观”的分片上传思路,下一轮再看它为什么会踩坑。


第二回合:追问触发合并的风险 ⚠️

面试官: 你说第 5 步"前端确认完成后通知服务端合并",这里有什么问题吗?

候选人: (思考)... 呃,如果前端通知的时候实际还有分片没传完?

面试官: 对,还有呢?

候选人: 哦!如果恶意客户端伪造请求,假装传完了让服务端合并?

面试官: 正是。如果完全信任前端,会有什么风险?

候选人:

  • 攻击者可以伪造 complete 请求,让服务端执行无效的合并,浪费资源
  • 或者前端有 bug,漏传了几个分片,服务端合并后文件是损坏的
  • 还有网络重试导致多次 complete 请求,可能并发合并同一个文件

面试官: 很好。那你觉得应该怎么设计?


候选人: (画图说明)

💡 ** 图 2:传统方案 vs S3 思路的信任边界**

flowchart TB
    subgraph A[传统方案:信任前端]
        A1[前端生成 UploadId] --> A2[前端自己记录分片状态]
        A2 --> A3[前端发起 complete]
        A3 --> A4[服务端直接合并]
        A4 --> A5[风险:伪造请求 / 漏传分片 / 重复合并]
    end

    subgraph B[S3 思路:服务端主导]
        B1[服务端生成 UploadId] --> B2[服务端记录已上传分片]
        B2 --> B3[客户端 complete 时提交 Parts 列表]
        B3 --> B4[服务端校验数量 + ETag + 状态]
        B4 --> B5[校验通过后才执行合并]
        B5 --> B6[收益:防伪造 / 可幂等 / 可审计]
    end

    A5 --> X[核心差异:谁掌握“上传是否真的完成”]
    B6 --> X

    classDef legacy fill:#FEE2E2,stroke:#991B1B,stroke-width:2px
    classDef s3 fill:#DDF5E7,stroke:#166534,stroke-width:2px
    classDef note fill:#DBEAFE,stroke:#1F2937,stroke-width:2px
    class A1,A2,A3,A4,A5 legacy
    class B1,B2,B3,B4,B5,B6 s3
    class X note

传统方案完全信任前端,存在伪造请求、漏传分片、无法幂等的风险;S3 思路把“是否真的上传完成”的判断权收回到服务端。


第三回合:引入 S3 协议思想

候选人: 我觉得应该参考 AWS S3 的 Multipart Upload 协议,让服务端掌握主动权,不完全信任前端。

面试官: 具体说说 S3 怎么做的。

候选人: S3 的核心设计是:

  1. 服务端生成 UploadId - 不是前端随便开始的
    • 前端先 POST /initiate,服务端生成唯一 UploadId 返回
    • 这个 UploadId 标识整个上传会话
  2. 每片上传后返回 ETag - 服务端记录所有分片信息
    • 前端 PUT /part/{partNumber} 上传单个分片
    • 服务端保存分片后,返回该分片的 ETag(通常是 MD5)
    • 服务端用 Redis 记录:这个 UploadId 已经上传了哪些分片
  3. Complete 时强制校验 - 服务端自主检查,不依赖前端
    • 前端 POST /complete 时,必须带上所有分片的 PartNumber 和 ETag 列表
    • 服务端检查:
      • 数量对不对(是否漏传)
      • 每个 ETag 是否匹配(分片是否被篡改)
    • 只有校验通过,才执行合并

面试官: 这个设计和你的第一版比,核心区别是什么?

候选人:

维度第一版(信任前端)S3 设计(服务端主导)
UploadId前端随便生成服务端生成并控制
分片记录前端自己记服务端用 Redis 持久化记录
Complete 校验前端说传完了就信服务端校验数量和 ETag
防伪造UploadId 会话机制 + ETag 校验
幂等性难保证可重试,重复 complete 返回相同结果

第四回合:深入 S3 协议细节

面试官: 你刚才提到 ETag,能详细说说 ETag 在 S3 中的作用吗?

候选人: S3 中 ETag 有两个层面的作用:

单分片层面:

  • 每个分片上传后,服务端计算该分片的 MD5,作为 ETag 返回
  • 客户端要保存这个 ETag,最后 complete 时带回来

最终文件层面:

  • 整个文件合并后的 ETag 不是简单 MD5
  • 而是:拼接所有分片的二进制 MD5 → 再算 MD5 → 最后加上 -N(N 是分片数)
  • 比如:"d41d8cd98f00b204e9800998ecf8427e-3"

面试官: 为什么要这么复杂的 ETag?

候选人:

  • 验证完整性:最终 ETag 包含了所有分片的信息,可以验证文件在传输和合并过程中没被篡改
  • 快速比对:秒传时只需要对比最终 ETag,不用比对每个分片
  • 版本识别:不同分片数量合并的文件,ETag 不同

面试官: Complete 的时候,如果前端传的 ETag 列表和服务端记录的不匹配,怎么处理?

候选人: 应该返回错误,让客户端重新上传不匹配的分片。

面试官: 对。那如果前端不传 ETag 列表,只传个 complete 请求呢?

候选人: (愣)... 那服务端应该拒绝?

面试官: 是的。S3 的 CompleteMultipartUpload 必须要求完整的 Parts 列表,PartNumber 和 ETag 缺一不可。

候选人: (画图说明整体架构)

💡 ** 图 3:分片上传系统的分层架构**

flowchart TB
    subgraph L1[客户端层]
        C1[Web / App / SDK]
        C2[分片切分]
        C3[并发上传 / 断点续传]
    end

    subgraph L2[接入层]
        G1[API Gateway / Nginx]
        G2[鉴权 / 限流 / STS / 预签名]
    end

    subgraph L3[服务层]
        S1[Upload Service]
        S2[Metadata Service]
        S3[Callback Service]
    end

    subgraph L4[存储层]
        R[(Redis)]
        M[(MySQL)]
        O[(OSS / S3 / COS)]
    end

    subgraph L5[处理层]
        P1[Merge Worker]
        P2[转码 / 校验 / 异步通知]
    end

    C1 --> C2 --> C3 --> G1
    G1 --> G2 --> S1
    S1 --> S2
    S1 --> R
    S2 --> M
    S1 -. 返回 UploadId / STS / 预签名 .-> C1
    C3 -. 直传分片 .-> O
    O -. 上传完成回调 .-> S3
    S3 --> M
    O --> P1
    P1 --> P2
    P1 --> O

    classDef layer fill:#F8FAFC,stroke:#D1D5DB,stroke-width:1.5px
    classDef storage fill:#DBEAFE,stroke:#1F2937,stroke-width:2px
    classDef core fill:#DDF5E7,stroke:#166534,stroke-width:2px
    class C1,C2,C3,G1,G2,S2,S3,P1,P2 layer
    class S1 core
    class R,M,O storage

客户端层负责分片计算和并发上传,接入层限流鉴权,服务层管理状态和鉴权,存储层使用 Redis、OSS 和 MySQL,处理层异步执行合并和视频处理。


第五回合:并发与竞态条件

面试官: 假设有两个客户端,同时用同一个 UploadId 上传 Part 1,会发生什么?

候选人: 会以最后一次上传为准。S3 的设计是:同一个 PartNumber 可以重复上传,后面的覆盖前面的。

面试官: 那如果客户端 A 上传 Part 1,客户端 B 上传 Part 2,然后它们同时发 complete 请求呢?

候选人: 这就涉及竞态条件了...

面试官: 怎么解决?

候选人:

  1. 分布式锁 - Complete 操作加锁,同一个 UploadId 只能有一个在执行
  2. 幂等设计 - Complete 成功后,结果缓存起来,重复的 complete 直接返回成功结果
  3. 状态机 - UploadId 有状态:INITIATED → COMPLETING → COMPLETED,避免重复进入合并流程

面试官: 很好。S3 实际是怎么保证原子性的?

候选人: S3 内部应该是:

  • Complete 操作是原子的,要么全部 Part 合并成功创建最终对象,要么什么都不做
  • 不会出现部分合并的中间状态
  • 如果合并过程中服务端挂了,客户端可以重试 complete,因为是幂等的

候选人: (画图说明状态流转)

💡 ** 图 4:上传状态机与幂等控制**

flowchart LR
    INIT[INITIATED] -->|开始上传| UP[UPLOADING]
    UP -->|分片齐全且校验通过| READY[READY_TO_MERGE]
    READY -->|发起 Complete| MERGING[MERGING]
    MERGING -->|合并成功| DONE[COMPLETED]
    MERGING -->|合并失败| FAIL[FAILED]
    FAIL -->|重试 Complete| READY

    INIT -->|Abort / TTL 到期| ABORT[ABORTED]
    UP -->|Abort / TTL 到期| ABORT
    READY -->|Abort / TTL 到期| ABORT
    DONE --> IDEM[重复 Complete 直接返回相同结果]

    classDef normal fill:#DBEAFE,stroke:#1F2937,stroke-width:2px
    classDef success fill:#DDF5E7,stroke:#166534,stroke-width:2px
    classDef fail fill:#FEE2E2,stroke:#991B1B,stroke-width:2px
    classDef note fill:#FEF3C7,stroke:#92400E,stroke-width:2px
    class INIT,UP,READY,MERGING normal
    class DONE success
    class FAIL,ABORT fail
    class IDEM note

状态流转:INITIATED → UPLOADING → READY_TO_MERGE → MERGING → COMPLETED,异常分支包括 FAILED 和 ABORTED,Complete 操作需要幂等性保证。


第六回合:异常处理与清理

面试官: 如果客户端上传了几个分片后,突然断网了,再也没回来,那些分片怎么办?

候选人: 需要清理机制。可以:

  1. 设置 UploadId 过期时间(比如 7 天),Redis 里设置 TTL
  2. 定时任务扫描过期的 UploadId,删除临时分片
  3. 或者直接学 S3 的 AbortMultipartUpload + Lifecycle

面试官: 说说 S3 的 Lifecycle 机制。

候选人: S3 可以配置 Bucket 的生命周期规则:

  • 对于未完成的分片上传(Incomplete MPU),在指定天数后自动 Abort
  • 自动删除所有临时分片,释放存储
  • 这是一个后台异步任务,不需要人工干预

面试官: 那 AbortMultipartUpload 和 Lifecycle 有什么区别?

候选人:

  • AbortMultipartUpload - 主动调用,立即删除指定 UploadId 的所有分片
  • Lifecycle - 被动触发,自动清理长时间未完成的 MPU

面试官: Abort 操作需要幂等吗?

候选人: 需要。S3 文档说 AbortMultipartUpload 可能需要多次调用才能完全释放资源,尤其是在并发上传的场景下。


第七回合:性能优化

面试官: 现在系统能正确运行了,但上传 10GB 文件还是太慢,怎么优化?

候选人:

  1. 并发上传 - 同时上传多个分片,而不是串行
  2. 分片大小调整 - 小文件用 5MB,大文件可以用更大的分片(比如 100MB),减少请求数
  3. 断点续传 - 记录已上传的分片,失败后只传未完成的
  4. CDN 加速 - 上传接入点靠近用户

面试官: 分片大小越大越好吗?

候选人: 不是。太大的分片:

  • 单点失败重传成本高
  • 内存占用大
  • 并发度降低

S3 推荐的最小分片是 5MB(除了最后一片),最大 5GB。

面试官: 如果文件 100GB,你选多大分片?

候选人: 100MB 左右。这样总分片数 1000 个左右,并发上传 10 个分片,既不会太碎片化,也能保证并行度。


第八回合:生产环境部署

面试官: 实际生产环境,你会怎么部署这个系统?

候选人:

方案 A:自研实现(中小规模)

  • 临时分片存本地磁盘或 NFS
  • Redis 记录上传进度
  • 合并服务单节点或主从部署
  • 适合:内部系统、文件大小可控

方案 B:对象存储(推荐,大规模)

  • 直接用阿里云 OSS / AWS S3 / 腾讯云 COS
  • 客户端先请求服务端获取 STS 临时凭证
  • 客户端直传到 OSS,分片/断点/秒传都由 OSS 处理
  • OSS 回调服务端通知上传完成
  • 优势:不经过应用服务器,无带宽和存储压力,高可用

面试官: 如果用对象存储,还需要自己处理合并吗?

候选人: 不需要。OSS/S3 原生支持 Multipart Upload,Complete 操作直接调用它们的 API 就行。

面试官: 那你这个服务在对象存储方案里承担什么角色?

候选人:

  • 鉴权中心:生成 STS 临时上传凭证
  • 业务逻辑:文件元数据管理、权限控制、回调处理
  • 秒传判断:根据 MD5 检查文件是否已存在
  • 结果通知:上传完成后通知业务系统

核心设计要点总结

S3 Multipart Upload 协议流程

💡 ** 图 5:S3 Multipart Upload 协议流程**

sequenceDiagram
    participant C as 客户端
    participant A as 应用服务
    participant R as Redis
    participant S as 存储(磁盘 / OSS)

    C->>A: 1. InitiateUpload(fileName, md5, size)
    A->>R: 记录 UploadId / TTL / 元信息
    A-->>C: 2. 返回 UploadId

    C->>A: 3. UploadPart(partNumber, data)
    A->>S: 保存分片
    A->>R: 记录 PartNumber + ETag
    A-->>C: 4. 返回 ETag

    C->>A: 5. CompleteUpload(Parts 列表)
    A->>R: 校验数量 / ETag / 状态
    A->>S: 校验通过后执行合并
    A-->>C: 6. 返回上传结果

关键设计决策

问题S3 解决方案我们的实现
谁来控制上传会话?服务端生成 UploadId同左
怎么记录已上传分片?服务端内部状态机Redis Hash
怎么防伪造 complete?强制校验 Parts 列表同左
怎么验证分片完整性?ETag(MD5)校验同左
合并失败怎么办?幂等重试同左
僵尸分片怎么清理?Abort + LifecycleRedis TTL + 定时任务

面试追问点与参考答案

序号面试官追问关键考察点参考答案要点
1前端通知合并有什么问题?信任边界意识完全信任前端有风险,可能被伪造请求、漏传分片
2服务端怎么判断"真的传完了"?服务端状态建模服务端预先知道总分片数,核对每个分片的 ETag
3怎么防止恶意客户端伪造完成请求?接口安全、鉴权、幂等UploadId 绑定用户,接口幂等,限流防刷
4分片上传后损坏了怎么发现?数据完整性设计分片级校验值(MD5/CRC32),合并后整文件校验
5合并时服务器宕机怎么办?可靠性、原子性先写临时文件,原子发布,状态机可恢复
6多个请求同时合并同一文件怎么办?分布式幂等、一致性CAS/分布式锁保证只有一个实例执行合并
7多个用户同时秒传同一个文件怎么处理?去重设计、逻辑与物理解耦文件 hash 去重,引用计数,单飞机制
8分片分散在不同节点怎么高效合并?存储架构、数据局部性避免跨节点拉取,利用对象存储 compose 能力
9上千个 10GB 文件同时传哪里先崩?性能瓶颈识别带宽、请求数、元数据写库、临时空间
10生产环境怎么部署?架构决策推荐直传 OSS,应用层只做控制面

面试追问深度解析

追问 1-3:信任边界与安全性

面试官意图: 看候选人是否意识到"客户端不可信"这个基本原则。

深度答案:

  1. 为什么不能信任前端?

    • 浏览器控制台可以伪造任何请求
    • 恶意用户可以直接调 API,绕过前端逻辑
    • 网络中间人可能篡改请求
    • 前端 Bug 可能导致逻辑错误(如认为传完了实际没传完)
  2. S3 的解决方案核心:服务端权威 传统方案:前端控制流程,服务端被动响应 S3 方案:服务端控制会话,前端只是执行者

    关键差异:
    - UploadId 由服务端生成(不是前端随便编一个)
    - 分片记录由服务端保存(不是前端说传了就是传了)
    - Complete 时强制校验(服务端自己查 Redis,不依赖前端报的列表)
    
  3. 鉴权细节:

    • 每个分片上传 URL 应该是预签名的(带过期时间)
    • UploadId 要绑定用户 ID,防止 A 用户的请求操作 B 用户的上传
    • Complete 接口要验证调用者是否是该 UploadId 的创建者
追问 4-6:可靠性与一致性

面试官意图: 看候选人是否做过高可用设计,理解原子性和幂等性。

深度答案:

  1. 数据完整性三层校验: 第一层:TCP 校验(网络层,不可靠) 第二层:分片级 ETag(MD5,发现单分片损坏) 第三层:整文件 MD5(最终校验,发现合并错误)

    S3 的最终 ETag 特殊格式:
    - 单分片上传:就是文件 MD5
    - 多分片上传:MD5-of-MD5s-N(N 是分片数)
    - 示例:"d41d8cd98f00b204e9800998ecf8427e-3"
    
  2. 原子性保证:

    // 伪代码:原子发布
    void completeUpload(String uploadId) {
        // 1. 先合并到临时位置
        Path tempFile = mergeToTemp(uploadId);
        
        // 2. 验证完整性
        if (!verifyMd5(tempFile, expectedMd5)) {
            throw new ChecksumException();
        }
        
        // 3. 原子移动到最终位置(文件系统 rename 是原子的)
        atomicRename(tempFile, finalPath);
        
        // 4. 更新状态
        redis.set("upload:" + uploadId + ":status", "COMPLETED");
    }
    
  3. 幂等性实现:

    // 幂等 key:uploadId + 文件MD5
    @Idempotent(key = "'complete:' + #uploadId + ':' + #fileMd5")
    public String completeUpload(String uploadId, String fileMd5) {
        // 已处理过直接返回结果
        String cachedResult = redis.get("complete:result:" + uploadId);
        if (cachedResult != null) {
            return cachedResult;
        }
        
        // 分布式锁防止并发执行
        try (AutoCloseableLock lock = redisLock.lock("complete:lock:" + uploadId, 60)) {
            // 再次检查(双检锁)
            cachedResult = redis.get("complete:result:" + uploadId);
            if (cachedResult != null) {
                return cachedResult;
            }
            
            // 执行合并
            String result = doComplete(uploadId);
            
            // 缓存结果,支持幂等重试
            redis.setex("complete:result:" + uploadId, 86400, result);
            return result;
        }
    }
    
追问 7-8:分布式场景

面试官意图: 看候选人是否有分布式系统设计经验。

深度答案:

  1. 秒传的分布式一致性: 问题场景:

    • 用户 A 开始上传大文件(10GB)
    • 用户 B 同时上传同一个文件(MD5 相同)
    • 如何避免 B 也传一遍?

    解决方案: a) 逻辑文件 vs 物理文件解耦

    • 用户文件表:记录用户-文件关系(多对一)
    • 物理文件表:记录真实的存储位置(唯一)

    b) 首次上传单飞机制

    • 第一个上传者成为"主上传者",真正上传
    • 后续秒传用户创建引用关系,状态标记为"引用中"
    • 等主上传者完成后,所有引用者一起成功

    c) 引用计数 - 物理文件维护引用计数 - 删除时只有引用计数归零才真正删除

    (画图说明数据模型)

    💡 ** 图 6:秒传去重里的逻辑文件与物理解耦**

    flowchart TB
        UA[用户 A 上传文件] --> H[计算文件 MD5]
        UB[用户 B 上传同一文件] --> H
        H --> Q{physical_files 中已存在?}
    
        Q -->|否| A1[用户 A 成为主上传者并真正上传]
        A1 --> PF[(physical_files<br/>真实文件 + 存储位置)]
        A1 --> UF1[(user_files<br/>A 的文件引用关系)]
        PF --> RC[ref_count = 1]
    
        Q -->|是| B1[用户 B 直接秒传]
        B1 --> UF2[(user_files<br/>B 的文件引用关系)]
        UF1 --> PF
        UF2 --> PF
        B1 --> RC
    
        RC --> CLEAN{ref_count 是否归 0?}
        CLEAN -->|否| KEEP[只删除 user_files 引用关系]
        CLEAN -->|是| DEL[删除 physical_files 对应真实文件]
    
        classDef user fill:#DBEAFE,stroke:#1F2937,stroke-width:2px
        classDef table fill:#DDF5E7,stroke:#166534,stroke-width:2px
        classDef decision fill:#FEF3C7,stroke:#92400E,stroke-width:2px
        classDef danger fill:#FEE2E2,stroke:#991B1B,stroke-width:2px
        class UA,UB,H,A1,B1,KEEP user
        class PF,UF1,UF2,RC table
        class Q,CLEAN decision
        class DEL danger
    

    逻辑层 user_files 记录“谁拥有这个文件”,物理层 physical_files 记录“文件真正存在哪”,两者解耦后才能同时支持秒传、去重和安全删除。

  2. 跨节点合并优化: 坏方案:

    • 分片存在节点 A、B、C
    • 合并服务把所有分片拉到本地再合并
    • 10GB 文件跨网络传输 3 次,带宽爆炸 好方案: a) 上传时就聚合
    • 同一文件的分片尽量落在同一节点(一致性 Hash) b) 对象存储原生支持
    • OSS/S3 有 ComposeObject/Multipart Complete API
    • 在存储层完成合并,不经过应用服务器 c) 分布式合并(不得已时)
    • 每个节点先本地合并自己负责的分片
    • 最后做一次全局合并
    • 减少跨网络数据传输
追问 9-10:性能与架构

面试官意图: 看候选人是否有生产环境经验,能识别真实瓶颈。

深度答案: 性能瓶颈识别: 瓶颈优先级(从高到低):

① 应用服务器带宽(最先崩) - 1000 用户 * 10MB/s = 10GB/s 上行 - 一般服务器网卡只有 1-10Gbps - 解决:直传 OSS,不经过应用层

② 元数据写库压力 - 每分片都要写 Redis/MySQL 记录状态 - 1000 文件 * 1000 分片 = 100万 写操作 - 解决:批量写入、异步刷盘、本地缓存

③ 临时存储空间 - 1000 个 10GB 文件同时上传 - 需要 10TB 临时空间 - 解决:流式处理、及时清理、限制并发数

④ 合并服务 CPU/IO - 合并是 CPU 密集型(MD5 计算)+ IO 密集型 - 解决:异步合并、独立合并集群、对象存储代劳

  1. 生产部署方案对比:
维度自研方案对象存储方案
复杂度高(自己实现所有细节)低(调用 API 即可)
可靠性依赖团队水平云厂商 SLA 保障
成本需要维护服务器集群按量付费
性能受限于自建机房带宽CDN 加速全球分布
扩展性需要提前规划容量弹性伸缩
适用场景内部系统、数据敏感场景互联网产品、海量用户

候选人常见错误与面试官点评

错误类型典型表现面试官点评
天真信任"前端传完了告诉后端合并就行""这说明你始终把客户端当可信的。实际任何客户端都可能被攻破、有 Bug、被恶意利用。服务端必须掌握真相。"
忽视校验"合并后文件肯定是对的""网络传输、磁盘写入、内存错误都可能导致数据损坏。生产环境没有校验就是埋雷。"
单点思维"合并服务用单线程就行""你的系统可能在 demo 环境能跑,但上生产后并发请求会把服务打崩。要考虑分布式锁、异步化、限流。"
忽视清理"临时文件定期人工删一下""10GB 文件传了一半用户关页面了,这种僵尸上传每天可能有上千个。没有自动清理机制,磁盘很快就满。"
过度设计"我要自己实现一套分布式存储""除非你在存储团队,否则业务开发应该优先考虑用现成的 OSS/S3。你的核心竞争力是业务,不是存储。"
忽略性能"所有分片都存数据库""每分片都写 MySQL,1000 用户同时上传就是百万级写入。要识别瓶颈,Redis 缓存、批量写入、异步刷盘都要考虑。"

参考资料