'use client'; import { type MouseEvent, type ReactNode } from 'react'; import { ArrowRight, BookOpen, Route, Workflow } from 'lucide-react'; import { courseHref } from './course-navigation'; import { modules } from './module-catalog'; import './course-home.css'; type Navigate = (module: string, lesson?: string) => void; const moduleDetails = [ { slug: 'api', position: '请求入口:浏览器与业务逻辑之间', responsibility: '把一个 Python 函数变成别人能调用的 HTTP 接口,约定输入、返回值、错误和通信方式。', importance: '接口是前后端的共同约定。边界不清,调用方就不知道请求是否成功、如何处理失败。', problem: '参数乱传、错误信息不一致、慢调用卡住服务,以及结果需要逐步展示时怎样通信。', example: '商家创建商品、修改名称和价格、切换上架状态;用这些操作练 CRUD,再用商品咨询演示 SSE 与 WebSocket。', question: '用户的一个操作,怎样成为服务端的一次可靠调用?', }, { slug: 'database', position: '持久数据层:任务与业务记录的真源', responsibility: '通过表、约束、索引和事务保存数据;“真源”就是冲突时系统最终以这里的记录为准。', importance: '进程会重启、请求会并发。数据库既要让数据留下来,也要守住“哪些状态允许存在”的规则。', problem: '数据丢失、重复记录、并发覆盖、慢查询、锁等待,以及备份能否真正恢复。', example: '在 shop_lab 从商品与分类需求推导表结构,解释价格为什么按分存、分类为什么拆表,再练事务、索引与恢复。', question: '服务重启后,哪些事实必须还在?', }, { slug: 'api-database', position: 'API 与 MySQL 的连接处:会话和事务边界', responsibility: '把接口接入数据库,安排连接的借用与归还,明确何时提交、何时回滚,以及如何升级表结构。', importance: '接口返回“成功”应对应已经提交的事实;重复请求和并发修改不能悄悄破坏业务规则。', problem: '连接泄漏、事务跨请求、重复提交、并发更新冲突,以及“记录写了、任务没发出去”的双写窗口。', example: '长任务受理时,把任务记录和 outbox 待投递事件写入同一事务;课程用持久化商品服务理解这些边界。', question: '一次接口成功,到底承诺了哪些数据已经写入?', }, { slug: 'tasks', position: '执行层:任务投递器、队列与独立 worker', responsibility: '先区分协程、线程和进程,再把长任务交给独立执行进程;管理状态、并发、重试与取消。', importance: '“接口先返回”不会自动带来可靠性。任务能否跨进程退出恢复,要依靠持久记录、投递和确认机制。', problem: '请求超时、事件循环阻塞、任务丢失、重复执行、积压,以及 worker 做到一半退出。', example: '商家导出商品清单,API 先返回任务 ID,worker 读取商品快照并生成文件;用这条链路练并发、重试和恢复。', question: '用户不用一直等的工作,之后由谁保证继续执行?', }, { slug: 'redis', position: '加速层:API 旁的可重建缓存', responsibility: '把高频读取的结果临时放到 Redis;定义缓存键、有效期、更新失效、容量和故障降级。', importance: '缓存让重复读取更快,但可能短暂落后于数据库。先说清可接受的旧数据窗口,才知道怎样使用它。', problem: '数据库重复承压、热点同时过期、缓存穿透、内存耗尽,以及缓存不可用时请求雪崩。', example: '商品详情被反复读取时使用 Redis 缓存;商家修改商品后处理失效,并以 MySQL 为真源。', question: '哪些数据可以临时读副本,丢了也能重新构建?', }, { slug: 'docker', position: '运行包装层:API、worker 和依赖服务的容器', responsibility: '把代码与依赖构建成镜像,用 Compose 组织服务网络、配置、健康检查、持久卷与资源限制。', importance: '同一份代码需要在不同机器上按可重复的方式启动;容器删掉时,数据库数据仍应有独立的保存位置。', problem: '本地能跑而部署失败、服务互相找不到、容器重建丢数据、资源争抢,以及升级无法回滚。', example: '把 API、worker、MySQL、Redis 作为职责不同的服务运行;课程搭建 shop-service 容器环境。', question: '怎样把几个进程及其依赖装成可重复运行的一套环境?', }, { slug: 'linux', position: '主机与网络入口:承载进程,对外提供服务', responsibility: '管理运行账户、目录权限、端口、systemd、Nginx、TLS、发布回滚和备份恢复流程。', importance: '程序能启动只是起点。它还要能被安全访问、在故障后恢复,并且在升级时有明确的退路。', problem: '端口不通、权限错误、进程退出、流式响应被代理缓冲、证书配置错误和发布中断。', example: '为商品服务的 HTTP 与流式入口配置反向代理,把 shop-service 部署到独立 Linux 练习机。', question: '从自己的电脑到别人能访问的服务,中间还有哪些工作?', }, { slug: 'troubleshooting', position: '横跨各层:日志、指标、追踪与恢复手册', responsibility: '用请求 ID 和任务 ID 串起现场;指标看整体变化,日志看具体事件,追踪看跨组件耗时。', importance: '“很慢”只是症状。先找证据和影响范围,才能决定该限流、回滚,还是修复某个依赖。', problem: '不知道请求卡在哪、CPU 与连接耗尽、慢 SQL、缓存失效、任务积压,以及修好后无法证明恢复。', example: '同一个任务 ID 贯穿 API、投递和 worker,区分“排队久”和“执行慢”;课程进行隔离故障演练。', question: '用户说任务一直转圈,怎样沿着证据找到卡住的那一段?', }, { slug: 'distributed', position: '设计视角:所有跨进程、跨机器的连接处', responsibility: '用架构图与故障推演理解扩容、一致性、复制、分片、消息可靠性和服务治理;练习说明设计取舍。', importance: '组件各自正常,不等于协作一定正确。网络超时只能说明没及时收到答复,不能证明对方没执行。', problem: '部分失败、重复消息、数据不一致、流量突增、热点、级联故障,以及系统设计题只会堆组件。', example: '讨论“任务已完成但响应丢失”“数据库成功但投递失败”等场景;12 节课以理论和面试推演为主。', question: '多个组件没有同时成功时,系统该如何继续工作?', }, { slug: 'review', position: '课程收尾:逐一检查九个技术模块', responsibility: '用理解问题、情境推演和成果证据检验掌握程度,把薄弱点对应到原课时。', importance: '能跟着做出结果之后,还需要独立解释原因,并在条件变化时作出有依据的判断。', problem: '看答案觉得都懂,合上课件却讲不清、做不出,或不知道还需要补哪一课。', example: '继续用商品查询、编辑和清单导出,闭卷回答问题,核对要点,并记录需要回学的部分。', question: '学完之后,拿什么证明自己已经掌握?', }, ]; function HomeLink({ module, navigate, children, className, }: { module: string; navigate: Navigate; children: ReactNode; className?: string; }) { return ( ) => { if ( event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) return; event.preventDefault(); navigate(module); }} > {children} ); } function ArchitectureNode({ title, lines, module, kind = 'service', }: { title: string; lines: string[]; module?: string; kind?: 'service' | 'context' | 'queue' | 'storage' | 'runtime'; }) { const className = `home-architecture-node home-node-${kind}`; const content = ( <> {title} {lines.map((line) => ( {line} ))} ); return module ? ( {content} ) : (
{content}
); } function ArchitectureLayer({ name, responsibility, kind, children, note, }: { name: string; responsibility: string; kind: string; children: ReactNode; note?: string; }) { return (

{name}

{responsibility}

{children}
{note &&

{note}

}
); } function ArchitectureDiagram() { return (
); } const timeline = [ [ '提交请求', '商家点击“导出商品清单”。API 校验请求并确定商品快照;这时尚未承诺文件已经生成。', '当前请求', ], [ '记录已提交', '在一个数据库事务里写入任务和待投递事件。事务提交后,API 返回 202 与任务 ID。', '受理 ≠ 完成', ], [ '任务交接', '投递器读取事件并发送到队列。此时浏览器的原请求已经结束,投递失败可以稍后重试。', '异步开始', ], [ '独立执行', 'worker 领取任务,把固定的商品快照写入导出文件;完成后保存结果引用,再确认消息。重复消息需要幂等处理。', '可能重试', ], [ '读取结果', '客户端拿任务 ID 查询 API,或订阅进度。API 从任务记录判断等待、执行、成功或失败。', '看持久状态', ], ]; export function CourseHome({ navigate }: { navigate: Navigate }) { const totalLessons = modules.reduce((sum, module) => sum + module.count, 0); return (
AGENT BACKEND · COURSE GUIDE

先看见整个系统,
再学会每一个模块.

从一个容易理解的电商商品服务出发,逐步学会接住请求、保存商品、导出清单,并在失败时恢复。这些后端能力也构成服务端 Agent 的基础。

从 API 第一模块开始 先看系统全貌
{String(modules.length).padStart(2, '0')}
学习模块
{totalLessons}
课时与自测单元
8 + 1 + 1
后端实践 + 架构理论 + 复盘自测
01 / SYSTEM MAP

把课程放进一张分层架构图

从客户端到运行底座,按职责逐层理解系统;观测与设计能力贯穿各层。节点编号对应下方课程模块,点击可查看定位。

课程覆盖模块 异步任务交接 * Agent 扩展上下文
分层表示职责与支撑关系,请求按需访问相关组件,并非逐层依次执行。橙色箭头表示异步任务交接;API 负责业务鉴权,Nginx 负责入口代理。带 * 的 Agent 编排、模型与工具用于说明扩展位置,不属于本课已实现的完整系统。
A

短请求:当场取回结果

客户端 → 网关 → API → 数据库、缓存或业务调用 → 原路返回。用户等待的是这一次请求完成;慢依赖仍会占用请求的时间预算。

B

长任务:先受理,再执行

API 写入任务和 outbox → 返回任务 ID。投递器 → 队列 → worker → 写回结果。用户之后再查询进度,接收成功并不等于执行成功。

C

运行与可靠性:贯穿整张图

Docker 管运行单元,Linux 管主机与入口,观测帮助找故障。分布式设计关注它们之间的网络、重复、超时与不一致。

两个容易混淆的词:队列像“待办传送带”,worker 是实际做事的进程;Redis 可以用来缓存,也可以承担某些队列的 broker 角色,但两种用途的丢失容忍度、持久化和内存策略不同,不能因产品名相同就视为同一种数据。

02 / MODULE GUIDE

每个模块,补上哪一种能力

前九个模块对应上图中的技术位置,最后一个模块用于复盘自测,检查这些能力能否独立运用。

{moduleDetails.map((detail, index) => { const catalogEntry = modules.find( (candidate) => candidate.slug === detail.slug, ); if (!catalogEntry) return null; return (
{String(index + 1).padStart(2, '0')} {catalogEntry.count}{' '} {detail.slug === 'review' ? '自测单元' : '课时'} ·{' '} {detail.slug === 'distributed' ? '理论与面试' : catalogEntry.kicker}

{catalogEntry.name}

{detail.question}

所在位置
{detail.position}
负责什么
{detail.responsibility}
为何重要
{detail.importance}
解决问题
{detail.problem}
放进实例
{detail.example}
进入{catalogEntry.name}
); })}
03 / ONE LONG TASK

把“导出一份商品清单”走一遍

这是把多个模块连起来的设计示例:关键不在“后台”两个字,而在每个阶段承诺了什么、失败后由谁接手。

    {timeline.map(([title, description, state], index) => (
  1. {index + 1}

    {title}

    {state}

    {description}

  2. ))}
如果 worker 刚写完结果就退出了?

消息可能尚未确认,随后会被再次投递。所以“至少一次投递”仍需业务幂等:同一个任务 ID 再次到达时,要识别已经提交的结果。若任务会向外部系统产生副作用,还需要对方支持幂等键或配套的核对与补偿流程。

去分布式模块推演更多失败场景
04 / LEARNING ROUTE

没有后端经验,就按这条路走

会基础 Python、能执行终端命令即可从第一模块开始。数据库、Docker、Linux 环境在对应模块准备,每一步都带着上一阶段的问题进入下一阶段。

  1. 先让请求有入口,数据能保存

    01 API → 02 数据库 → 03 API + 数据库。先完成一个能正确提交、查询与修改数据的服务。

    开始
  2. 再处理等待、并发与重复读取

    04 异步任务 + 05 Redis。两者都建立在持久化 API 之上;任务模块进入队列部分前,先准备 Redis 基础环境。

    进入
  3. 让服务运行起来,也能查清故障

    06 Docker → 07 Linux → 08 排障。用部署、停止、升级和恢复检验前面实现的边界。

    进入
  4. 把组件连起来,解释架构为什么这样设计

    09 分布式系统设计。用已有组件经验讨论容量、数据与失败;围绕理论和面试场景展开,不安排实战代码。

    进入
  5. 最后,检验每个模块是否真的掌握

    10 复盘与自测。每个技术模块对应一个自测单元:独立回答问题、推演变化、核对掌握证据,再按缺口回学。

    开始自测
); }