刷题系统的技术架构选型对比单体 vs 微服务 vs Serverless一、深度引言与场景痛点一个刷题工具真的需要上微服务吗7 月我准备把自己用的刷题记录工具从一个本地脚本升级为一个可用的 Web 系统。摆在面前的第一个问题是技术选型用单体还是微服务甚至要不要上 Serverless一开始我的大脑本能地浮现出微服务的各种好处——独立部署、技术栈灵活、故障隔离——这些词汇在技术博客里见过无数遍。但冷静下来后我发现一个事实我这个系统目前只有我一个用户未来最多也就几十个人用。在上微服务之前我甚至还没有验证这个系统对别人有用这个最基础的假设。本文以一个真实的小型刷题系统为案例拆解单体、微服务、Serverless 三种架构在这个场景下的适用性对比。重点不是介绍架构概念而是在具体约束条件下做 trade-off 分析的方法论。二、底层机制与原理深度剖析三种架构的本质差异三种架构的本质差异不在于服务数量而在于模块边界的管理方式和部署单元的粒度。单体架构的核心特征所有功能运行在同一个进程中。模块间的调用是进程内的函数调用延迟极低纳秒级。所有功能共享同一个数据库连接池和内存空间。部署时只需要部署一个可执行文件或容器。微服务架构的核心特征按业务边界将系统拆分为多个独立部署、独立运行、独立扩展的服务。服务间通过网络HTTP/gRPC/消息队列通信。每个服务有自己的数据库数据去中心化。Serverless 架构的核心特征功能以函数为单位不关心服务器和运行时。每次请求触发函数执行空闲时不占用资源。适合低频、突发、计算密集的任务。对于一个只有 5 个模块的刷题系统用户管理、题库管理、刷题记录、AI 题解生成、统计分析——单体架构是最合理的选择。这 5 个模块之间的调用关系紧密统计需要同时查用户表、题库表和刷题记录表拆分微服务只会引入不必要的网络延迟和分布式事务复杂度。三、生产级代码实现与最佳实践三种架构的代码对比 刷题系统架构对比 —— 三种架构下同一个提交题解功能的实现差异 通过对比直观展示架构选择对代码复杂度的实际影响 from dataclasses import dataclass from typing import List, Dict, Optional # 方案一单体架构 单体架构下的提交题解功能 所有逻辑在同一个进程中完成 1. 参数校验 2. 数据库写入题目记录 击败百分比更新 3. 统计更新用户总题数、连续打卡天数 4. 返回结果 优点代码直观调用链路短事务控制简单 缺点所有功能耦合在一个代码仓库中 dataclass class SubmissionRequest: user_id: int problem_id: str language: str code: str passed: bool class MonolithicSubmissionService: 单体版本的题解提交服务 def __init__( self, submission_repo, # 依赖注入但都在同一进程 user_stats_repo, problem_repo, ): self.submission_repo submission_repo self.user_stats_repo user_stats_repo self.problem_repo problem_repo def submit(self, request: SubmissionRequest) - Dict: 在一个数据库事务中完成所有操作 优势事务边界清晰数据一致性由数据库 ACID 保证 # 1. 保存提交记录 submission_id self.submission_repo.save({ user_id: request.user_id, problem_id: request.problem_id, language: request.language, passed: request.passed, }) # 2. 更新用户统计总题数等 self.user_stats_repo.increment_solved_count(request.user_id) # 3. 更新题目通过率 self.problem_repo.update_acceptance_rate(request.problem_id) # 4. 检查是否解锁成就 streak self.user_stats_repo.get_streak(request.user_id) return { submission_id: submission_id, total_solved: self.user_stats_repo.get_solved_count( request.user_id ), streak: streak, } # 方案二微服务架构 微服务架构下的提交题解功能 拆分为三个服务调用 1. SubmissionService保存提交记录 2. UserStatsService更新用户统计 3. ProblemService更新题目统计 成本三次网络调用、分布式事务用消息队列保证最终一致性 class MicroserviceSubmissionHandler: 微服务版本的提交处理器 —— 复杂度显著上升 def __init__(self, http_client, message_queue): self.http http_client # 服务间 HTTP 调用 self.mq message_queue # 用于异步统计更新 def submit(self, request: SubmissionRequest) - Dict: 微服务下的提交需要协调多个远程服务 # 1. 调用提交服务同步写入 submission_resp self.http.post( http://submission-service/save, json{user_id: request.user_id, problem_id: request.problem_id} ) # 2. 异步发送统计更新消息最终一致性 self.mq.publish(stats.update, { type: submission, user_id: request.user_id, problem_id: request.problem_id, passed: request.passed, }) # 3. 同步获取用户当前统计可能有延迟 stats self.http.get( fhttp://user-stats-service/stats/{request.user_id} ) return { submission_id: submission_resp[id], total_solved: stats.get(total_solved, 统计更新中...), } # 方案三Serverless 架构 Serverless 下的提交题解功能 适合刷题系统中的低频功能如 - 生成周报统计每天凌晨触发一次 - 分析刷题趋势计算密集型但不频繁调用 # Serverless 函数以 AWS Lambda 风格为例 def generate_weekly_report_handler(event, context): 周报生成的 Serverless 函数 每周日凌晨触发一次计算所有用户的周报 优势计算密集型任务但调用频率低Serverless 按需付费很低 user_id event.get(user_id) week event.get(week) # 计算本周刷题数据——这个计算逻辑较重聚合、排序、分析 weekly_data { total_problems: 0, avg_time_per_problem: 0.0, strongest_topic: , weakest_topic: , } # 生产代码中这里会从数据库加载并计算 # ... return { user_id: user_id, week: week, report: weekly_data, }三种架构的实现对比说明架构选择对代码复杂度的影响是数量级的。同样的功能单体的代码量和复杂度是 1x微服务是 3-5xServerless 取决于函数粒度。这个差距不是用了好框架能弥补的是架构本身的固有成本。四、边界分析与架构权衡刷题系统究竟有没有必要用微服务对大多数刷题系统来说没必要用微服务。判断标准如下只有当以下三个条件全部满足时才考虑微服务团队有 5 人以上且需要并行开发不同的模块某些模块需要独立的扩容策略例如 AI 题解生成需要 GPU其他模块不需要某些模块需要独立的技术栈例如题解引擎用 Python用户服务用 Java当以下条件满足时考虑 Serverless功能调用频率极低每天 100 次常规服务成本浪费严重计算任务可以拆分为独立的、无状态的函数大多数刷题系统的最优方案模块化单体。即代码按业务模块用户、题库、统计、AI拆分为清晰的 package但部署为单一应用。这种架构保留了单体的简单性同时为后续的微服务拆分保留了代码结构上的可迁移性。架构决策的核心不是哪种更先进而是哪种在当前约束下最合理。违反这个原则的架构选型叫做过度设计。结论做架构选型时最容易犯的错误是为未来可能的场景设计忽略了当前实际的场景。微服务的引入成本是实打实的分布式事务、服务发现、配置中心、链路追踪但它的收益依赖于你是否真的有微服务要解决的问题团队并行开发、独立扩容、多技术栈。对于刷题系统这个场景我的结论很明确起步用模块化单体当单一团队已经无法高效协作时才开始拆分微服务。Serverless 适合其中的计算密集型低频任务如周报生成、趋势分析作为单体的补充而非替代。架构选型没有标准答案但有标准流程先定义约束条件用户量、业务复杂度、团队规模再在约束范围内找到最简单的可行方案——最后把为什么要用这个方案的理由写下来。这个写下来的过程本身就是对架构决策的最好检验。