feat: enhance demo components with consistent styling and info boxes
- Add standardized header and info box components to all demo files - Improve visual consistency with theme colors and spacing - Add max-height and overflow-y for better content containment - Update package.json build script with --force flag - Add .gitignore entries for REFACTORING files - Fix table formatting in audio-intro.md
This commit is contained in:
+320
-755
File diff suppressed because it is too large
Load Diff
@@ -151,12 +151,12 @@ _很多人以为 AI 直接处理"声音",但实际上 AI 处理的是**数字
|
||||
|
||||
### 4.1 自回归 vs 非自回归
|
||||
|
||||
| 特性 | 自回归 (AR) | 非自回归 (NAR) | 流匹配 (Flow) |
|
||||
|------|------------|---------------|--------------|
|
||||
| 生成方式 | 逐个时间步 | 一次性生成 | 流匹配路径 |
|
||||
| 速度 | 慢 | 快 | 很快 |
|
||||
| 音质 | 高 | 中高 | 高 |
|
||||
| 代表模型 | Tacotron 2 | FastSpeech 2 | F5-TTS |
|
||||
| 特性 | 自回归 (AR) | 非自回归 (NAR) | 流匹配 (Flow) |
|
||||
| -------- | ----------- | -------------- | ------------- |
|
||||
| 生成方式 | 逐个时间步 | 一次性生成 | 流匹配路径 |
|
||||
| 速度 | 慢 | 快 | 很快 |
|
||||
| 音质 | 高 | 中高 | 高 |
|
||||
| 代表模型 | Tacotron 2 | FastSpeech 2 | F5-TTS |
|
||||
|
||||
### 4.2 关键组件
|
||||
|
||||
@@ -285,15 +285,15 @@ GST (Global Style Token) 是一种从参考音频中提取风格特征的方法
|
||||
|
||||
## 附录:常用术语表 (Vocabulary)
|
||||
|
||||
| 术语 | 英文 | 解释 |
|
||||
| :------------- | :--------------------------- | :------------------------------------------- |
|
||||
| **采样率** | Sample Rate | 每秒采集的音频样本数(如 44.1kHz)。 |
|
||||
| **梅尔频谱** | Mel-Spectrogram | 模拟人耳感知的频谱表示,音频 AI 的核心输入。 |
|
||||
| **声码器** | Vocoder | 将频谱图还原为音频波形的模型。 |
|
||||
| **TTS** | Text-to-Speech | 文本转语音,让 AI 说话的技术。 |
|
||||
| **ASR** | Automatic Speech Recognition | 自动语音识别,让 AI 听懂的技术。 |
|
||||
| **零样本克隆** | Zero-Shot Cloning | 只需几秒参考音频就能模仿任何声音。 |
|
||||
| **流匹配** | Flow Matching | 一种高效的生成方法,用于最新的 TTS 模型。 |
|
||||
| **声音编码器** | Speaker Encoder | 提取声音身份特征的神经网络。 |
|
||||
| **GST** | Global Style Token | 全局风格 Token,用于情感控制。 |
|
||||
| **神经编解码器**| Neural Codec | 将音频压缩为离散 Token 的模型。 |
|
||||
| 术语 | 英文 | 解释 |
|
||||
| :--------------- | :--------------------------- | :------------------------------------------- |
|
||||
| **采样率** | Sample Rate | 每秒采集的音频样本数(如 44.1kHz)。 |
|
||||
| **梅尔频谱** | Mel-Spectrogram | 模拟人耳感知的频谱表示,音频 AI 的核心输入。 |
|
||||
| **声码器** | Vocoder | 将频谱图还原为音频波形的模型。 |
|
||||
| **TTS** | Text-to-Speech | 文本转语音,让 AI 说话的技术。 |
|
||||
| **ASR** | Automatic Speech Recognition | 自动语音识别,让 AI 听懂的技术。 |
|
||||
| **零样本克隆** | Zero-Shot Cloning | 只需几秒参考音频就能模仿任何声音。 |
|
||||
| **流匹配** | Flow Matching | 一种高效的生成方法,用于最新的 TTS 模型。 |
|
||||
| **声音编码器** | Speaker Encoder | 提取声音身份特征的神经网络。 |
|
||||
| **GST** | Global Style Token | 全局风格 Token,用于情感控制。 |
|
||||
| **神经编解码器** | Neural Codec | 将音频压缩为离散 Token 的模型。 |
|
||||
|
||||
@@ -1,404 +1,603 @@
|
||||
# 后端架构演进:从单机到云原生
|
||||
|
||||
> 💡 **学习指南**:本章节无需编程基础,通过交互式演示带你回顾后端架构的 30 年变迁。我们将从最原始的物理服务器讲起,一直到现代的 Serverless 云计算。理解架构演进的历史,能帮助你在面对技术选型时做出更明智的决策。
|
||||
::: tip 🎯 核心问题
|
||||
**代码写好了,怎么让全世界的人都能访问?** 这就像问:你是想开一家路边小摊,还是经营一家跨国连锁餐厅?后端架构的选择,决定了你的"餐厅"能服务多少顾客。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么要了解架构演进?
|
||||
|
||||
想象一下,你正在规划一次长途旅行。你可以选择骑自行车、开私家车、坐高铁,或者乘飞机。每种方式都有其适用的场景:自行车适合短距离且想锻炼身体的情况,飞机则适合跨越大陆的长途旅行。
|
||||
|
||||
**后端架构的选择也是如此。**
|
||||
|
||||
从互联网诞生到现在,后端架构经历了多次重大变革。每一次变革都不是为了"追新潮",而是为了解决当时面临的特定问题:
|
||||
|
||||
| 年代 | 核心问题 | 架构演进 |
|
||||
| ----- | ------------------------ | ------------------- |
|
||||
| 1990s | 如何把网站跑起来 | 物理服务器 |
|
||||
| 2000s | 代码越来越乱怎么维护 | 单体架构 + MVC |
|
||||
| 2010s | 系统太大怎么扩展和协作 | 微服务 + 容器化 |
|
||||
| 2020s | 如何降低运维成本和复杂性 | Serverless + 云原生 |
|
||||
|
||||
::: tip 📊 从表格中你能看到什么?
|
||||
让我们逐行解读这张表:
|
||||
|
||||
**1990s → 2000s**:从"能跑就行"到"需要维护"。网站从静态页面变成动态应用,代码量激增,需要更好的组织方式。
|
||||
|
||||
**2000s → 2010s**:从"单机"到"分布式"。用户量爆炸式增长,单台服务器扛不住了,需要拆分系统,水平扩展。
|
||||
|
||||
**2010s → 2020s**:从"自己运维"到"云服务"。容器和微服务虽然强大,但运维成本太高,Serverless 让开发者只关注业务逻辑。
|
||||
|
||||
**核心启示**:架构演进不是技术选型的游戏,而是**解决实际问题**的过程。每个阶段都有其适用的场景,没有"最好的架构",只有"最适合的架构"。
|
||||
:::
|
||||
|
||||
**了解架构演进的意义在于:**
|
||||
|
||||
1. **避免重复造轮子**:很多"新"概念其实早在几十年前就有雏形,了解历史能让你站在巨人的肩膀上
|
||||
2. **做出合理的技术选型**:没有最好的架构,只有最适合当前阶段的架构
|
||||
3. **理解技术背后的权衡**:每一次架构演进都是在**开发效率**、**系统性能**、**运维复杂度**之间做取舍
|
||||
4. **预判技术趋势**:历史总是押韵的,理解过去的演进规律有助于把握未来方向
|
||||
|
||||
<EvolutionIntroDemo />
|
||||
|
||||
---
|
||||
|
||||
## 0. 引言:为什么要了解架构演进?
|
||||
## 2. 物理服务器时代 (1990s)
|
||||
|
||||
想象一下,你正在规划一次长途旅行。你可以选择骑自行车、开私家车、坐高铁,或者乘飞机。每种方式都有其适用的场景:自行车适合短距离且想锻炼身体的情况,飞机则适合跨越大陆的长途旅行。
|
||||
### 2.1 什么是物理服务器?
|
||||
|
||||
**后端架构的选择也是如此。**
|
||||
在互联网刚起步时,后端就是一台放在机房里的**物理服务器**(一台真实的电脑)。
|
||||
|
||||
从互联网诞生到现在,后端架构经历了多次重大变革。每一次变革都不是为了"追新潮",而是为了解决当时面临的特定问题:
|
||||
::: tip 💡 通俗解释
|
||||
**物理服务器**就像你家里的台式机,但它:
|
||||
|
||||
| 年代 | 核心问题 | 架构演进 |
|
||||
|------|---------|---------|
|
||||
| 1990s | 如何把网站跑起来 | 物理服务器 |
|
||||
| 2000s | 代码越来越乱怎么维护 | 单体架构 + MVC |
|
||||
| 2010s | 系统太大怎么扩展和协作 | 微服务 + 容器化 |
|
||||
| 2020s | 如何降低运维成本和复杂性 | Serverless + 云原生 |
|
||||
- 7×24小时不关机
|
||||
- 放在专门的数据中心(有空调、UPS电源、消防系统)
|
||||
- 有更快的网络带宽(企业级光纤)
|
||||
- 有固定的公网IP地址(全世界都能访问)
|
||||
|
||||
**了解架构演进的意义在于:**
|
||||
这就好比你家 vs 餐厅:你家只是偶尔做饭,餐厅则是专业厨房,全天候营业,设备更专业。
|
||||
:::
|
||||
|
||||
1. **避免重复造轮子**:很多"新"概念其实早在几十年前就有雏形,了解历史能让你站在巨人的肩膀上
|
||||
2. **做出合理的技术选型**:没有最好的架构,只有最适合当前阶段的架构
|
||||
3. **理解技术背后的权衡**:每一次架构演进都是在**开发效率**、**系统性能**、**运维复杂度**之间做取舍
|
||||
4. **预判技术趋势**:历史总是押韵的,理解过去的演进规律有助于把握未来方向
|
||||
### 2.2 核心特点
|
||||
|
||||
<ArchitectureComparisonDemo />
|
||||
- **单机部署**:所有应用运行在一台物理机上
|
||||
- **手动运维**:需要人工上架、布线、安装系统
|
||||
- **垂直扩展**:性能不够时只能买更强的机器
|
||||
|
||||
---
|
||||
::: details 🔧 垂直扩展 vs 水平扩展
|
||||
**垂直扩展**(Scale Up):升级单台服务器的配置(更多CPU、更大内存、更快硬盘)。
|
||||
|
||||
## 1. 物理服务器时代 (1990s)
|
||||
**水平扩展**(Scale Out):增加更多服务器,让它们一起工作。
|
||||
|
||||
在互联网刚起步时,后端就是一台放在机房里的**物理服务器**(一台真实的电脑)。
|
||||
**比喻**:
|
||||
|
||||
- 垂直扩展:把小餐厅改成大餐厅,装修更豪华,但只有一个厨师
|
||||
- 水平扩展:开连锁店,每个店规模不大,但有100家分店
|
||||
|
||||
**优缺点**:
|
||||
|
||||
- 垂直扩展简单,但有上限(顶级服务器很贵,且有限制)
|
||||
- 水平扩展理论上无限,但需要解决数据一致性问题
|
||||
:::
|
||||
|
||||
### 2.3 痛点
|
||||
|
||||
- **慢**:每次改代码都要手动上传,然后重启服务器
|
||||
- **贵**:扩容只能买更大的机器(垂直扩展)
|
||||
- **难扩展**:一台机器顶住所有请求,CPU满载时就只能排队
|
||||
|
||||
<PhysicalServerDemo />
|
||||
|
||||
### 1.1 核心特点
|
||||
### 2.4 物理服务器时代的优缺点
|
||||
|
||||
- **单机部署**:所有应用运行在一台物理机上
|
||||
- **手动运维**:需要人工上架、布线、安装系统
|
||||
- **垂直扩展**:性能不够时只能买更强的机器
|
||||
| 维度 | 评价 |
|
||||
| ------------ | ------------------------------------------------------------ |
|
||||
| **优点** | 完全掌控硬件,性能可预测;没有虚拟化开销;数据物理隔离,安全性高 |
|
||||
| **缺点** | 采购周期长(数周);前期投入大(CapEx);资源利用率低;扩容困难 |
|
||||
| **适用场景** | 金融核心系统、政府涉密系统、对数据主权有严格要求的场景 |
|
||||
|
||||
### 1.2 痛点
|
||||
::: tip 💡 CapEx vs OpEx
|
||||
**CapEx**(Capital Expenditure):资本性支出,一次性投入大量资金购买硬件。
|
||||
|
||||
- **慢**:每次改代码都要手动上传,然后重启服务器
|
||||
- **贵**:扩容只能买更大的机器(垂直扩展)
|
||||
- **难扩展**:一台机器顶住所有请求,CPU 满载时就只能排队
|
||||
**OpEx**(Operating Expenditure):运营性支出,按使用量付费(如云服务器)。
|
||||
|
||||
### 1.3 扩展策略
|
||||
**比喻**:
|
||||
|
||||
<ScalingStrategyDemo />
|
||||
- CapEx:买房,一次性付几百万,之后每月只需交物业费
|
||||
- OpEx:租房,每月交房租,不用一次性掏大钱
|
||||
|
||||
### 1.4 物理服务器时代的优缺点
|
||||
|
||||
| 维度 | 评价 |
|
||||
|------|------|
|
||||
| **优点** | 完全掌控硬件,性能可预测;没有虚拟化开销;数据物理隔离,安全性高 |
|
||||
| **缺点** | 采购周期长(数周);前期投入大(CapEx);资源利用率低;扩容困难 |
|
||||
| **适用场景** | 金融核心系统、政府涉密系统、对数据主权有严格要求的场景 |
|
||||
**云时代**的启示:Serverless 和云服务让更多公司从 CapEx 转向 OpEx,降低创业门槛。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 单体架构时代 (2000s)
|
||||
## 3. 单体架构时代 (2000s)
|
||||
|
||||
随着框架的出现(Rails / Django / Spring),大家把所有功能都塞进一个应用里。
|
||||
### 3.1 什么是单体架构?
|
||||
|
||||
随着框架的出现(Rails / Django / Spring),大家把所有功能都塞进一个应用里。
|
||||
|
||||
::: tip 💡 通俗解释
|
||||
**单体架构**(Monolith)就像一个超级商场:
|
||||
|
||||
- 服装区、食品区、电器区都在同一栋楼里
|
||||
- 所有员工在一个管理系统里工作
|
||||
- 如果整栋楼停电,所有区域都停止营业
|
||||
|
||||
对比微服务就像商业街:每家店独立运营,一家店关门不影响其他店。
|
||||
:::
|
||||
|
||||
<MonolithDemo />
|
||||
|
||||
### 2.1 核心特点
|
||||
### 3.2 核心特点
|
||||
|
||||
- **单一代码库**:所有功能模块在同一个项目中
|
||||
- **共享数据库**:所有模块共用同一个数据库
|
||||
- **统一部署**:整个应用作为一个整体打包部署
|
||||
- **单一代码库**:所有功能模块在同一个项目中
|
||||
- **共享数据库**:所有模块共用同一个数据库
|
||||
- **统一部署**:整个应用作为一个整体打包部署
|
||||
|
||||
### 2.2 优点
|
||||
### 3.3 优点
|
||||
|
||||
- **开发简单**:一个项目搞定所有功能
|
||||
- **部署方便**:把一个大包扔到服务器上就行
|
||||
- **调试容易**:本地启动就能调试所有功能
|
||||
- **开发简单**:一个项目搞定所有功能
|
||||
- **部署方便**:把一个大包扔到服务器上就行
|
||||
- **调试容易**:本地启动就能调试所有功能
|
||||
|
||||
### 2.3 痛点:雪崩效应
|
||||
### 3.4 痛点:雪崩效应
|
||||
|
||||
想象一下,如果"切菜"的师傅不小心切到了手(代码出了 Bug),整个后厨都要停下来处理伤口,导致所有客人都吃不上饭。
|
||||
想象一下,如果"切菜"的师傅不小心切到了手(代码出了Bug),整个后厨都要停下来处理伤口,导致所有客人都吃不上饭。
|
||||
|
||||
这就是单体架构最大的风险:**隔离性差**。
|
||||
这就是单体架构最大的风险:**隔离性差**。
|
||||
|
||||
### 2.4 单体架构的优缺点与适用场景
|
||||
::: details 🚨 真实的雪崩案例
|
||||
某电商公司双十一大促:
|
||||
|
||||
| 维度 | 评价 |
|
||||
|------|------|
|
||||
| **优点** | 开发简单,无需考虑分布式复杂性;调试方便,本地启动即可调试全功能;部署简单,一个包即可运行;事务管理容易,单机数据库即可保证 ACID |
|
||||
| **缺点** | 代码耦合度高,随着业务增长代码膨胀;技术栈单一,难以局部升级;扩展困难,只能整体扩容;故障隔离差,一个模块故障影响全局;团队协作效率低,多人改同一套代码 |
|
||||
| **适用场景** | 初创公司 MVP 验证、小型团队(<10人)、业务相对简单、对交付速度要求高于扩展性的场景 |
|
||||
| **不适用场景** | 大型团队并行开发、需要频繁发布不同模块、某些模块需要独立扩容的场景 |
|
||||
- 订单服务因为某个商品的价格计算错误,抛出异常
|
||||
- 异常没有被正确捕获,导致线程池耗尽
|
||||
- 所有后续请求(包括商品浏览、搜索、用户登录)都被阻塞
|
||||
- 整个网站彻底瘫痪,持续1小时
|
||||
|
||||
### 2.5 部署流程演进
|
||||
**如果用微服务**:
|
||||
|
||||
<DeploymentFlowDemo />
|
||||
- 订单服务挂了,但商品浏览、搜索、用户登录仍然可用
|
||||
- 用户至少可以继续浏览商品,损失降到最低
|
||||
:::
|
||||
|
||||
### 2.6 单体架构的技术栈
|
||||
### 3.5 单体架构的优缺点与适用场景
|
||||
|
||||
在单体架构时代,开发者通常使用以下技术栈:
|
||||
| 维度 | 评价 |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **优点** | 开发简单,无需考虑分布式复杂性;调试方便,本地启动即可调试全功能;部署简单,一个包即可运行;事务管理容易,单机数据库即可保证ACID |
|
||||
| **缺点** | 代码耦合度高,随着业务增长代码膨胀;技术栈单一,难以局部升级;扩展困难,只能整体扩容;故障隔离差,一个模块故障影响全局;团队协作效率低,多人改同一套代码 |
|
||||
| **适用场景** | 初创公司MVP验证、小型团队(<10人)、业务相对简单、对交付速度要求高于扩展性的场景 |
|
||||
| **不适用场景** | 大型团队并行开发、需要频繁发布不同模块、某些模块需要独立扩容的场景 |
|
||||
|
||||
| 语言/框架 | 特点 | 代表企业 |
|
||||
|---------|------|---------|
|
||||
| **Java + Spring** | 企业级开发首选,生态完善 | 阿里巴巴、京东 |
|
||||
| **PHP + Laravel/ThinkPHP** | 快速开发,适合中小型项目 | 早期 Facebook、微博 |
|
||||
| **Python + Django/Flask** | 开发效率高,适合快速原型 | Instagram、Pinterest |
|
||||
| **Ruby on Rails** | 约定优于配置,初创公司最爱 | GitHub、Twitter(早期) |
|
||||
| **Node.js + Express** | 前后端统一语言,I/O 密集型场景 | Netflix、Uber |
|
||||
::: tip 🎯 初学者建议
|
||||
如果你正在学习后端开发,**强烈建议从单体架构开始**:
|
||||
|
||||
1. **先学会走路**:理解HTTP、数据库、基本的MVC架构
|
||||
2. **再考虑跑步**:当项目真的遇到扩展性问题,再考虑微服务
|
||||
3. **避免过度设计**:很多公司的"微服务"其实是"分布式单体",更难维护
|
||||
|
||||
**学习路径**:
|
||||
|
||||
- 阶段1:用 Spring Boot / Django / Rails 写一个完整的单体应用
|
||||
- 阶段2:遇到性能瓶颈时,尝试拆分出1-2个服务
|
||||
- 阶段3:当团队规模>50人,系统真的复杂了,再全面微服务化
|
||||
:::
|
||||
|
||||
### 3.6 单体架构的技术栈
|
||||
|
||||
| 语言/框架 | 特点 | 代表企业 |
|
||||
| -------------------------- | ---------------------------- | --------------------- |
|
||||
| **Java + Spring** | 企业级开发首选,生态完善 | 阿里巴巴、京东 |
|
||||
| **PHP + Laravel/ThinkPHP** | 快速开发,适合中小型项目 | 早期 Facebook、微博 |
|
||||
| **Python + Django/Flask** | 开发效率高,适合快速原型 | Instagram、Pinterest |
|
||||
| **Ruby on Rails** | 约定优于配置,初创公司最爱 | GitHub、Twitter(早期) |
|
||||
| **Node.js + Express** | 前后端统一语言,I/O密集型场景 | Netflix、Uber |
|
||||
|
||||
---
|
||||
|
||||
## 3. 容器化与微服务 (2010s)
|
||||
## 4. 容器化与微服务 (2010s)
|
||||
|
||||
### 3.1 Docker 容器化
|
||||
### 4.1 为什么需要微服务?
|
||||
|
||||
单体架构的痛点在2010年代集中爆发:
|
||||
|
||||
- **代码太庞大**:一个项目几百万行代码,新人入职要花一个月才能看懂
|
||||
- **部署太慢**:构建一次要30分钟,发布一次要小心翼翼
|
||||
- **协作太难**:100个开发者改同一个项目,代码冲突每天发生
|
||||
- **扩展太贵**:只需要扩展"聊天服务",却要复制整个应用
|
||||
|
||||
**微服务的核心思想**:把大应用拆成多个小服务,每个服务:
|
||||
|
||||
- 独立开发、独立部署
|
||||
- 有自己的数据库
|
||||
- 通过API通信
|
||||
|
||||
<ContainerDockerDemo />
|
||||
|
||||
Docker 就像是**集装箱**,它把每个小服务连同它的锅碗瓢盆(依赖库)一起打包。
|
||||
::: tip 💡 Docker是什么?
|
||||
**Docker**就像是"集装箱":
|
||||
|
||||
无论运到哪里(哪台服务器),打开集装箱就能直接开工,不用再重新安装环境。
|
||||
- 每个集装箱里有独立的货物(代码 + 依赖库 + 运行环境)
|
||||
- 无论运到哪里(哪台服务器),打开集装箱就能直接开工
|
||||
- 不用担心"我这台机器没有Python 3.9"、"那个机器缺少某个库"
|
||||
|
||||
### 3.2 技术栈时间线
|
||||
**比喻**:
|
||||
|
||||
- 没有 Docker:每次搬家,要把家具、电器、衣服一件件搬上卡车,到了新家再一件件摆好
|
||||
- 有 Docker:所有东西打包进集装箱,卡车直接运走,到了新家放下就能用
|
||||
|
||||
**核心价值**:"一次构建,到处运行"。
|
||||
:::
|
||||
|
||||
### 4.2 技术栈时间线
|
||||
|
||||
<TechStackTimelineDemo />
|
||||
|
||||
### 3.3 微服务架构
|
||||
### 4.3 微服务架构
|
||||
|
||||
<MicroservicesDemo />
|
||||
|
||||
为了解决单体的问题,我们把大厨房拆成了很多个小厨房(服务):
|
||||
为了解决单体的问题,我们把大厨房拆成了很多个小厨房(服务):
|
||||
|
||||
- 专门负责用户的服务
|
||||
- 专门负责订单的服务
|
||||
- 专门负责支付的服务
|
||||
|
||||
### 3.4 Kubernetes 编排
|
||||
<MicroservicesDemo />
|
||||
|
||||
### 4.4 Kubernetes 编排
|
||||
|
||||
当集装箱数量到达成百上千,就需要一个"港口调度系统":
|
||||
|
||||
- **Kubernetes (K8s)**:负责把容器安排到合适的机器上(调度、扩缩容、滚动更新)
|
||||
- **Service Mesh**:负责服务之间的交通规则(熔断、限流、重试、可观测)
|
||||
|
||||
<KubernetesDemo />
|
||||
|
||||
当集装箱数量到达成百上千,就需要一个"港口调度系统":
|
||||
::: tip 💡 什么是"编排"?
|
||||
**编排**(Orchestration)是指自动管理大量容器的系统。
|
||||
|
||||
- **Kubernetes (K8s)**:负责把容器安排到合适的机器上(调度、扩缩容、滚动更新)
|
||||
- **Service Mesh**:负责服务之间的交通规则(熔断、限流、重试、可观测)
|
||||
**比喻**:
|
||||
|
||||
**关键点**:微服务不是"拆开就好",真正的难点在于**治理和运维**。
|
||||
- 没有 K8s:你手动管理100个容器,哪个挂了要手动重启,哪个流量大了要手动加机器
|
||||
- 有 K8s:你告诉它"我要这个服务一直有10个实例运行",它会自动完成:
|
||||
- 哪台服务器资源充足,就把容器调度到那里
|
||||
- 容器挂了,自动重启
|
||||
- 流量大了,自动扩容到20个实例
|
||||
- 更新代码时,滚动更新(先停1个旧实例,启动1个新实例,逐个替换)
|
||||
|
||||
### 3.5 微服务与容器化的优缺点
|
||||
**关键点**:微服务不是"拆开就好",真正的难点在于**治理和运维**。
|
||||
:::
|
||||
|
||||
| 维度 | 评价 |
|
||||
|------|------|
|
||||
| **优点** | 服务独立部署,技术栈可异构;故障隔离,单个服务崩溃不影响全局;按需扩展,热点服务单独扩容;团队协作友好,不同团队负责不同服务;代码库更小,易于理解和维护 |
|
||||
| **缺点** | 分布式复杂性高(网络延迟、分布式事务、服务发现);运维成本高,需要专业的 DevOps 团队;调试困难,问题可能需要跨多个服务追踪;数据一致性难以保证;部署和监控基础设施要求复杂 |
|
||||
| **适用场景** | 大型团队(>50人)、业务复杂需要分模块独立演进、某些模块需要独立扩容、需要多语言技术栈、对可用性要求高的系统 |
|
||||
| **不适用场景** | 小型团队、业务简单、流量小且稳定、没有专业运维团队的情况 |
|
||||
### 4.5 微服务与容器化的优缺点
|
||||
|
||||
### 3.6 微服务技术栈
|
||||
| 维度 | 评价 |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **优点** | 服务独立部署,技术栈可异构;故障隔离,单个服务崩溃不影响全局;按需扩展,热点服务单独扩容;团队协作友好,不同团队负责不同服务;代码库更小,易于理解和维护 |
|
||||
| **缺点** | 分布式复杂性高(网络延迟、分布式事务、服务发现);运维成本高,需要专业的DevOps团队;调试困难,问题可能需要跨多个服务追踪;数据一致性难以保证;部署和监控基础设施要求复杂 |
|
||||
| **适用场景** | 大型团队(>50人)、业务复杂需要分模块独立演进、某些模块需要独立扩容、需要多语言技术栈、对可用性要求高的系统 |
|
||||
| **不适用场景** | 小型团队、业务简单、流量小且稳定、没有专业运维团队的情况 |
|
||||
|
||||
| 类别 | 技术/工具 | 作用 |
|
||||
|------|---------|------|
|
||||
| **容器化** | Docker, containerd | 应用打包与隔离 |
|
||||
| **编排调度** | Kubernetes, Docker Swarm | 容器管理与自动扩缩容 |
|
||||
| **服务发现** | Consul, etcd, ZooKeeper | 服务注册与发现 |
|
||||
| **API 网关** | Kong, Zuul, Envoy | 统一入口、路由、限流 |
|
||||
| **配置中心** | Apollo, Nacos, Spring Cloud Config | 集中配置管理 |
|
||||
| **监控告警** | Prometheus, Grafana, ELK | 指标监控与日志分析 |
|
||||
| **链路追踪** | Jaeger, Zipkin, SkyWalking | 分布式请求追踪 |
|
||||
| **服务网格** | Istio, Linkerd | 流量治理与安全 |
|
||||
::: details ⚠️ 微服务的陷阱
|
||||
**陷阱1:分布式单体**
|
||||
|
||||
拆了10个微服务,但它们之间紧密耦合:
|
||||
|
||||
- 服务A调用服务B,服务B调用服务C,服务C又调用服务A
|
||||
- 改一个功能,要同时改5个服务
|
||||
- 部署时,必须按顺序依次部署,否则系统报错
|
||||
|
||||
**这比单体更糟糕**:你拥有了单体的复杂性,又没有享受到微服务的独立部署好处。
|
||||
|
||||
**陷阱2:过度拆分**
|
||||
|
||||
把只有100行代码的功能也拆成一个独立服务:
|
||||
|
||||
- 10个服务,每个只有100行代码
|
||||
- 服务间通信的开销(网络序列化/反序列化)比实际业务逻辑还重
|
||||
- 运维成本爆炸:要部署、监控、日志收集10个服务
|
||||
|
||||
**正确做法**:从功能内聚的角度拆分,一个微服务应该是一个完整的业务能力(如"订单服务",而不是"订单创建服务"、"订单查询服务")。
|
||||
:::
|
||||
|
||||
### 4.6 微服务技术栈
|
||||
|
||||
| 类别 | 技术/工具 | 作用 |
|
||||
| ------------ | ---------------------------------- | -------------------- |
|
||||
| **容器化** | Docker, containerd | 应用打包与隔离 |
|
||||
| **编排调度** | Kubernetes, Docker Swarm | 容器管理与自动扩缩容 |
|
||||
| **服务发现** | Consul, etcd, ZooKeeper | 服务注册与发现 |
|
||||
| **API网关** | Kong, Zuul, Envoy | 统一入口、路由、限流 |
|
||||
| **配置中心** | Apollo, Nacos, Spring Cloud Config | 集中配置管理 |
|
||||
| **监控告警** | Prometheus, Grafana, ELK | 指标监控与日志分析 |
|
||||
| **链路追踪** | Jaeger, Zipkin, SkyWalking | 分布式请求追踪 |
|
||||
| **服务网格** | Istio, Linkerd | 流量治理与安全 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Serverless 与云原生时代 (2020s+)
|
||||
## 5. Serverless 与云原生时代 (2020s+)
|
||||
|
||||
微服务虽然好,但维护几十个小厨房还是很累。你需要担心:
|
||||
### 5.1 为什么需要 Serverless?
|
||||
|
||||
- 厨房够不够大?(服务器扩容)
|
||||
- 停电了怎么办?(高可用)
|
||||
- 容器太多怎么管?(运维成本)
|
||||
微服务虽然好,但维护几十个小厨房还是很累。你需要担心:
|
||||
|
||||
- 厨房够不够大?(服务器扩容)
|
||||
- 停电了怎么办?(高可用)
|
||||
- 容器太多怎么管?(运维成本)
|
||||
|
||||
<ServerlessDemo />
|
||||
|
||||
### 4.1 什么是 Serverless?
|
||||
::: tip 💡 Serverless 不是真的"没有服务器"
|
||||
**Serverless**的意思是"你不需要管理服务器",而不是真的没有服务器。
|
||||
|
||||
Serverless 并不是"没有服务器",而是**"你不需要管理服务器"**。
|
||||
**比喻**:
|
||||
|
||||
就像你现在不想自己做饭,也不想开饭馆,而是直接叫**外卖**。
|
||||
- **物理服务器时代**:你买地、盖房、装修、雇厨师、买食材...全部自己来
|
||||
- **云服务器时代**:你租一个已经装修好的餐厅,但自己雇厨师、管理运营
|
||||
- **Serverless时代**:你只需要设计菜单,云端有共享厨房,有专业厨师,你下单他们做,按次付费
|
||||
|
||||
- 你只需要写代码(下单)
|
||||
- 云厂商(美团)负责准备机器、运行代码、自动扩容
|
||||
- **按次付费**:代码跑了 100 毫秒,就收 100 毫秒的钱。没人访问就不收钱
|
||||
**核心变化**:
|
||||
|
||||
### 4.2 适用场景
|
||||
- 以前:买服务器 → 配环境 → 部署代码 → 监控 → 扩容 → 维护
|
||||
- 现在:写代码 → 上传 → 按使用量付费
|
||||
|
||||
Serverless 特别适合:
|
||||
**就像外卖**:你不需要厨房,只需要设计菜单,有人帮你做。
|
||||
:::
|
||||
|
||||
- **潮汐流量**:比如外卖软件,中午流量大,半夜没人。Serverless 会自动在中午为你分配 1000 台机器,半夜缩减到 0 台
|
||||
- **事件驱动**:比如"用户上传图片后,自动压缩图片"
|
||||
- **快速验证**:小团队、MVP、黑客松项目
|
||||
### 5.2 什么是 Serverless?
|
||||
|
||||
### 4.3 BaaS 组合拳
|
||||
**Serverless = FaaS + BaaS**
|
||||
|
||||
Serverless 的真正力量来自于 **BaaS (Backend as a Service)**:
|
||||
**FaaS**(Function as a Service,函数即服务):
|
||||
|
||||
- 登录 -> Auth0 / Supabase Auth
|
||||
- 支付 -> Stripe
|
||||
- 数据库 -> Supabase / Firebase / DynamoDB
|
||||
- 消息 -> Kafka / SQS
|
||||
- 你只写函数(如"用户注册时发送欢迎邮件")
|
||||
- 云厂商负责运行这个函数,自动扩缩容
|
||||
- 典型代表:AWS Lambda、阿里云函数计算
|
||||
|
||||
**关键点**:Serverless 让后端越来越像"搭积木"。
|
||||
**BaaS**(Backend as a Service,后端即服务):
|
||||
|
||||
### 4.4 Serverless 与云原生的优缺点
|
||||
- 登录 → Auth0 / Supabase Auth
|
||||
- 支付 → Stripe
|
||||
- 数据库 → Supabase / Firebase / DynamoDB
|
||||
- 消息 → Kafka / SQS
|
||||
|
||||
| 维度 | 评价 |
|
||||
|------|------|
|
||||
| **优点** | 零运维成本,开发者只需关注业务代码;自动扩缩容,完美应对流量峰值;按需付费,无流量时成本接近零;快速上线,几分钟即可部署全球;高可用内置,云服务自动处理故障转移 |
|
||||
| **缺点** | 冷启动延迟(几百毫秒到数秒);运行时长限制(通常5-15分钟);调试困难,本地难以完全模拟云环境;供应商锁定风险;不适合长时间运行或计算密集型任务;成本在高频持续流量下可能反超传统方案 |
|
||||
| **适用场景** | 事件驱动处理(图片处理、消息通知);潮汐流量应用(活动页、促销);快速原型验证和MVP;低频API或后台任务;无专职运维团队的小团队 |
|
||||
| **不适用场景** | 需要持续低延迟的应用;长时间计算任务;对冷启动敏感的场景(高频交易);需要精细控制底层基础设施的场景 |
|
||||
::: tip 🎯 Serverless 适用场景
|
||||
**最佳场景**:
|
||||
|
||||
### 4.5 Serverless 技术栈与平台
|
||||
1. **潮汐流量**:外卖软件,中午流量大,半夜没人。Serverless会自动在中午分配1000台机器,半夜缩减到0台
|
||||
2. **事件驱动**:"用户上传图片后,自动压缩图片"
|
||||
3. **快速验证**:小团队、MVP、黑客松项目
|
||||
|
||||
| 类别 | 技术/平台 | 特点 |
|
||||
|------|---------|------|
|
||||
| **FaaS 平台** | AWS Lambda | 最早的 FaaS 服务,生态最成熟 |
|
||||
| | Azure Functions | 微软云集成度高,.NET 友好 |
|
||||
| | Google Cloud Functions | 与 GCP 服务深度集成 |
|
||||
| | 阿里云函数计算 | 国内生态完善,冷启动优化好 |
|
||||
| | 腾讯云云函数 | 与微信生态整合 |
|
||||
| | Vercel/Netlify Functions | 前端开发者友好,边缘部署 |
|
||||
| **BaaS 服务** | Firebase | Google 的移动端后端方案 |
|
||||
| | Supabase | PostgreSQL 的 Firebase 开源替代 |
|
||||
| | AWS Amplify | AWS 的移动和 Web 应用开发平台 |
|
||||
| **部署工具** | Serverless Framework | 多云部署,社区活跃 |
|
||||
| | Terraform | 基础设施即代码 |
|
||||
| | Pulumi | 用编程语言定义基础设施 |
|
||||
**不适合场景**:
|
||||
|
||||
1. **长时间运行的任务**:视频转码(可能跑1小时,函数最大执行时间通常只有15分钟)
|
||||
2. **需要低延迟的应用**:高频交易(冷启动延迟可能几十毫秒到几秒)
|
||||
3. **需要精细控制底层**:操作系统内核调优、GPU直接访问
|
||||
:::
|
||||
|
||||
### 5.3 Serverless 与云原生的优缺点
|
||||
|
||||
| 维度 | 评价 |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **优点** | 零运维成本,开发者只需关注业务代码;自动扩缩容,完美应对流量峰值;按需付费,无流量时成本接近零;快速上线,几分钟即可部署全球;高可用内置,云服务自动处理故障转移 |
|
||||
| **缺点** | 冷启动延迟(几百毫秒到数秒);运行时长限制(通常5-15分钟);调试困难,本地难以完全模拟云环境;供应商锁定风险;不适合长时间运行或计算密集型任务;成本在高频持续流量下可能反超传统方案 |
|
||||
| **适用场景** | 事件驱动处理(图片处理、消息通知);潮汐流量应用(活动页、促销);快速原型验证和MVP;低频API或后台任务;无专职运维团队的小团队 |
|
||||
| **不适用场景** | 需要持续低延迟的应用;长时间计算任务;对冷启动敏感的场景(高频交易);需要精细控制底层基础设施的场景 |
|
||||
|
||||
::: details 💰 成本对比:何时Serverless更贵?
|
||||
**场景1:低频访问**
|
||||
|
||||
- 传统服务器:每月$20(不管有没有人访问)
|
||||
- Serverless:100万次请求 × $0.0002/次 = $20(仅在有流量时付费)
|
||||
- **结论**:低频场景,Serverless更省钱
|
||||
|
||||
**场景2:高频持续访问**
|
||||
|
||||
- 传统服务器:每月$20
|
||||
- Serverless:1亿次请求 × $0.0002/次 = $20,000
|
||||
- **结论**:高频持续场景,传统服务器更省钱
|
||||
|
||||
**场景3:潮汐流量**
|
||||
|
||||
- 传统服务器:为了应对峰值,需要$100/月的服务器(平时资源利用率只有10%)
|
||||
- Serverless:峰值时$20,平时几乎$0
|
||||
- **结论**:潮汐流量场景,Serverless节省成本
|
||||
|
||||
**启示**:不要盲目上Serverless,要根据实际流量特征做成本测算。
|
||||
:::
|
||||
|
||||
### 5.4 Serverless 技术栈与平台
|
||||
|
||||
| 类别 | 技术/平台 | 特点 |
|
||||
| ------------ | ------------------------ | ---------------------------- |
|
||||
| **FaaS平台** | AWS Lambda | 最早的FaaS服务,生态最成熟 |
|
||||
| | Azure Functions | 微软云集成度高,.NET友好 |
|
||||
| | Google Cloud Functions | 与GCP服务深度集成 |
|
||||
| | 阿里云函数计算 | 国内生态完善,冷启动优化好 |
|
||||
| | 腾讯云云函数 | 与微信生态整合 |
|
||||
| | Vercel/Netlify Functions | 前端开发者友好,边缘部署 |
|
||||
| **BaaS服务** | Firebase | Google的移动端后端方案 |
|
||||
| | Supabase | PostgreSQL的Firebase开源替代 |
|
||||
| | AWS Amplify | AWS的移动和Web应用开发平台 |
|
||||
| **部署工具** | Serverless Framework | 多云部署,社区活跃 |
|
||||
| | Terraform | 基础设施即代码 |
|
||||
| | Pulumi | 用编程语言定义基础设施 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 各架构阶段对比与选型指南
|
||||
## 6. 各架构阶段对比与选型指南
|
||||
|
||||
### 5.1 架构演进全景对比
|
||||
### 6.1 架构演进全景对比
|
||||
|
||||
| 维度 | 物理服务器 | 单体架构 | 微服务+容器 | Serverless |
|
||||
|------|-----------|---------|------------|-------------|
|
||||
| **团队规模** | 1-5人 | 5-50人 | 50-500人 | 1-20人 |
|
||||
| **部署复杂度** | 极高 | 低 | 极高 | 极低 |
|
||||
| **运维成本** | 高 | 中 | 很高 | 低 |
|
||||
| **扩展性** | 差 | 垂直扩展有限 | 水平扩展优秀 | 自动扩展 |
|
||||
| **技术栈灵活性** | 无 | 单一 | 多样化 | 受限 |
|
||||
| **冷启动** | 无 | 无 | 容器启动时间 | 有延迟 |
|
||||
| **适用场景** | 遗留系统、特殊合规要求 | 初创公司、业务简单 | 大型互联网公司、复杂业务 | 快速验证、事件驱动 |
|
||||
<ArchitectureComparisonDemo />
|
||||
|
||||
### 5.2 技术选型决策树
|
||||
| 维度 | 物理服务器 | 单体架构 | 微服务+容器 | Serverless |
|
||||
| ---------------- | ---------------------- | ------------------ | ------------------------ | ------------------ |
|
||||
| **团队规模** | 1-5人 | 5-50人 | 50-500人 | 1-20人 |
|
||||
| **部署复杂度** | 极高 | 低 | 极高 | 极低 |
|
||||
| **运维成本** | 高 | 中 | 很高 | 低 |
|
||||
| **扩展性** | 差 | 垂直扩展有限 | 水平扩展优秀 | 自动扩展 |
|
||||
| **技术栈灵活性** | 无 | 单一 | 多样化 | 受限 |
|
||||
| **冷启动** | 无 | 无 | 容器启动时间 | 有延迟 |
|
||||
| **适用场景** | 遗留系统、特殊合规要求 | 初创公司、业务简单 | 大型互联网公司、复杂业务 | 快速验证、事件驱动 |
|
||||
|
||||
### 6.2 技术选型决策树
|
||||
|
||||
```
|
||||
开始选型
|
||||
│
|
||||
├─ 团队有专业运维人员?
|
||||
├─ 团队有专业运维人员?
|
||||
│ ├─ 是 → 考虑微服务或物理机
|
||||
│ └─ 否 → 继续判断
|
||||
│
|
||||
├─ 需要快速上线验证想法?
|
||||
├─ 需要快速上线验证想法?
|
||||
│ ├─ 是 → Serverless 或单体
|
||||
│ └─ 否 → 继续判断
|
||||
│
|
||||
├─ 团队规模 > 50人?
|
||||
├─ 团队规模 > 50人?
|
||||
│ ├─ 是 → 考虑微服务
|
||||
│ └─ 否 → 继续判断
|
||||
│
|
||||
├─ 流量有明显峰谷特征?
|
||||
├─ 流量有明显峰谷特征?
|
||||
│ ├─ 是 → Serverless
|
||||
│ └─ 否 → 单体架构(推荐初创)
|
||||
│ └─ 否 → 单体架构(推荐初创)
|
||||
│
|
||||
└─ 特殊要求(合规、遗留系统)?
|
||||
└─ 特殊要求(合规、遗留系统)?
|
||||
└─ 是 → 物理服务器
|
||||
```
|
||||
|
||||
### 5.3 不同场景下的推荐架构
|
||||
::: tip 🎯 初学者选型建议
|
||||
**如果你是个开发者或小团队:**
|
||||
|
||||
#### 场景一:独立开发者/兼职项目
|
||||
- **推荐架构**:Serverless (Vercel/Netlify) 或 单体应用
|
||||
- **理由**:几乎零运维成本,按需付费,快速上线
|
||||
- **示例技术栈**:Next.js + Vercel + Supabase
|
||||
1. **阶段0 (学习)**:本地跑单体应用,理解HTTP、数据库、基本架构
|
||||
2. **阶段1 (MVP)**:部署单体应用到云服务器(如阿里云ECS、AWS EC2)
|
||||
3. **阶段2 (增长)**:当团队>10人、业务变复杂,考虑拆分出1-2个微服务
|
||||
4. **阶段3 (成熟)**:当团队>50人、流量百万级,全面微服务化
|
||||
|
||||
#### 场景二:初创公司 MVP 验证
|
||||
- **推荐架构**:单体架构 + 云服务器
|
||||
- **理由**:开发速度快,团队可以专注于业务逻辑而非基础设施
|
||||
- **示例技术栈**:Spring Boot / Django / Rails + RDS + ECS
|
||||
**关键原则**:不要一开始就上微服务,那是"过早优化"。让架构随业务成长而演进。
|
||||
:::
|
||||
|
||||
#### 场景三:成长型公司(10-50人团队)
|
||||
- **推荐架构**:模块化单体 或 轻量级微服务
|
||||
- **理由**:开始面临代码耦合问题,但还不需要完整的微服务复杂度
|
||||
- **示例技术栈**:Spring Cloud / Go Micro + Kubernetes
|
||||
### 6.3 不同场景下的推荐架构
|
||||
|
||||
#### 场景四:大型互联网公司
|
||||
- **推荐架构**:微服务 + 服务网格 + 中台架构
|
||||
- **理由**:团队规模大,业务复杂,需要独立的发布节奏和技术栈
|
||||
- **示例技术栈**:自研 RPC 框架 + Istio + 自建 PaaS 平台
|
||||
#### 场景一:独立开发者/兼职项目
|
||||
|
||||
#### 场景五:事件驱动/潮汐流量应用
|
||||
- **推荐架构**:Serverless + 事件总线
|
||||
- **理由**:流量波动大,需要极致的成本优化和自动扩缩容
|
||||
- **示例技术栈**:AWS Lambda + API Gateway + EventBridge
|
||||
- **推荐架构**:Serverless (Vercel/Netlify) 或 单体应用
|
||||
- **理由**:几乎零运维成本,按需付费,快速上线
|
||||
- **示例技术栈**:Next.js + Vercel + Supabase
|
||||
|
||||
#### 场景二:初创公司MVP验证
|
||||
|
||||
- **推荐架构**:单体架构 + 云服务器
|
||||
- **理由**:开发速度快,团队可以专注于业务逻辑而非基础设施
|
||||
- **示例技术栈**:Spring Boot / Django / Rails + RDS + ECS
|
||||
|
||||
#### 场景三:成长型公司(10-50人团队)
|
||||
|
||||
- **推荐架构**:模块化单体 或 轻量级微服务
|
||||
- **理由**:开始面临代码耦合问题,但还不需要完整的微服务复杂度
|
||||
- **示例技术栈**:Spring Cloud / Go Micro + Kubernetes
|
||||
|
||||
#### 场景四:大型互联网公司
|
||||
|
||||
- **推荐架构**:微服务 + 服务网格 + 中台架构
|
||||
- **理由**:团队规模大,业务复杂,需要独立的发布节奏和技术栈
|
||||
- **示例技术栈**:自研RPC框架 + Istio + 自建PaaS平台
|
||||
|
||||
#### 场景五:事件驱动/潮汐流量应用
|
||||
|
||||
- **推荐架构**:Serverless + 事件总线
|
||||
- **理由**:流量波动大,需要极致的成本优化和自动扩缩容
|
||||
- **示例技术栈**:AWS Lambda + API Gateway + EventBridge
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结与学习路线
|
||||
## 7. 总结与学习路线
|
||||
|
||||
后端架构的演进,本质上是在做**加法**和**减法**:
|
||||
### 7.1 核心要点
|
||||
|
||||
| 时代 | 架构 | 开发者要做的事 | 运维要做的事 |
|
||||
| :--- | :----- | :--------------- | :-------------------- |
|
||||
| **物理时代** | 单机 | 写脚本、手动部署 | 维护机房与硬件 |
|
||||
| **单体时代** | 一整块 | 写所有业务逻辑 | 维护几台大服务器 |
|
||||
| **微服务时代** | 拆分 | 关注单一业务 | 维护 K8s 集群 (很累!) |
|
||||
| **Serverless** | 函数 | 只写核心函数 | 喝茶 (云厂商全包了) |
|
||||
后端架构的演进,本质上是在做**加法**和**减法**:
|
||||
|
||||
**下一步建议**:
|
||||
| 时代 | 架构 | 开发者要做的事 | 运维要做的事 |
|
||||
| :------------- | :----- | :--------------- | :----------------- |
|
||||
| **物理时代** | 单机 | 写脚本、手动部署 | 维护机房与硬件 |
|
||||
| **单体时代** | 一整块 | 写所有业务逻辑 | 维护几台大服务器 |
|
||||
| **微服务时代** | 拆分 | 关注单一业务 | 维护K8s集群(很累!) |
|
||||
| **Serverless** | 函数 | 只写核心函数 | 喝茶(云厂商全包了) |
|
||||
|
||||
- 想打基础:学会 HTTP、数据库、缓存、消息队列
|
||||
- 想上手实践:用 Docker 跑一个小项目,再部署到云端
|
||||
- 想更专业:了解 K8s、监控体系、CI/CD 流水线
|
||||
**关键洞察**:
|
||||
|
||||
未来的后端开发,将越来越像"搭积木"——你只需要关注**业务逻辑**,底层的脏活累活,全部交给云。
|
||||
- 架构演进不是"新技术取代旧技术",而是**适用场景的变化**
|
||||
- 没有银弹,每个架构都有其适用的边界
|
||||
- 选择架构要考虑:团队规模、业务复杂度、流量特征、运维能力
|
||||
|
||||
### 6.2 学习路线建议
|
||||
### 7.2 学习路线建议
|
||||
|
||||
根据你的职业阶段,推荐以下学习路径:
|
||||
根据你的职业阶段,推荐以下学习路径:
|
||||
|
||||
#### 阶段一:打好基础(0-1年)
|
||||
**目标**:理解后端核心概念,能独立开发单体应用
|
||||
#### 阶段一:打好基础(0-1年)
|
||||
|
||||
- 掌握一门后端语言(Java/Python/Go 任选其一)
|
||||
- 学习 HTTP 协议和 RESTful API 设计
|
||||
- 掌握关系型数据库(MySQL/PostgreSQL)
|
||||
- 了解缓存基础(Redis)
|
||||
- 学习 Git 和基础 Linux 命令
|
||||
- **实践项目**:用单体架构完成一个 CRUD 应用(如博客系统、待办事项)
|
||||
**目标**:理解后端核心概念,能独立开发单体应用
|
||||
|
||||
#### 阶段二:扩展能力(1-3年)
|
||||
**目标**:理解分布式系统,能参与微服务开发
|
||||
- 掌握一门后端语言(Java/Python/Go任选其一)
|
||||
- 学习HTTP协议和RESTful API设计
|
||||
- 掌握关系型数据库(MySQL/PostgreSQL)
|
||||
- 了解缓存基础(Redis)
|
||||
- 学习Git和基础Linux命令
|
||||
- **实践项目**:用单体架构完成一个CRUD应用(如博客系统、待办事项)
|
||||
|
||||
#### 阶段二:扩展能力(1-3年)
|
||||
|
||||
**目标**:理解分布式系统,能参与微服务开发
|
||||
|
||||
- 深入学习微服务架构和拆分策略
|
||||
- 掌握 Docker 和 Kubernetes 基础
|
||||
- 学习消息队列(Kafka/RabbitMQ)
|
||||
- 掌握Docker和Kubernetes基础
|
||||
- 学习消息队列(Kafka/RabbitMQ)
|
||||
- 了解分布式事务和一致性
|
||||
- 掌握监控和日志(Prometheus/ELK)
|
||||
- **实践项目**:将单体应用拆分为 3-5 个微服务,使用 Docker 部署
|
||||
- 掌握监控和日志(Prometheus/ELK)
|
||||
- **实践项目**:将单体应用拆分为3-5个微服务,使用Docker部署
|
||||
|
||||
#### 阶段三:专业深化(3-5年)
|
||||
**目标**:能设计大型系统,具备技术选型能力
|
||||
#### 阶段三:专业深化(3-5年)
|
||||
|
||||
- 深入理解云原生架构(Service Mesh、Serverless)
|
||||
**目标**:能设计大型系统,具备技术选型能力
|
||||
|
||||
- 深入理解云原生架构(Service Mesh、Serverless)
|
||||
- 掌握容量规划和性能调优
|
||||
- 了解多活架构和灾备设计
|
||||
- 学习 DDD(领域驱动设计)
|
||||
- 学习DDD(领域驱动设计)
|
||||
- 培养技术判断力和架构思维
|
||||
- **实践项目**:设计一个支持百万级用户的系统架构,包含高可用、弹性伸缩等方案
|
||||
- **实践项目**:设计一个支持百万级用户的系统架构,包含高可用、弹性伸缩等方案
|
||||
|
||||
#### 持续学习资源推荐
|
||||
### 7.3 持续学习资源推荐
|
||||
|
||||
**书籍**
|
||||
- 《设计数据密集型应用》(DDIA)- 分布式系统必读
|
||||
**书籍**:
|
||||
|
||||
- 《设计数据密集型应用》(DDIA)- 分布式系统必读
|
||||
- 《云原生模式》
|
||||
- 《微服务设计》
|
||||
- 《领域驱动设计》
|
||||
|
||||
**在线资源**
|
||||
**在线资源**:
|
||||
|
||||
- AWS/Azure/阿里云官方架构文档
|
||||
- CNCF(云原生计算基金会)项目文档
|
||||
- 各大公司技术博客(Netflix Tech Blog、阿里技术公众号等)
|
||||
|
||||
### 6.3 架构选型的核心原则
|
||||
|
||||
记住以下原则,帮助你在实际工作中做出正确的选择:
|
||||
|
||||
1. **没有银弹**:不存在最好的架构,只有最适合当前场景的架构
|
||||
2. **演进优于完美**:先让系统跑起来,再逐步优化,不要过度设计
|
||||
3. **团队能力优先**:选择团队熟悉和能驾驭的技术,而不是最新最酷的技术
|
||||
4. **成本意识**:计算总体拥有成本(TCO),包括开发、运维、培训等
|
||||
5. **可回退性**:设计时考虑回退方案,微服务可以合并回单体,但很难拆分
|
||||
- CNCF(云原生计算基金会)项目文档
|
||||
- 各大公司技术博客(Netflix Tech Blog、阿里技术公众号等)
|
||||
|
||||
---
|
||||
|
||||
## 7. 名词速查表 (Glossary)
|
||||
## 8. 名词速查表(Glossary)
|
||||
|
||||
| 名词 | 全称 | 解释 |
|
||||
| :---------------- | :-------------------------------- | :--------------------------------------------------- |
|
||||
| **Backend** | - | 服务器端系统,负责处理业务逻辑、数据存储和对外接口 |
|
||||
| **CGI** | Common Gateway Interface | 早期动态网页技术,通过脚本处理请求并返回结果 |
|
||||
| **Monolith** | - | 单体架构,把所有业务逻辑打包在同一个应用中 |
|
||||
| **Microservices** | - | 微服务架构,把业务拆分成多个独立服务 |
|
||||
| **Container** | - | 容器化技术,把应用和依赖打包成可移植单元 |
|
||||
| **K8s** | Kubernetes | 容器编排平台,用于调度、扩缩容和治理容器 |
|
||||
| **Service Mesh** | - | 服务网格,负责微服务间通信治理、观测与安全 |
|
||||
| **Serverless** | - | 无服务计算,开发者只写函数,平台自动运行与扩缩容 |
|
||||
| **BaaS** | Backend as a Service | 即插即用的后端云服务(认证、数据库、支付等) |
|
||||
| **CI/CD** | Continuous Integration / Delivery | 持续集成与持续交付,自动化测试与部署流程 |
|
||||
| **Observability** | - | 可观测性,利用日志/指标/追踪理解系统运行状态 |
|
||||
| 名词 | 全称 | 解释 |
|
||||
| :---------------- | :-------------------------------- | :------------------------------------------------ |
|
||||
| **Backend** | - | 服务器端系统,负责处理业务逻辑、数据存储和对外接口 |
|
||||
| **CGI** | Common Gateway Interface | 早期动态网页技术,通过脚本处理请求并返回结果 |
|
||||
| **Monolith** | - | 单体架构,把所有业务逻辑打包在同一个应用中 |
|
||||
| **Microservices** | - | 微服务架构,把业务拆分成多个独立服务 |
|
||||
| **Container** | - | 容器化技术,把应用和依赖打包成可移植单元 |
|
||||
| **K8s** | Kubernetes | 容器编排平台,用于调度、扩缩容和治理容器 |
|
||||
| **Service Mesh** | - | 服务网格,负责微服务间通信治理、观测与安全 |
|
||||
| **Serverless** | - | 无服务计算,开发者只写函数,平台自动运行与扩缩容 |
|
||||
| **BaaS** | Backend as a Service | 即插即用的后端云服务(认证、数据库、支付等) |
|
||||
| **CI/CD** | Continuous Integration / Delivery | 持续集成与持续交付,自动化测试与部署流程 |
|
||||
| **Observability** | - | 可观测性,利用日志/指标/追踪理解系统运行状态 |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+154
-571
@@ -1,94 +1,112 @@
|
||||
# Canvas 2D 入门:从像素到动画(交互式教程)
|
||||
# Canvas 2D 入门:从像素到动画
|
||||
|
||||
> **学习指南**:本章节无需深厚的前端基础,通过交互式演示带你掌握 Canvas 2D 的核心原理和实践技巧。我们将从最基础的绘制开始,一直到构建复杂的交互式图形应用。
|
||||
::: tip 🎯 核心问题
|
||||
**如何在网页上画图、做动画、甚至开发游戏?** Canvas 提供了一个强大的 2D 绘图能力,让你用代码创造视觉内容。
|
||||
:::
|
||||
|
||||
## 0. 引言:Canvas 是什么
|
||||
---
|
||||
|
||||
Canvas(画布)是 HTML5 提供的一个通过 JavaScript 绘制 2D 图形的元素。你可以把它想象成一张**数字画布**,上面可以用代码"画"出任何东西:简单的形状、复杂的图表、流畅的动画,甚至是完整的游戏。
|
||||
## 1. 为什么要学 Canvas?
|
||||
|
||||
### 0.1 Canvas vs SVG:有什么区别?
|
||||
### 1.1 Canvas 是什么?
|
||||
|
||||
在 Web 开发中,绘制图形主要有两种方式:Canvas 和 SVG(Scalable Vector Graphics)。它们各有优劣:
|
||||
**Canvas (画布)** 是 HTML5 提供的一个通过 JavaScript 绘制 2D 图形的元素。
|
||||
|
||||
你可以把它想象成一张**数字画布**:
|
||||
|
||||
- 🖌️ 你可以用代码"画笔"在上面作画
|
||||
- 🎨 可以画任何东西: 简单的形状、复杂的图表、流畅的动画
|
||||
- 🎮 甚至可以做成完整的游戏
|
||||
|
||||
::: tip 💡 Canvas vs SVG:有什么区别?
|
||||
|
||||
在 Web 开发中,绘制图形主要有两种方式:
|
||||
|
||||
| 特性 | Canvas | SVG |
|
||||
| :------- | :------------------- | :-------------------- |
|
||||
| **类型** | 位图(光栅图形) | 矢量图形 |
|
||||
| -------- | -------------------- | --------------------- |
|
||||
| **类型** | 位图(光栅图形) | 矢量图形 |
|
||||
| **DOM** | 单个 `<canvas>` 元素 | 每个图形都是 DOM 元素 |
|
||||
| **交互** | 需要手动计算碰撞 | 天然支持事件绑定 |
|
||||
| **性能** | 适合大量对象 | 适合少量复杂对象 |
|
||||
| **缩放** | 放大会失真 | 无限缩放不失真 |
|
||||
| **应用** | 游戏、数据可视化 | 图标、插画 |
|
||||
|
||||
**简单总结**:
|
||||
**简单总结**:
|
||||
|
||||
- **Canvas** = 像素画,画完就变成像素,性能好但交互麻烦
|
||||
- **SVG** = 矢量图,每个图形都是对象,交互方便但对象多了会慢
|
||||
- **Canvas** = 像素画,画完就变成像素,性能好但交互麻烦
|
||||
- **SVG** = 矢量图,每个图形都是对象,交互方便但对象多了会慢
|
||||
:::
|
||||
|
||||
### 0.2 Canvas 的应用场景
|
||||
### 1.2 Canvas 的应用场景
|
||||
|
||||
Canvas 的用途非常广泛,你可能在很多地方都见过它:
|
||||
Canvas 的用途非常广泛,你可能每天都在用:
|
||||
|
||||
1. **数据可视化**:折线图、饼图、热力图(如 ECharts、Chart.js)
|
||||
2. **游戏开发**:网页游戏(如 Phaser.js 引擎)
|
||||
3. **图像处理**:图片裁剪、滤镜、拼图(如 Fabric.js)
|
||||
4. **创意效果**:粒子特效、动画背景(如 Three.js 的 2D 渲染)
|
||||
5. **工程绘图**:CAD、流程图、思维导图
|
||||
1. **数据可视化**: ECharts、Chart.js 的图表
|
||||
2. **游戏开发**: 网页游戏(如 Phaser.js 引擎)
|
||||
3. **图像处理**: 图片裁剪、滤镜、拼图(如 Fabric.js)
|
||||
4. **创意效果**: 粒子特效、动画背景
|
||||
5. **工程绘图**: CAD、流程图、思维导图
|
||||
|
||||
---
|
||||
|
||||
## 1. Canvas 基础
|
||||
## 2. Canvas 基础
|
||||
|
||||
### 1.1 Canvas 元素和上下文
|
||||
### 2.1 Canvas 元素和上下文
|
||||
|
||||
使用 Canvas 的第一步是在 HTML 中创建一个 `<canvas>` 元素:
|
||||
使用 Canvas 的第一步是在 HTML 中创建一个 `<canvas>` 元素:
|
||||
|
||||
```html
|
||||
<canvas id="myCanvas" width="600" height="400"></canvas>
|
||||
```
|
||||
|
||||
然后通过 JavaScript 获取**渲染上下文(Rendering Context)**:
|
||||
然后通过 JavaScript 获取**渲染上下文 (Rendering Context)**:
|
||||
|
||||
```javascript
|
||||
const canvas = document.getElementById('myCanvas')
|
||||
const ctx = canvas.getContext('2d') // 获取 2D 上下文
|
||||
```
|
||||
|
||||
**关键概念**:
|
||||
::: tip 💡 关键概念
|
||||
|
||||
- `canvas` 是 DOM 元素,控制画布的大小和位置
|
||||
- `ctx` 是绘图工具,所有的绘制操作都通过它完成
|
||||
- `'2d'` 表示使用 2D 渲染上下文(WebGL 使用 `'webgl'`)
|
||||
- **canvas** 是 DOM 元素,控制画布的大小和位置
|
||||
- **ctx** 是绘图工具,所有的绘制操作都通过它完成
|
||||
- **`"2d"`** 表示使用 2D 渲染上下文(WebGL 使用 `"webgl"`)
|
||||
:::
|
||||
|
||||
> 🕹️ **交互演示**:点击下方按钮,体验 Canvas 的基本绘图操作。
|
||||
### 2.2 坐标系统:Canvas 的"地图规则"
|
||||
|
||||
<CanvasBasicsDemo />
|
||||
Canvas 使用的是**屏幕坐标系**,这与传统数学坐标系有所不同:
|
||||
|
||||
### 1.2 坐标系统
|
||||
|
||||
Canvas 使用的是**屏幕坐标系**,这与传统数学坐标系有所不同:
|
||||
|
||||
- **原点 (0, 0)**:在左上角(不是中心)
|
||||
- **X 轴**:向右为正方向
|
||||
- **Y 轴**:向下为正方向(注意:数学坐标系中 Y 轴向上)
|
||||
- **单位**:像素(px)
|
||||
- **原点 (0, 0)**: 在**左上角**(不是中心)
|
||||
- **X 轴**: 向右为正方向
|
||||
- **Y 轴**: **向下**为正方向(注意: 数学坐标系中 Y 轴向上)
|
||||
- **单位**: 像素 (px)
|
||||
|
||||
```javascript
|
||||
// 在左上角绘制一个点
|
||||
// 在左上角绘制一个矩形
|
||||
ctx.fillRect(0, 0, 10, 10)
|
||||
|
||||
// 在右下角绘制一个点
|
||||
// 在右下角绘制一个矩形
|
||||
ctx.fillRect(canvas.width - 10, canvas.height - 10, 10, 10)
|
||||
```
|
||||
|
||||
> 🕹️ **交互演示**:拖动下方的点,感受 Canvas 的坐标系统。
|
||||
::: tip 💡 记忆技巧
|
||||
|
||||
<CoordinateSystemDemo />
|
||||
想象你在看**屏幕**:
|
||||
|
||||
### 1.3 绘制基本形状
|
||||
- 向右移 → X 增加 ✅
|
||||
- 向下移(滚动页面) → Y 增加 ✅
|
||||
- 向左移 → X 减少
|
||||
- 向上移(向上滚动) → Y 减少
|
||||
|
||||
Canvas 提供了几种绘制基本形状的方法:
|
||||
这就是 Canvas 的坐标规则。
|
||||
:::
|
||||
|
||||
#### 矩形
|
||||
### 2.3 绘制基本形状
|
||||
|
||||
Canvas 提供了几种绘制基本形状的方法:
|
||||
|
||||
**矩形**:
|
||||
|
||||
```javascript
|
||||
// 填充矩形
|
||||
@@ -104,7 +122,7 @@ ctx.strokeRect(x, y, width, height)
|
||||
ctx.clearRect(x, y, width, height)
|
||||
```
|
||||
|
||||
#### 圆形
|
||||
**圆形**:
|
||||
|
||||
```javascript
|
||||
ctx.beginPath()
|
||||
@@ -112,18 +130,16 @@ ctx.arc(x, y, radius, startAngle, endAngle)
|
||||
ctx.fill() // 或 ctx.stroke()
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
**参数说明**:
|
||||
|
||||
- `x, y`:圆心坐标
|
||||
- `radius`:半径
|
||||
- `startAngle, endAngle`:起始和结束角度(弧度制)
|
||||
- **x, y**: 圆心坐标
|
||||
- **radius**: 半径
|
||||
- **startAngle, endAngle**: 起始和结束角度(弧度制)
|
||||
- `0` = 3 点钟方向
|
||||
- `Math.PI / 2` = 6 点钟方向
|
||||
- `Math.PI` = 9 点钟方向
|
||||
- `Math.PI * 1.5` = 12 点钟方向
|
||||
- `Math.PI * 2` = 回到 3 点钟方向
|
||||
|
||||
#### 线条
|
||||
**线条**:
|
||||
|
||||
```javascript
|
||||
ctx.beginPath()
|
||||
@@ -132,15 +148,15 @@ ctx.lineTo(x2, y2) // 终点
|
||||
ctx.stroke()
|
||||
```
|
||||
|
||||
### 1.4 颜色和渐变
|
||||
### 2.4 颜色和样式
|
||||
|
||||
Canvas 支持多种颜色设置方式:
|
||||
Canvas 支持多种颜色设置方式:
|
||||
|
||||
```javascript
|
||||
// 纯色
|
||||
ctx.fillStyle = '#3498db' // 十六进制
|
||||
ctx.fillStyle = 'rgb(52, 152, 219)' // RGB
|
||||
ctx.fillStyle = 'rgba(52, 152, 219, 0.5)' // RGBA(带透明度)
|
||||
ctx.fillStyle = 'rgba(52, 152, 219, 0.5)' // RGBA(带透明度)
|
||||
|
||||
// 线性渐变
|
||||
const gradient = ctx.createLinearGradient(x1, y1, x2, y2)
|
||||
@@ -157,32 +173,32 @@ ctx.fillStyle = radialGradient
|
||||
|
||||
---
|
||||
|
||||
## 2. 路径与形状
|
||||
## 3. 路径:Canvas 的"笔画"
|
||||
|
||||
### 2.1 路径 (Path) 的概念
|
||||
### 3.1 什么是路径?
|
||||
|
||||
**路径**是 Canvas 中的核心概念,它是由一系列点连接成的"轨迹"。你可以把它想象成用笔画线的过程:
|
||||
**路径 (Path)** 是 Canvas 中的核心概念。你可以把它想象成用笔画线的过程:
|
||||
|
||||
1. `beginPath()` - 开始新路径(拿起笔)
|
||||
2. `moveTo()` - 移动到起点(不画线)
|
||||
3. `lineTo()` / `arc()` / `curveTo()` - 绘制线条或曲线
|
||||
4. `closePath()` - 闭合路径(可选)
|
||||
5. `fill()` / `stroke()` - 填充或描边
|
||||
1. **`beginPath()`** - 开始新路径(拿起笔)
|
||||
2. **`moveTo()`** - 移动到起点(不画线)
|
||||
3. **`lineTo()` / `arc()`** - 绘制线条或曲线
|
||||
4. **`closePath()`** - 闭合路径(可选)
|
||||
5. **`fill()` / `stroke()`** - 填充或描边
|
||||
|
||||
```javascript
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(100, 100) // 移动到起点
|
||||
ctx.lineTo(200, 100) // 画横线
|
||||
ctx.lineTo(150, 150) // 画斜线
|
||||
ctx.closePath() // 闭合路径(回到起点)
|
||||
ctx.closePath() // 闭合路径(回到起点)
|
||||
ctx.fill() // 填充
|
||||
```
|
||||
|
||||
### 2.2 绘制复杂形状
|
||||
### 3.2 绘制复杂形状
|
||||
|
||||
通过组合路径,可以绘制任意复杂的形状:
|
||||
通过组合路径,可以绘制任意复杂的形状。
|
||||
|
||||
#### 三角形
|
||||
**三角形**:
|
||||
|
||||
```javascript
|
||||
ctx.beginPath()
|
||||
@@ -194,114 +210,17 @@ ctx.fillStyle = '#e74c3c'
|
||||
ctx.fill()
|
||||
```
|
||||
|
||||
#### 星形
|
||||
|
||||
```javascript
|
||||
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
|
||||
let rot = (Math.PI / 2) * 3
|
||||
let x = cx
|
||||
let y = cy
|
||||
const step = Math.PI / spikes
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx, cy - outerRadius)
|
||||
|
||||
for (let i = 0; i < spikes; i++) {
|
||||
x = cx + Math.cos(rot) * outerRadius
|
||||
y = cy + Math.sin(rot) * outerRadius
|
||||
ctx.lineTo(x, y)
|
||||
rot += step
|
||||
|
||||
x = cx + Math.cos(rot) * innerRadius
|
||||
y = cy + Math.sin(rot) * innerRadius
|
||||
ctx.lineTo(x, y)
|
||||
rot += step
|
||||
}
|
||||
|
||||
ctx.lineTo(cx, cy - outerRadius)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
drawStar(ctx, 150, 150, 5, 50, 25)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 文本与图片
|
||||
|
||||
### 3.1 绘制文本
|
||||
|
||||
Canvas 也可以绘制文本:
|
||||
|
||||
```javascript
|
||||
// 填充文本
|
||||
ctx.font = '30px Arial'
|
||||
ctx.fillStyle = '#2c3e50'
|
||||
ctx.fillText('Hello Canvas', x, y)
|
||||
|
||||
// 描边文本
|
||||
ctx.font = 'bold 40px Arial'
|
||||
ctx.strokeStyle = '#e74c3c'
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeText('Hello Canvas', x, y)
|
||||
|
||||
// 文本对齐
|
||||
ctx.textAlign = 'center' // left, center, right
|
||||
ctx.textBaseline = 'middle' // top, middle, bottom
|
||||
ctx.fillText('Centered', canvas.width / 2, canvas.height / 2)
|
||||
```
|
||||
|
||||
### 3.2 加载和绘制图片
|
||||
|
||||
```javascript
|
||||
const img = new Image()
|
||||
img.src = 'image.png'
|
||||
img.onload = () => {
|
||||
// 绘制图片
|
||||
ctx.drawImage(img, x, y)
|
||||
|
||||
// 缩放图片
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
|
||||
// 裁剪图片
|
||||
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
- `sx, sy, sWidth, sHeight`:源图像的裁剪区域
|
||||
- `dx, dy, dWidth, dHeight`:目标画布的绘制区域
|
||||
|
||||
### 3.3 裁剪与合成
|
||||
|
||||
```javascript
|
||||
// 裁剪区域
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2)
|
||||
ctx.clip() // 之后的所有绘制都只会显示在圆形内
|
||||
ctx.drawImage(img, 0, 0)
|
||||
ctx.restore()
|
||||
|
||||
// 全局合成操作
|
||||
ctx.globalCompositeOperation = 'source-over' // 默认
|
||||
ctx.globalCompositeOperation = 'destination-over' // 绘制在现有内容后面
|
||||
ctx.globalCompositeOperation = 'source-in' // 只保留重叠部分
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 动画基础
|
||||
|
||||
### 4.1 requestAnimationFrame
|
||||
### 4.1 动画循环
|
||||
|
||||
在 Canvas 中创建动画,核心是使用 `requestAnimationFrame` 方法。它是浏览器专门为动画优化的 API:
|
||||
在 Canvas 中创建动画,核心是使用 **`requestAnimationFrame`** 方法。
|
||||
|
||||
```javascript
|
||||
function animate() {
|
||||
// 1. 清除画布(或绘制半透明背景产生拖尾效果)
|
||||
// 1. 清除画布(或绘制半透明背景产生拖尾效果)
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 2. 更新状态
|
||||
@@ -318,93 +237,35 @@ function animate() {
|
||||
animate()
|
||||
```
|
||||
|
||||
**为什么用 requestAnimationFrame 而不是 setInterval?**
|
||||
::: tip 💡 为什么用 requestAnimationFrame 而不是 setInterval?
|
||||
|
||||
- 自动优化,通常为 60FPS(每秒 60 帧)
|
||||
- 页面不可见时自动暂停,节省资源
|
||||
- 与浏览器刷新周期同步,避免画面撕裂
|
||||
- ✅ 自动优化,通常为 60FPS(每秒 60 帧)
|
||||
- ✅ 页面不可见时自动暂停,节省资源
|
||||
- ✅ 与浏览器刷新周期同步,避免画面撕裂
|
||||
:::
|
||||
|
||||
> 🕹️ **交互演示**:点击播放,观察不同类型的动画效果。
|
||||
### 4.2 动画的本质
|
||||
|
||||
<AnimationLoopDemo />
|
||||
动画的本质是**快速连续绘制静态画面**。每帧需要:
|
||||
|
||||
### 4.2 清除与重绘
|
||||
|
||||
动画的本质是**快速连续绘制静态画面**。每帧需要:
|
||||
|
||||
1. **清除旧画面**:`ctx.clearRect()` 或用半透明背景覆盖
|
||||
2. **更新状态**:计算新位置、新角度等
|
||||
3. **绘制新画面**:重新绘制所有对象
|
||||
1. **清除旧画面**: `ctx.clearRect()` 或用半透明背景覆盖
|
||||
2. **更新状态**: 计算新位置、新角度等
|
||||
3. **绘制新画面**: 重新绘制所有对象
|
||||
|
||||
```javascript
|
||||
// 方法1:完全清除
|
||||
// 清除画布
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 方法2:半透明背景(产生拖尾效果)
|
||||
// 半透明背景(产生拖尾效果)
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 方法3:只清除变化区域(脏矩形优化)
|
||||
objects.forEach((obj) => {
|
||||
if (obj.moved) {
|
||||
ctx.clearRect(obj.oldX, obj.oldY, obj.size, obj.size)
|
||||
obj.draw(ctx)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 4.3 动画循环
|
||||
|
||||
一个完整的动画循环示例:
|
||||
|
||||
```javascript
|
||||
let ball = {
|
||||
x: 300,
|
||||
y: 200,
|
||||
vx: 2,
|
||||
vy: 3,
|
||||
radius: 20
|
||||
}
|
||||
|
||||
function update() {
|
||||
// 更新位置
|
||||
ball.x += ball.vx
|
||||
ball.y += ball.vy
|
||||
|
||||
// 边界碰撞
|
||||
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
|
||||
ball.vx = -ball.vx
|
||||
}
|
||||
if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
|
||||
ball.vy = -ball.vy
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// 清除画布
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 绘制球
|
||||
ctx.beginPath()
|
||||
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2)
|
||||
ctx.fillStyle = '#3498db'
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function animate() {
|
||||
update()
|
||||
draw()
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
animate()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 事件处理
|
||||
|
||||
Canvas 只是一个 DOM 元素,不像 SVG 那样每个图形都是独立的 DOM 元素。因此,我们需要**手动处理交互事件**。
|
||||
Canvas 只是一个 DOM 元素,不像 SVG 那样每个图形都是独立的 DOM 元素。因此,我们需要**手动处理交互事件**。
|
||||
|
||||
### 5.1 鼠标事件
|
||||
|
||||
@@ -466,168 +327,15 @@ canvas.addEventListener('mouseup', () => {
|
||||
})
|
||||
```
|
||||
|
||||
### 5.3 键盘事件
|
||||
|
||||
```javascript
|
||||
canvas.tabIndex = 0 // 使 canvas 可以获取焦点
|
||||
canvas.focus()
|
||||
|
||||
canvas.addEventListener('keydown', (e) => {
|
||||
const step = 10
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
selectedObject.y -= step
|
||||
break
|
||||
case 'ArrowDown':
|
||||
selectedObject.y += step
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
selectedObject.x -= step
|
||||
break
|
||||
case 'ArrowRight':
|
||||
selectedObject.x += step
|
||||
break
|
||||
case 'Delete':
|
||||
objects = objects.filter((obj) => obj !== selectedObject)
|
||||
break
|
||||
}
|
||||
|
||||
draw()
|
||||
})
|
||||
```
|
||||
|
||||
> 🕹️ **交互演示**:尝试在下方的 Canvas 中点击、拖拽、悬停,体验不同的事件处理方式。
|
||||
|
||||
<EventHandlingDemo />
|
||||
|
||||
---
|
||||
|
||||
## 6. 实战案例
|
||||
## 6. 性能优化
|
||||
|
||||
### 6.1 绘制折线图
|
||||
随着绘制的对象增多,Canvas 性能会下降。以下是一些常用的优化技巧:
|
||||
|
||||
```javascript
|
||||
const data = [10, 50, 30, 80, 60, 90, 40]
|
||||
### 6.1 离屏 Canvas (Offscreen Canvas)
|
||||
|
||||
function drawLineChart(ctx, data) {
|
||||
const padding = 50
|
||||
const chartWidth = canvas.width - padding * 2
|
||||
const chartHeight = canvas.height - padding * 2
|
||||
const maxValue = Math.max(...data)
|
||||
|
||||
// 绘制坐标轴
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(padding, padding)
|
||||
ctx.lineTo(padding, canvas.height - padding)
|
||||
ctx.lineTo(canvas.width - padding, canvas.height - padding)
|
||||
ctx.strokeStyle = '#2c3e50'
|
||||
ctx.stroke()
|
||||
|
||||
// 绘制折线
|
||||
ctx.beginPath()
|
||||
data.forEach((value, index) => {
|
||||
const x = padding + (index / (data.length - 1)) * chartWidth
|
||||
const y = canvas.height - padding - (value / maxValue) * chartHeight
|
||||
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y)
|
||||
} else {
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
})
|
||||
ctx.strokeStyle = '#3498db'
|
||||
ctx.lineWidth = 2
|
||||
ctx.stroke()
|
||||
|
||||
// 绘制数据点
|
||||
data.forEach((value, index) => {
|
||||
const x = padding + (index / (data.length - 1)) * chartWidth
|
||||
const y = canvas.height - padding - (value / maxValue) * chartHeight
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, 5, 0, Math.PI * 2)
|
||||
ctx.fillStyle = '#e74c3c'
|
||||
ctx.fill()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 简单粒子系统
|
||||
|
||||
粒子系统是游戏和特效中常见的技术,它由大量小粒子组成,每个粒子有独立的位置、速度、生命周期等属性。
|
||||
|
||||
```javascript
|
||||
class Particle {
|
||||
constructor(x, y) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.vx = (Math.random() - 0.5) * 4
|
||||
this.vy = (Math.random() - 0.5) * 4
|
||||
this.life = 1.0
|
||||
this.color = `hsl(${Math.random() * 360}, 70%, 50%)`
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.vx
|
||||
this.y += this.vy
|
||||
this.life -= 0.02
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.globalAlpha = this.life
|
||||
ctx.fillStyle = this.color
|
||||
ctx.beginPath()
|
||||
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.globalAlpha = 1.0
|
||||
}
|
||||
|
||||
isDead() {
|
||||
return this.life <= 0
|
||||
}
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
let particles = []
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 更新和绘制粒子
|
||||
particles = particles.filter((p) => !p.isDead())
|
||||
particles.forEach((p) => {
|
||||
p.update()
|
||||
p.draw(ctx)
|
||||
})
|
||||
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
// 鼠标移动产生粒子
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
const { x, y } = getMousePos(e)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
particles.push(new Particle(x, y))
|
||||
}
|
||||
})
|
||||
|
||||
animate()
|
||||
```
|
||||
|
||||
> 🕹️ **交互演示**:在下方 Canvas 中移动鼠标,体验不同的粒子效果。
|
||||
|
||||
<ParticleSystemDemo />
|
||||
|
||||
---
|
||||
|
||||
## 7. 性能优化
|
||||
|
||||
随着绘制的对象增多,Canvas 性能会下降。以下是一些常用的优化技巧:
|
||||
|
||||
### 7.1 离屏 Canvas (Offscreen Canvas)
|
||||
|
||||
预渲染静态内容到离屏 Canvas,减少每帧的绘制操作:
|
||||
预渲染静态内容到离屏 Canvas,减少每帧的绘制操作:
|
||||
|
||||
```javascript
|
||||
// 创建离屏 Canvas
|
||||
@@ -640,7 +348,6 @@ offscreenCanvas.height = 400
|
||||
function drawBackground(ctx) {
|
||||
ctx.fillStyle = '#f0f0f0'
|
||||
ctx.fillRect(0, 0, 600, 400)
|
||||
// 绘制网格...
|
||||
}
|
||||
drawBackground(offscreenCtx)
|
||||
|
||||
@@ -654,33 +361,9 @@ function draw() {
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 图层管理
|
||||
### 6.2 减少重绘(脏矩形优化)
|
||||
|
||||
将静态背景和动态对象分层渲染:
|
||||
|
||||
```javascript
|
||||
// 背景层(只绘制一次)
|
||||
const backgroundLayer = document.createElement('canvas')
|
||||
// ... 绘制静态背景
|
||||
|
||||
// 动态层(每帧重绘)
|
||||
const dynamicLayer = canvas
|
||||
|
||||
function draw() {
|
||||
// 清除动态层
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 绘制背景层
|
||||
ctx.drawImage(backgroundLayer, 0, 0)
|
||||
|
||||
// 绘制动态对象
|
||||
objects.forEach((obj) => obj.draw(ctx))
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 减少重绘
|
||||
|
||||
只重绘变化的部分(脏矩形优化):
|
||||
只重绘变化的部分:
|
||||
|
||||
```javascript
|
||||
function draw() {
|
||||
@@ -703,9 +386,9 @@ function draw() {
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 批量渲染
|
||||
### 6.3 批量渲染
|
||||
|
||||
减少状态切换(fillStyle、strokeStyle 等):
|
||||
减少状态切换(fillStyle、strokeStyle 等):
|
||||
|
||||
```javascript
|
||||
// 按颜色分组
|
||||
@@ -728,19 +411,15 @@ Object.keys(batches).forEach((color) => {
|
||||
})
|
||||
```
|
||||
|
||||
> 🕹️ **交互演示**:对比不同优化技术的性能差异。
|
||||
|
||||
<PerformanceDemo />
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见库与框架
|
||||
## 7. 常见库与框架
|
||||
|
||||
虽然原生 Canvas 已经很强大,但在实际项目中,使用成熟的库可以大大提高开发效率。
|
||||
虽然原生 Canvas 已经很强大,但在实际项目中,使用成熟的库可以大大提高开发效率。
|
||||
|
||||
### 8.1 Fabric.js
|
||||
### 7.1 Fabric.js
|
||||
|
||||
**特点**:对象模型,支持交互
|
||||
**特点**: 对象模型,支持交互
|
||||
|
||||
```javascript
|
||||
const canvas = new fabric.Canvas('c')
|
||||
@@ -762,38 +441,11 @@ circle.on('click', () => {
|
||||
})
|
||||
```
|
||||
|
||||
**适用场景**:图片编辑器、白板工具、图形设计工具
|
||||
**适用场景**: 图片编辑器、白板工具、图形设计工具
|
||||
|
||||
### 8.2 Konva.js
|
||||
### 7.2 PixiJS (WebGL)
|
||||
|
||||
**特点**:高性能,支持动画和滤镜
|
||||
|
||||
```javascript
|
||||
const stage = new Konva.Stage({
|
||||
container: 'container',
|
||||
width: 600,
|
||||
height: 400
|
||||
})
|
||||
|
||||
const layer = new Konva.Layer()
|
||||
stage.add(layer)
|
||||
|
||||
const circle = new Konva.Circle({
|
||||
x: 300,
|
||||
y: 200,
|
||||
radius: 50,
|
||||
fill: '#3498db',
|
||||
draggable: true
|
||||
})
|
||||
|
||||
layer.add(circle)
|
||||
```
|
||||
|
||||
**适用场景**:复杂的图形应用、动画演示
|
||||
|
||||
### 8.3 PixiJS (WebGL)
|
||||
|
||||
**特点**:WebGL 加速,超高性能
|
||||
**特点**: WebGL 加速,超高性能
|
||||
|
||||
```javascript
|
||||
const app = new PIXI.Application({
|
||||
@@ -810,60 +462,24 @@ graphics.endFill()
|
||||
app.stage.addChild(graphics)
|
||||
```
|
||||
|
||||
**适用场景**:大型游戏、粒子系统、大量对象的场景
|
||||
|
||||
### 8.4 Three.js (3D)
|
||||
|
||||
虽然 Three.js 主要用于 3D,但也支持 2D 渲染:
|
||||
|
||||
```javascript
|
||||
const scene = new THREE.Scene()
|
||||
const camera = new THREE.OrthographicCamera(0, 600, 400, 0, 1, 1000)
|
||||
const renderer = new THREE.WebGLRenderer()
|
||||
renderer.setSize(600, 400)
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
const geometry = new THREE.CircleGeometry(50, 32)
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0x3498db })
|
||||
const circle = new THREE.Mesh(geometry, material)
|
||||
circle.position.set(300, 200, 0)
|
||||
scene.add(circle)
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate)
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
animate()
|
||||
```
|
||||
|
||||
**适用场景**:2.5D 游戏、混合 2D/3D 应用
|
||||
|
||||
### 8.5 选择建议
|
||||
|
||||
| 库 | 优势 | 劣势 | 适用场景 |
|
||||
| :-------------- | :----------------- | :--------- | :--------------- |
|
||||
| **原生 Canvas** | 轻量、无依赖 | 开发效率低 | 学习、简单图形 |
|
||||
| **Fabric.js** | 对象模型、交互友好 | 性能一般 | 图片编辑器、白板 |
|
||||
| **Konva.js** | 高性能、API 简洁 | 体积较大 | 复杂图形应用 |
|
||||
| **PixiJS** | 超高性能、WebGL | 学习曲线陡 | 大型游戏 |
|
||||
| **Three.js** | 3D 能力强 | 过于重量级 | 2.5D 游戏 |
|
||||
**适用场景**: 大型游戏、粒子系统、大量对象的场景
|
||||
|
||||
---
|
||||
|
||||
## 9. 总结与最佳实践
|
||||
## 8. 总结与最佳实践
|
||||
|
||||
### 9.1 核心要点回顾
|
||||
### 8.1 核心要点回顾
|
||||
|
||||
1. **Canvas 是位图画布**:绘制后就是像素,无法直接修改已有内容
|
||||
2. **坐标系统**:原点在左上角,Y 轴向下为正
|
||||
3. **路径系统**:beginPath → moveTo → lineTo → fill/stroke
|
||||
4. **动画原理**:清除 → 更新 → 绘制 → requestAnimationFrame
|
||||
5. **事件处理**:需要手动计算碰撞检测
|
||||
6. **性能优化**:离屏 Canvas、脏矩形、批量渲染
|
||||
1. **Canvas 是位图画布**: 绘制后就是像素,无法直接修改已有内容
|
||||
2. **坐标系统**: 原点在左上角,Y 轴向下为正
|
||||
3. **路径系统**: beginPath → moveTo → lineTo → fill/stroke
|
||||
4. **动画原理**: 清除 → 更新 → 绘制 → requestAnimationFrame
|
||||
5. **事件处理**: 需要手动计算碰撞
|
||||
6. **性能优化**: 离屏 Canvas、脏矩形、批量渲染
|
||||
|
||||
### 9.2 最佳实践
|
||||
### 8.2 最佳实践
|
||||
|
||||
#### 代码组织
|
||||
**代码组织**:
|
||||
|
||||
```javascript
|
||||
// 使用类封装对象
|
||||
@@ -889,81 +505,48 @@ class GameObject {
|
||||
}
|
||||
```
|
||||
|
||||
#### 性能优化清单
|
||||
**性能优化清单**:
|
||||
|
||||
- ✅ 使用 `requestAnimationFrame` 而不是 `setInterval`
|
||||
- ✅ 减少状态切换(按颜色分组绘制)
|
||||
- ✅ 减少状态切换(按颜色分组绘制)
|
||||
- ✅ 使用离屏 Canvas 预渲染静态内容
|
||||
- ✅ 只重绘变化的部分(脏矩形)
|
||||
- ✅ 限制对象数量,使用对象池
|
||||
- ✅ 只重绘变化的部分(脏矩形)
|
||||
- ✅ 限制对象数量,使用对象池
|
||||
- ✅ 避免 `save()` 和 `restore()` 的频繁调用
|
||||
|
||||
#### 调试技巧
|
||||
---
|
||||
|
||||
```javascript
|
||||
// 绘制边界框(用于调试)
|
||||
function drawBoundingBox(ctx, obj) {
|
||||
ctx.strokeStyle = '#e74c3c'
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeRect(
|
||||
obj.x - obj.radius,
|
||||
obj.y - obj.radius,
|
||||
obj.radius * 2,
|
||||
obj.radius * 2
|
||||
)
|
||||
}
|
||||
## 9. 名词速查表 (Glossary)
|
||||
|
||||
// 显示 FPS
|
||||
let lastTime = performance.now()
|
||||
let frameCount = 0
|
||||
|
||||
function showFPS() {
|
||||
const now = performance.now()
|
||||
frameCount++
|
||||
|
||||
if (now >= lastTime + 1000) {
|
||||
console.log(`FPS: ${frameCount}`)
|
||||
frameCount = 0
|
||||
lastTime = now
|
||||
}
|
||||
|
||||
requestAnimationFrame(showFPS)
|
||||
}
|
||||
showFPS()
|
||||
```
|
||||
|
||||
### 9.3 学习路线
|
||||
|
||||
1. **入门**:掌握基本形状绘制和颜色
|
||||
2. **进阶**:学习动画原理和事件处理
|
||||
3. **实战**:制作小游戏(贪吃蛇、打砖块)
|
||||
4. **优化**:学习性能优化技巧
|
||||
5. **扩展**:尝试成熟的 Canvas 库(Fabric.js、Konva)
|
||||
| 名词 | 解释 |
|
||||
| ------------------------- | ----------------------------------------------------------------------- |
|
||||
| **Context / 上下文** | Canvas 的渲染环境,通过 `getContext("2d")` 获取,所有绘制操作都通过它完成 |
|
||||
| **Path / 路径** | 由一系列点连接成的轨迹,是 Canvas 绘图的基础 |
|
||||
| **Stroke / 描边** | 绘制路径的轮廓线 |
|
||||
| **Fill / 填充** | 用颜色填充路径内部 |
|
||||
| **requestAnimationFrame** | 浏览器提供的动画 API,在每次重绘前调用回调函数 |
|
||||
| **Offscreen Canvas** | 离屏 Canvas,用于预渲染静态内容以提高性能 |
|
||||
| **Dirty Rect** | 脏矩形优化,只重绘变化的部分 |
|
||||
| **Collision Detection** | 碰撞检测,判断鼠标或对象是否点击了某个图形 |
|
||||
| **Raster vs Vector** | 位图 vs 矢量图,Canvas 是位图,SVG 是矢量图 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 名词速查表 (Glossary)
|
||||
## 总结
|
||||
|
||||
| 名词 | 解释 |
|
||||
| :------------------------ | :------------------------------------------------------------------------ |
|
||||
| **Context / 上下文** | Canvas 的渲染环境,通过 `getContext('2d')` 获取,所有绘制操作都通过它完成 |
|
||||
| **Path / 路径** | 由一系列点连接成的轨迹,是 Canvas 绘图的基础 |
|
||||
| **Stroke / 描边** | 绘制路径的轮廓线 |
|
||||
| **Fill / 填充** | 用颜色填充路径内部 |
|
||||
| **requestAnimationFrame** | 浏览器提供的动画 API,在每次重绘前调用回调函数 |
|
||||
| **Offscreen Canvas** | 离屏 Canvas,用于预渲染静态内容以提高性能 |
|
||||
| **Dirty Rect** | 脏矩形优化,只重绘变化的部分 |
|
||||
| **Particle System** | 粒子系统,由大量小粒子组成的特效系统 |
|
||||
| **Collision Detection** | 碰撞检测,判断鼠标或对象是否点击了某个图形 |
|
||||
| **Raster vs Vector** | 位图 vs 矢量图,Canvas 是位图,SVG 是矢量图 |
|
||||
现在你已经掌握了 Canvas 2D 的核心概念:
|
||||
|
||||
---
|
||||
- **基本绘图**: 矩形、圆形、线条
|
||||
- **样式控制**: 颜色、渐变、阴影
|
||||
- **动画制作**: requestAnimationFrame + 清除重绘
|
||||
- **交互处理**: 鼠标事件、碰撞检测
|
||||
- **性能优化**: 离屏 Canvas、批量渲染
|
||||
|
||||
**下一步建议**:
|
||||
**下一步建议**:
|
||||
|
||||
- 如果你想深入学习 Canvas 动画,可以尝试制作一个**贪吃蛇游戏**或**打砖块游戏**
|
||||
- 如果你对数据可视化感兴趣,可以学习 **ECharts** 或 **D3.js**
|
||||
- 如果你想做游戏开发,可以尝试 **Phaser.js** 游戏引擎
|
||||
- 如果你对 WebGL 感兴趣,可以学习 **Three.js** 或 **PixiJS**
|
||||
- 如果你想深入学习动画,可以尝试制作一个**贪吃蛇游戏**或**打砖块游戏**
|
||||
- 如果你对数据可视化感兴趣,可以学习 **ECharts** 或 **D3.js**
|
||||
- 如果你想做游戏开发,可以尝试 **Phaser.js** 游戏引擎
|
||||
- 如果你对 WebGL 感兴趣,可以学习 **Three.js** 或 **PixiJS**
|
||||
|
||||
祝你学习愉快!🎨
|
||||
祝你学习愉快! 🎨
|
||||
|
||||
+101
-102
@@ -36,12 +36,12 @@
|
||||
|
||||
想象一下,你们公司搬到了一栋新写字楼:
|
||||
|
||||
| 场景 | 没有 IAM 的做法 | 有 IAM 的做法 |
|
||||
| :--- | :--- | :--- |
|
||||
| 新员工入职 | 给他一把能开所有门的万能钥匙 | 给他一张门禁卡,只能刷他办公区域的门 |
|
||||
| 员工离职 | 钥匙丢了就丢了,也不知道谁拿着 | 立即在系统里注销他的门禁卡,所有门都打不开了 |
|
||||
| 外包人员 | 把钥匙借给他几天 | 发临时门禁卡,设置3天后自动失效 |
|
||||
| 访客 | 前台配一把钥匙给他 | 发一次性访客码,只能进会议室 |
|
||||
| 场景 | 没有 IAM 的做法 | 有 IAM 的做法 |
|
||||
| :--------- | :----------------------------- | :------------------------------------------- |
|
||||
| 新员工入职 | 给他一把能开所有门的万能钥匙 | 给他一张门禁卡,只能刷他办公区域的门 |
|
||||
| 员工离职 | 钥匙丢了就丢了,也不知道谁拿着 | 立即在系统里注销他的门禁卡,所有门都打不开了 |
|
||||
| 外包人员 | 把钥匙借给他几天 | 发临时门禁卡,设置3天后自动失效 |
|
||||
| 访客 | 前台配一把钥匙给他 | 发一次性访客码,只能进会议室 |
|
||||
|
||||
**IAM(Identity and Access Management,身份与访问管理)**,就像是这套"智能门禁系统":
|
||||
|
||||
@@ -55,13 +55,13 @@
|
||||
|
||||
不同的云厂商都有自己的 IAM 实现:
|
||||
|
||||
| 云厂商 | 服务名称 | 核心概念 |
|
||||
| :--- | :--- | :--- |
|
||||
| **AWS** | IAM (Identity and Access Management) | User、Group、Role、Policy |
|
||||
| **阿里云** | RAM (Resource Access Management) | 用户、用户组、角色、策略 |
|
||||
| **腾讯云** | CAM (Cloud Access Management) | 用户、用户组、角色、策略 |
|
||||
| **华为云** | IAM | 用户、用户组、委托、策略 |
|
||||
| **Azure** | Azure AD + RBAC | User、Group、Role、RBAC |
|
||||
| 云厂商 | 服务名称 | 核心概念 |
|
||||
| :--------- | :----------------------------------- | :------------------------ |
|
||||
| **AWS** | IAM (Identity and Access Management) | User、Group、Role、Policy |
|
||||
| **阿里云** | RAM (Resource Access Management) | 用户、用户组、角色、策略 |
|
||||
| **腾讯云** | CAM (Cloud Access Management) | 用户、用户组、角色、策略 |
|
||||
| **华为云** | IAM | 用户、用户组、委托、策略 |
|
||||
| **Azure** | Azure AD + RBAC | User、Group、Role、RBAC |
|
||||
|
||||
虽然名字不同,但**核心概念都是相通的**:
|
||||
|
||||
@@ -80,11 +80,11 @@
|
||||
|
||||
用一个办公室的场景来类比:
|
||||
|
||||
| 概念 | 类比 | 适用场景 | 特点 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **用户(User)** | 正式员工,有自己的工位和门禁卡 | 长期、稳定的团队成员 | 有永久凭证(密码、AK/SK) |
|
||||
| **用户组(Group)** | 部门,如"技术部"、"销售部" | 批量管理权限 | 不能登录,只是权限容器 |
|
||||
| **角色(Role)** | 临时访客证、外包临时卡 | 临时授权、跨账号访问 | 没有永久凭证,靠"扮演"获取临时凭证 |
|
||||
| 概念 | 类比 | 适用场景 | 特点 |
|
||||
| :------------------ | :----------------------------- | :------------------- | :--------------------------------- |
|
||||
| **用户(User)** | 正式员工,有自己的工位和门禁卡 | 长期、稳定的团队成员 | 有永久凭证(密码、AK/SK) |
|
||||
| **用户组(Group)** | 部门,如"技术部"、"销售部" | 批量管理权限 | 不能登录,只是权限容器 |
|
||||
| **角色(Role)** | 临时访客证、外包临时卡 | 临时授权、跨账号访问 | 没有永久凭证,靠"扮演"获取临时凭证 |
|
||||
|
||||
### 2.2 真实案例:一个创业公司的权限演进
|
||||
|
||||
@@ -151,13 +151,13 @@ IAM Role 有两个核心组成部分:
|
||||
|
||||
用一个话剧表演的类比:
|
||||
|
||||
| 概念 | 类比 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Role(角色)** | 剧本里的"哈姆雷特" | 定义了要演什么戏(权限)|
|
||||
| **Trust Policy** | 导演说"谁能演哈姆雷特" | 可能是"本剧团的演员"(本账号用户)、"隔壁剧团借来的演员"(跨账号)、"特邀嘉宾"(外部 IdP)|
|
||||
| **Permission Policy** | 剧本内容 | 哈姆雷特能做什么:说台词、决斗、发疯(具体权限)|
|
||||
| **Assume Role** | 演员上台表演 | 小李被导演选中演哈姆雷特,上台后他就拥有了剧本里定义的所有权限 |
|
||||
| **临时凭证** | 演出证 | 小李拿到一个"临时演出证",演出结束后就失效了 |
|
||||
| 概念 | 类比 | 说明 |
|
||||
| :-------------------- | :--------------------- | :----------------------------------------------------------------------------------------- |
|
||||
| **Role(角色)** | 剧本里的"哈姆雷特" | 定义了要演什么戏(权限) |
|
||||
| **Trust Policy** | 导演说"谁能演哈姆雷特" | 可能是"本剧团的演员"(本账号用户)、"隔壁剧团借来的演员"(跨账号)、"特邀嘉宾"(外部 IdP) |
|
||||
| **Permission Policy** | 剧本内容 | 哈姆雷特能做什么:说台词、决斗、发疯(具体权限) |
|
||||
| **Assume Role** | 演员上台表演 | 小李被导演选中演哈姆雷特,上台后他就拥有了剧本里定义的所有权限 |
|
||||
| **临时凭证** | 演出证 | 小李拿到一个"临时演出证",演出结束后就失效了 |
|
||||
|
||||
### 3.2 策略(Policy):权限的"语法"
|
||||
|
||||
@@ -174,11 +174,7 @@ IAM Policy 是一个 JSON 文档,定义了"谁能对什么资源做什么操
|
||||
{
|
||||
"Sid": "AllowS3ReadWrite",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject"
|
||||
],
|
||||
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
||||
"Resource": "arn:aws:s3:::my-app-bucket/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
@@ -201,15 +197,15 @@ IAM Policy 是一个 JSON 文档,定义了"谁能对什么资源做什么操
|
||||
|
||||
**关键字段解释**:
|
||||
|
||||
| 字段 | 含义 | 示例 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Version** | Policy 语法版本 | "2012-10-17" |
|
||||
| **Statement** | 权限声明数组,可包含多个规则 | [...] |
|
||||
| **Sid** | 声明 ID,可选,用于标识这条规则 | "AllowS3ReadWrite" |
|
||||
| **Effect** | 效果:Allow(允许)或 Deny(拒绝) | "Allow" |
|
||||
| **Action** | 允许/拒绝的操作,支持通配符 | "s3:GetObject", "s3:*" |
|
||||
| **Resource** | 作用的资源,用 ARN 标识 | "arn:aws:s3:::bucket/*" |
|
||||
| **Condition** | 可选,满足特定条件时才生效 | 区域限制、MFA 要求等 |
|
||||
| 字段 | 含义 | 示例 |
|
||||
| :------------ | :--------------------------------- | :----------------------- |
|
||||
| **Version** | Policy 语法版本 | "2012-10-17" |
|
||||
| **Statement** | 权限声明数组,可包含多个规则 | [...] |
|
||||
| **Sid** | 声明 ID,可选,用于标识这条规则 | "AllowS3ReadWrite" |
|
||||
| **Effect** | 效果:Allow(允许)或 Deny(拒绝) | "Allow" |
|
||||
| **Action** | 允许/拒绝的操作,支持通配符 | "s3:GetObject", "s3:\*" |
|
||||
| **Resource** | 作用的资源,用 ARN 标识 | "arn:aws:s3:::bucket/\*" |
|
||||
| **Condition** | 可选,满足特定条件时才生效 | 区域限制、MFA 要求等 |
|
||||
|
||||
### 3.3 权限的优先级:Deny > Allow > 默认拒绝
|
||||
|
||||
@@ -246,6 +242,7 @@ IAM 的权限评估逻辑可以用一句话总结:**显式 Deny 永远赢,
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
|
||||
- 开发者虽然有 `s3:*` 的 Allow 权限
|
||||
- 但敏感目录有显式的 Deny 规则
|
||||
- Deny 优先级更高,所以开发者无法访问敏感数据
|
||||
@@ -261,10 +258,10 @@ IAM 的权限评估逻辑可以用一句话总结:**显式 Deny 永远赢,
|
||||
|
||||
Access Key(访问密钥)是云服务提供的一种长期凭证,用于程序化的 API 调用。它由两部分组成:
|
||||
|
||||
| 组成部分 | 名称 | 作用 | 类比 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Access Key ID** | 访问密钥 ID | 标识你是谁(类似于用户名) | 银行卡号 |
|
||||
| **Secret Access Key** | 秘密访问密钥 | 证明你是你(类似于密码) | 银行卡密码 |
|
||||
| 组成部分 | 名称 | 作用 | 类比 |
|
||||
| :-------------------- | :----------- | :------------------------- | :--------- |
|
||||
| **Access Key ID** | 访问密钥 ID | 标识你是谁(类似于用户名) | 银行卡号 |
|
||||
| **Secret Access Key** | 秘密访问密钥 | 证明你是你(类似于密码) | 银行卡密码 |
|
||||
|
||||
### 4.2 为什么 AK/SK 是"高危物品"?
|
||||
|
||||
@@ -302,12 +299,12 @@ upload_file('./test.jpg', 'my-company-bucket', 'uploads/test.jpg')
|
||||
|
||||
**这个案例告诉我们什么?**
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
| :--- | :--- |
|
||||
| 把 AK/SK 硬编码在代码中 | 使用 IAM Role,让程序自动获取临时凭证 |
|
||||
| 把 AK/SK 提交到 Git 仓库 | 使用 `.gitignore` 忽略配置文件,使用密钥管理服务 |
|
||||
| 长期使用同一个 AK/SK 不轮换 | 定期轮换 AK/SK,使用临时凭证替代长期凭证 |
|
||||
| 给 AK/SK 分配过大权限 | 遵循最小权限原则,只授予必要的权限 |
|
||||
| 错误做法 | 正确做法 |
|
||||
| :-------------------------- | :----------------------------------------------- |
|
||||
| 把 AK/SK 硬编码在代码中 | 使用 IAM Role,让程序自动获取临时凭证 |
|
||||
| 把 AK/SK 提交到 Git 仓库 | 使用 `.gitignore` 忽略配置文件,使用密钥管理服务 |
|
||||
| 长期使用同一个 AK/SK 不轮换 | 定期轮换 AK/SK,使用临时凭证替代长期凭证 |
|
||||
| 给 AK/SK 分配过大权限 | 遵循最小权限原则,只授予必要的权限 |
|
||||
|
||||
### 4.3 AK/SK 的安全使用指南
|
||||
|
||||
@@ -356,7 +353,7 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # 关键:允许请求 OIDC token
|
||||
id-token: write # 关键:允许请求 OIDC token
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -374,13 +371,13 @@ jobs:
|
||||
|
||||
**总结:AK/SK 使用的安全层级**
|
||||
|
||||
| 安全等级 | 做法 | 适用场景 | 风险等级 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 最高 | 使用 IAM Role(无长期凭证) | EC2、Lambda、ECS、CI/CD | 极低 |
|
||||
| 高 | 使用 OIDC Federation | GitHub Actions、GitLab CI | 低 |
|
||||
| 中 | 使用密钥管理服务 | 本地开发、小团队 | 中 |
|
||||
| 低 | 使用环境变量 | 快速原型、个人项目 | 高 |
|
||||
| 极低 | 硬编码在代码中 | 任何场景都不推荐 | 极高 |
|
||||
| 安全等级 | 做法 | 适用场景 | 风险等级 |
|
||||
| :------- | :-------------------------- | :------------------------ | :------- |
|
||||
| 最高 | 使用 IAM Role(无长期凭证) | EC2、Lambda、ECS、CI/CD | 极低 |
|
||||
| 高 | 使用 OIDC Federation | GitHub Actions、GitLab CI | 低 |
|
||||
| 中 | 使用密钥管理服务 | 本地开发、小团队 | 中 |
|
||||
| 低 | 使用环境变量 | 快速原型、个人项目 | 高 |
|
||||
| 极低 | 硬编码在代码中 | 任何场景都不推荐 | 极高 |
|
||||
|
||||
---
|
||||
|
||||
@@ -392,21 +389,21 @@ jobs:
|
||||
|
||||
MFA(Multi-Factor Authentication,多因素认证),也叫 2FA(Two-Factor Authentication,双因素认证),是一种安全机制,要求用户在登录时提供**两种或以上**不同类型的认证因素:
|
||||
|
||||
| 因素类型 | 是什么 | 例子 |
|
||||
| :--- | :--- | :--- |
|
||||
| **知识因素**(你知道什么) | 只有用户知道的信息 | 密码、PIN 码 |
|
||||
| **持有因素**(你有什么) | 用户拥有的物理设备 | 手机、硬件密钥 |
|
||||
| **生物因素**(你是什么) | 用户的生物特征 | 指纹、面部识别 |
|
||||
| 因素类型 | 是什么 | 例子 |
|
||||
| :------------------------- | :----------------- | :------------- |
|
||||
| **知识因素**(你知道什么) | 只有用户知道的信息 | 密码、PIN 码 |
|
||||
| **持有因素**(你有什么) | 用户拥有的物理设备 | 手机、硬件密钥 |
|
||||
| **生物因素**(你是什么) | 用户的生物特征 | 指纹、面部识别 |
|
||||
|
||||
### 5.2 为什么 MFA 这么重要?
|
||||
|
||||
**真实数据告诉你答案**:
|
||||
|
||||
| 攻击方式 | 没有 MFA 时的成功率 | 有 MFA 时的成功率 |
|
||||
| :--- | :--- | :--- |
|
||||
| 密码猜测/暴力破解 | 很高 | 极低(还需要第二因素) |
|
||||
| 钓鱼攻击获取密码 | 很高 | 极低(钓鱼页面无法获取 MFA 码) |
|
||||
| 密码泄露(其他网站泄露)| 很高 | 极低(不知道第二因素) |
|
||||
| 攻击方式 | 没有 MFA 时的成功率 | 有 MFA 时的成功率 |
|
||||
| :----------------------- | :------------------ | :------------------------------ |
|
||||
| 密码猜测/暴力破解 | 很高 | 极低(还需要第二因素) |
|
||||
| 钓鱼攻击获取密码 | 很高 | 极低(钓鱼页面无法获取 MFA 码) |
|
||||
| 密码泄露(其他网站泄露) | 很高 | 极低(不知道第二因素) |
|
||||
|
||||
**微软安全报告(2020)**:启用 MFA 可以阻止 **99.9%** 的自动化攻击。
|
||||
|
||||
@@ -441,14 +438,14 @@ MFA(Multi-Factor Authentication,多因素认证),也叫 2FA(Two-Factor
|
||||
|
||||
随着业务增长,很多公司会使用**多账号架构**来隔离不同环境:
|
||||
|
||||
| 账号类型 | 用途 | 权限要求 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Master Account** | 组织管理、账单结算 | 几乎不使用 |
|
||||
| **Security Audit** | 集中收集所有账号的日志 | 只读访问其他账号 |
|
||||
| **Shared Services** | 共享资源(镜像仓库等) | 其他账号只读访问 |
|
||||
| **Development** | 开发环境 | 开发者完全权限 |
|
||||
| **Staging** | 测试/预发布环境 | 测试人员权限 |
|
||||
| **Production** | 生产环境 | 严格限制,需要审批 |
|
||||
| 账号类型 | 用途 | 权限要求 |
|
||||
| :------------------ | :--------------------- | :----------------- |
|
||||
| **Master Account** | 组织管理、账单结算 | 几乎不使用 |
|
||||
| **Security Audit** | 集中收集所有账号的日志 | 只读访问其他账号 |
|
||||
| **Shared Services** | 共享资源(镜像仓库等) | 其他账号只读访问 |
|
||||
| **Development** | 开发环境 | 开发者完全权限 |
|
||||
| **Staging** | 测试/预发布环境 | 测试人员权限 |
|
||||
| **Production** | 生产环境 | 严格限制,需要审批 |
|
||||
|
||||
**问题:Shared Services 账号里的镜像,怎么让 Production 账号的 EC2 拉取?**
|
||||
|
||||
@@ -498,6 +495,7 @@ MFA(Multi-Factor Authentication,多因素认证),也叫 2FA(Two-Factor
|
||||
**步骤二:获取 Role ARN**
|
||||
|
||||
创建完成后,复制 Role 的 ARN:
|
||||
|
||||
```
|
||||
arn:aws:iam::SHARED_SERVICES_ACCOUNT_ID:role/CrossAccountECRReadRole
|
||||
```
|
||||
@@ -691,38 +689,38 @@ aws ecr describe-repositories --registry-id SHARED_SERVICES_ACCOUNT_ID
|
||||
|
||||
### 8.1 十大 IAM 反模式
|
||||
|
||||
| # | 反模式 | 为什么不好 | 正确做法 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 1 | 使用根账号进行日常操作 | 根账号拥有所有权限,一旦泄露无法限制损害 | 创建 IAM 管理员账号,根账号仅在必要时使用 |
|
||||
| 2 | 给所有人 AdministratorAccess | 违反最小权限原则,增加误操作和内部威胁风险 | 按角色分组,只授予必要的权限 |
|
||||
| 3 | 在代码中硬编码 AK/SK | AK/SK 容易通过 GitHub 泄露,且难以轮换 | 使用 IAM Role、环境变量或密钥管理服务 |
|
||||
| 4 | 长期不轮换 AK/SK | 增加凭证泄露后的风险敞口时间 | 设置 90 天轮换策略,或更好的——使用临时凭证 |
|
||||
| 5 | 忽略 MFA | 密码泄露后账号直接沦陷 | 为所有 IAM 用户启用 MFA,尤其是高权限用户 |
|
||||
| 6 | 不使用 CloudTrail | 无法审计谁做了什么操作,出事后无法溯源 | 启用 CloudTrail,并将日志存储到独立的审计账号 |
|
||||
| 7 | IAM Policy 过于宽松 | 如 `Resource: "*"`、`Action: "*"`,增加攻击面 | 明确指定资源 ARN 和具体 Action |
|
||||
| 8 | 不清理离职员工的 IAM User | 僵尸账号可能成为后门 | 建立离职流程,立即禁用并删除 IAM User |
|
||||
| 9 | 不使用 IAM Access Analyzer | 无法发现过度宽松的资源策略(如公开 S3 bucket) | 启用 IAM Access Analyzer,定期检查外部访问 |
|
||||
| 10 | 不在测试环境验证 Policy | 直接在生产环境应用 Policy,可能导致服务中断 | 使用 IAM Policy Simulator 测试,先在测试环境验证 |
|
||||
| # | 反模式 | 为什么不好 | 正确做法 |
|
||||
| :-- | :--------------------------- | :--------------------------------------------- | :----------------------------------------------- |
|
||||
| 1 | 使用根账号进行日常操作 | 根账号拥有所有权限,一旦泄露无法限制损害 | 创建 IAM 管理员账号,根账号仅在必要时使用 |
|
||||
| 2 | 给所有人 AdministratorAccess | 违反最小权限原则,增加误操作和内部威胁风险 | 按角色分组,只授予必要的权限 |
|
||||
| 3 | 在代码中硬编码 AK/SK | AK/SK 容易通过 GitHub 泄露,且难以轮换 | 使用 IAM Role、环境变量或密钥管理服务 |
|
||||
| 4 | 长期不轮换 AK/SK | 增加凭证泄露后的风险敞口时间 | 设置 90 天轮换策略,或更好的——使用临时凭证 |
|
||||
| 5 | 忽略 MFA | 密码泄露后账号直接沦陷 | 为所有 IAM 用户启用 MFA,尤其是高权限用户 |
|
||||
| 6 | 不使用 CloudTrail | 无法审计谁做了什么操作,出事后无法溯源 | 启用 CloudTrail,并将日志存储到独立的审计账号 |
|
||||
| 7 | IAM Policy 过于宽松 | 如 `Resource: "*"`、`Action: "*"`,增加攻击面 | 明确指定资源 ARN 和具体 Action |
|
||||
| 8 | 不清理离职员工的 IAM User | 僵尸账号可能成为后门 | 建立离职流程,立即禁用并删除 IAM User |
|
||||
| 9 | 不使用 IAM Access Analyzer | 无法发现过度宽松的资源策略(如公开 S3 bucket) | 启用 IAM Access Analyzer,定期检查外部访问 |
|
||||
| 10 | 不在测试环境验证 Policy | 直接在生产环境应用 Policy,可能导致服务中断 | 使用 IAM Policy Simulator 测试,先在测试环境验证 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 名词对照表
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--- | :--- | :--- |
|
||||
| **IAM (Identity and Access Management)** | 身份与访问管理 | 云服务中管理用户身份和访问权限的服务 |
|
||||
| **RAM (Resource Access Management)** | 资源访问管理 | 阿里云的 IAM 服务名称 |
|
||||
| **Root Account** | 根账号 | 注册云账号时创建的拥有者账号,拥有最高权限 |
|
||||
| **IAM User** | IAM 用户/子账号 | 由根账号创建的子身份,用于日常操作 |
|
||||
| **IAM Role** | IAM 角色 | 临时性权限载体,无长期凭证,需要被"扮演" |
|
||||
| **IAM Policy** | IAM 策略 | JSON 格式的权限规则定义 |
|
||||
| **ARN** | 亚马逊资源名称 | 全局唯一的资源标识符 |
|
||||
| **AK/SK** | 访问密钥/密钥 | 程序访问云 API 的凭证 |
|
||||
| **STS** | 安全令牌服务 | 提供临时安全凭证的服务 |
|
||||
| **MFA** | 多因素认证 | 需要两个或以上因素的认证方式 |
|
||||
| **SSO** | 单点登录 | 用户一次登录即可访问多个系统的认证方式 |
|
||||
| **ExternalId** | 外部 ID | 用于防止困惑代理攻击的安全标识符 |
|
||||
| **CloudTrail** | 云审计服务 | 记录云账号中所有 API 调用和操作的日志服务 |
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--------------------------------------- | :-------------- | :----------------------------------------- |
|
||||
| **IAM (Identity and Access Management)** | 身份与访问管理 | 云服务中管理用户身份和访问权限的服务 |
|
||||
| **RAM (Resource Access Management)** | 资源访问管理 | 阿里云的 IAM 服务名称 |
|
||||
| **Root Account** | 根账号 | 注册云账号时创建的拥有者账号,拥有最高权限 |
|
||||
| **IAM User** | IAM 用户/子账号 | 由根账号创建的子身份,用于日常操作 |
|
||||
| **IAM Role** | IAM 角色 | 临时性权限载体,无长期凭证,需要被"扮演" |
|
||||
| **IAM Policy** | IAM 策略 | JSON 格式的权限规则定义 |
|
||||
| **ARN** | 亚马逊资源名称 | 全局唯一的资源标识符 |
|
||||
| **AK/SK** | 访问密钥/密钥 | 程序访问云 API 的凭证 |
|
||||
| **STS** | 安全令牌服务 | 提供临时安全凭证的服务 |
|
||||
| **MFA** | 多因素认证 | 需要两个或以上因素的认证方式 |
|
||||
| **SSO** | 单点登录 | 用户一次登录即可访问多个系统的认证方式 |
|
||||
| **ExternalId** | 外部 ID | 用于防止困惑代理攻击的安全标识符 |
|
||||
| **CloudTrail** | 云审计服务 | 记录云账号中所有 API 调用和操作的日志服务 |
|
||||
|
||||
---
|
||||
|
||||
@@ -756,6 +754,7 @@ aws ecr describe-repositories --registry-id SHARED_SERVICES_ACCOUNT_ID
|
||||
---
|
||||
|
||||
> **延伸阅读**:
|
||||
>
|
||||
> - [AWS IAM 官方文档](https://docs.aws.amazon.com/iam/)
|
||||
> - [阿里云 RAM 官方文档](https://www.aliyun.com/product/ram)
|
||||
> - [AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
|
||||
|
||||
@@ -31,13 +31,13 @@
|
||||
|
||||
**核心区别一览**:
|
||||
|
||||
| 维度 | 传统文件系统 | 对象存储 |
|
||||
| :--- | :--- | :--- |
|
||||
| **组织方式** | 层级目录树 | 扁平键值对 |
|
||||
| **访问协议** | POSIX(本地文件操作) | HTTP/REST API |
|
||||
| **扩展性** | 单机容量有限 | 近乎无限水平扩展 |
|
||||
| **元数据** | 基础属性(大小、时间) | 丰富的自定义元数据 |
|
||||
| **典型场景** | 本地办公文档 | 图片/视频/备份/静态资源 |
|
||||
| 维度 | 传统文件系统 | 对象存储 |
|
||||
| :----------- | :--------------------- | :---------------------- |
|
||||
| **组织方式** | 层级目录树 | 扁平键值对 |
|
||||
| **访问协议** | POSIX(本地文件操作) | HTTP/REST API |
|
||||
| **扩展性** | 单机容量有限 | 近乎无限水平扩展 |
|
||||
| **元数据** | 基础属性(大小、时间) | 丰富的自定义元数据 |
|
||||
| **典型场景** | 本地办公文档 | 图片/视频/备份/静态资源 |
|
||||
|
||||
### 1.2 对象存储的核心概念
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
桶是对象存储的顶级容器,相当于一个独立的命名空间。所有对象都必须存放在某个桶中。
|
||||
|
||||
**命名规则**(以阿里云 OSS 为例):
|
||||
|
||||
- 全局唯一:在整个云厂商的所有用户中不能重复
|
||||
- 只能包含小写字母、数字和短横线
|
||||
- 必须以小写字母或数字开头和结尾
|
||||
@@ -73,11 +74,11 @@
|
||||
|
||||
对象存储提供多层权限控制:
|
||||
|
||||
| 层级 | 控制方式 | 典型场景 |
|
||||
| :--- | :--- | :--- |
|
||||
| **桶级别** | Bucket Policy(资源策略) | 禁止所有外网访问、只允许特定 IP |
|
||||
| **对象级别** | ACL(访问控制列表) | 公开图片、私有文档 |
|
||||
| **临时授权** | STS(安全令牌服务) | 前端直传、移动端上传 |
|
||||
| 层级 | 控制方式 | 典型场景 |
|
||||
| :----------- | :------------------------ | :------------------------------ |
|
||||
| **桶级别** | Bucket Policy(资源策略) | 禁止所有外网访问、只允许特定 IP |
|
||||
| **对象级别** | ACL(访问控制列表) | 公开图片、私有文档 |
|
||||
| **临时授权** | STS(安全令牌服务) | 前端直传、移动端上传 |
|
||||
|
||||
**安全红线**:永远不要把 AccessKey ID 和 AccessKey Secret 写在前端代码里!正确做法是:前端向你的后端申请临时 STS 凭证,后端验证身份后返回带过期时间的临时凭证。
|
||||
|
||||
@@ -102,11 +103,13 @@
|
||||
#### 边缘节点:离用户最近的"快递站"
|
||||
|
||||
边缘节点是 CDN 网络中最接近用户的层级,通常部署在:
|
||||
|
||||
- 运营商机房(联通/电信/移动)
|
||||
- 大城市互联网交换中心
|
||||
- 重要交通枢纽
|
||||
|
||||
**中国主要 CDN 节点分布**:
|
||||
|
||||
- 一线城市:北京、上海、广州、深圳
|
||||
- 二线城市:杭州、南京、成都、武汉、西安
|
||||
- 海外:香港、新加坡、东京、硅谷、法兰克福
|
||||
@@ -116,11 +119,13 @@
|
||||
#### 源站:内容的"总仓库"
|
||||
|
||||
源站是 CDN 回源获取内容的地方,可以是:
|
||||
|
||||
- 对象存储(OSS/COS/S3)
|
||||
- 自建服务器(ECS/物理机)
|
||||
- 负载均衡(SLB/CLB)
|
||||
|
||||
**关键配置**:
|
||||
|
||||
- **回源 HOST**:CDN 节点访问源站时使用的域名/IP
|
||||
- **回源协议**:HTTP 还是 HTTPS
|
||||
- **回源端口**:80、443 还是自定义端口
|
||||
@@ -128,10 +133,12 @@
|
||||
#### 中间层节点:"区域分拨中心"
|
||||
|
||||
在边缘节点和源站之间,CDN 通常还有一层或多层中间节点:
|
||||
|
||||
- **汇聚节点**:聚合多个边缘节点的回源请求,减少源站压力
|
||||
- **区域中心**:负责一个大区的内容分发和调度
|
||||
|
||||
这种分层架构的好处:
|
||||
|
||||
1. **降低源站压力**:1000 个边缘节点的请求,可能只需要向源站发起 10 次
|
||||
2. **提高命中率**:热门内容在中间层就被拦截,不需要回源
|
||||
3. **故障隔离**:某条链路出问题,可以自动切换到其他路径
|
||||
@@ -143,14 +150,17 @@
|
||||
<CachePolicyDemo />
|
||||
|
||||
**Step 1:DNS 解析**(智能调度)
|
||||
|
||||
```
|
||||
用户输入:cdn.example.com/image.jpg
|
||||
↓
|
||||
DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
```
|
||||
|
||||
这里的关键是**智能 DNS**:根据用户的运营商、地理位置、节点负载,返回最优的 CDN 节点 IP。
|
||||
|
||||
**Step 2:边缘节点查找**(缓存命中?)
|
||||
|
||||
```
|
||||
请求到达北京联通 CDN 节点(1.2.3.4)
|
||||
↓
|
||||
@@ -160,6 +170,7 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
```
|
||||
|
||||
**Step 3:回源获取**(层层向上)
|
||||
|
||||
```
|
||||
边缘节点未命中
|
||||
↓
|
||||
@@ -173,6 +184,7 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
```
|
||||
|
||||
**Step 4:缓存并返回**(下次更快)
|
||||
|
||||
```
|
||||
内容沿链路返回
|
||||
↓
|
||||
@@ -198,17 +210,20 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
```
|
||||
|
||||
**流程**:
|
||||
|
||||
1. 用户选择文件,点击上传
|
||||
2. 文件先上传到你的后端服务器
|
||||
3. 后端接收完整文件后,再转上传到对象存储
|
||||
4. 返回上传结果给用户
|
||||
|
||||
**优点**:
|
||||
|
||||
- 实现简单,前后端都好控制
|
||||
- 可以在后端做文件校验、格式转换
|
||||
- 敏感操作可以记录日志、做权限校验
|
||||
|
||||
**缺点**:
|
||||
|
||||
- **带宽双吃**:用户上传占用一次带宽,服务器转传又占用一次
|
||||
- **服务器压力大**:大文件会占用大量内存和 CPU
|
||||
- **上传慢**:相当于多了一道中转,用户感知到的上传时间更长
|
||||
@@ -224,6 +239,7 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
```
|
||||
|
||||
**流程**:
|
||||
|
||||
1. 用户选择文件,前端先向后端申请"上传凭证"
|
||||
2. 后端验证用户身份,向对象存储服务申请**临时 STS 凭证**(带过期时间)
|
||||
3. 后端把临时凭证返回给前端
|
||||
@@ -231,12 +247,14 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
5. 对象存储返回上传结果,前端通知后端"上传完成"
|
||||
|
||||
**优点**:
|
||||
|
||||
- **上传快**:少了中转环节,用户感知速度最快
|
||||
- **服务器压力小**:只处理凭证签发,不处理文件流
|
||||
- **带宽省**:只走一次上传流量
|
||||
- **安全性高**:临时凭证有过期时间,泄露也危害有限
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 实现稍复杂,需要理解 STS、签名机制
|
||||
- 前端需要处理分片上传、断点续传等逻辑
|
||||
- 跨域(CORS)需要配置
|
||||
@@ -261,21 +279,21 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
|
||||
**为什么需要分片?**
|
||||
|
||||
| 场景 | 不分片 | 分片 |
|
||||
| :--- | :--- | :--- |
|
||||
| **网络波动** | 传了 99% 断网,全部重传 | 只重传失败的分片 |
|
||||
| **上传速度** | 单线程,速度慢 | 多线程并行,速度快 |
|
||||
| **内存占用** | 需要缓存整个文件 | 只需缓存当前分片 |
|
||||
| **进度显示** | 只有 0% 和 100% | 精确到每个分片的进度 |
|
||||
| 场景 | 不分片 | 分片 |
|
||||
| :----------- | :---------------------- | :------------------- |
|
||||
| **网络波动** | 传了 99% 断网,全部重传 | 只重传失败的分片 |
|
||||
| **上传速度** | 单线程,速度慢 | 多线程并行,速度快 |
|
||||
| **内存占用** | 需要缓存整个文件 | 只需缓存当前分片 |
|
||||
| **进度显示** | 只有 0% 和 100% | 精确到每个分片的进度 |
|
||||
|
||||
**主流云厂商的分片规格**:
|
||||
|
||||
| 厂商 | 分片大小限制 | 最大分片数 | 最小分片大小 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **阿里云 OSS** | 100MB | 10000 | 100KB |
|
||||
| **腾讯云 COS** | 5GB | 10000 | 1MB |
|
||||
| **AWS S3** | 5GB | 10000 | 5MB(推荐) |
|
||||
| **七牛云** | 100MB | 10000 | 4MB |
|
||||
| 厂商 | 分片大小限制 | 最大分片数 | 最小分片大小 |
|
||||
| :------------- | :----------- | :--------- | :----------- |
|
||||
| **阿里云 OSS** | 100MB | 10000 | 100KB |
|
||||
| **腾讯云 COS** | 5GB | 10000 | 1MB |
|
||||
| **AWS S3** | 5GB | 10000 | 5MB(推荐) |
|
||||
| **七牛云** | 100MB | 10000 | 4MB |
|
||||
|
||||
### 3.2 CDN 回源策略详解
|
||||
|
||||
@@ -284,6 +302,7 @@ DNS 服务器返回:北京联通 CDN 节点 IP(1.2.3.4)
|
||||
#### 什么是"回源"?
|
||||
|
||||
CDN 边缘节点缓存了源站的内容,但当:
|
||||
|
||||
- 用户请求的内容**第一次被访问**
|
||||
- 缓存的内容**已过期(TTL 到期)**
|
||||
- 缓存被**手动刷新/预热**
|
||||
@@ -292,11 +311,11 @@ CDN 节点就需要向**源站**请求最新内容,这个过程就叫"回源"
|
||||
|
||||
#### 回源的三种模式
|
||||
|
||||
| 模式 | 原理 | 适用场景 | 优缺点 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **直接回源** | CDN 节点 → 源站 | 源站有公网 IP,且流量不大 | 简单直接,但源站压力大 |
|
||||
| **中间源回源** | CDN 节点 → 中间层 → 源站 | 大型网站,多层缓存架构 | 分担源站压力,架构复杂 |
|
||||
| ** OSS/COS 作为源站** | CDN 节点 → 对象存储 | 静态资源、图片、视频 | 最佳实践,成本低、性能好 |
|
||||
| 模式 | 原理 | 适用场景 | 优缺点 |
|
||||
| :-------------------- | :----------------------- | :------------------------ | :----------------------- |
|
||||
| **直接回源** | CDN 节点 → 源站 | 源站有公网 IP,且流量不大 | 简单直接,但源站压力大 |
|
||||
| **中间源回源** | CDN 节点 → 中间层 → 源站 | 大型网站,多层缓存架构 | 分担源站压力,架构复杂 |
|
||||
| ** OSS/COS 作为源站** | CDN 节点 → 对象存储 | 静态资源、图片、视频 | 最佳实践,成本低、性能好 |
|
||||
|
||||
#### 回源配置实战
|
||||
|
||||
@@ -315,6 +334,7 @@ CDN 节点就需要向**源站**请求最新内容,这个过程就叫"回源"
|
||||
```
|
||||
|
||||
关键配置项:
|
||||
|
||||
- **源站类型**:OSS/COS 域名 或 自定义源站
|
||||
- **回源协议**:HTTP 还是 HTTPS(建议 HTTPS)
|
||||
- **回源 HOST**:访问源站时使用的 Host 头
|
||||
@@ -332,6 +352,7 @@ CDN 边缘节点
|
||||
```
|
||||
|
||||
主备模式:
|
||||
|
||||
```
|
||||
CDN 边缘节点
|
||||
├─ 主源站 A (健康时全部流量)
|
||||
@@ -342,12 +363,13 @@ CDN 边缘节点
|
||||
|
||||
这里有个容易混淆的概念:
|
||||
|
||||
| 指标 | 定义 | 计费关系 |
|
||||
| :--- | :--- | :--- |
|
||||
| **CDN 下行带宽** | 从 CDN 节点到用户的流量 | 通常按流量计费的 CDN 费用 |
|
||||
| **回源带宽** | 从源站到 CDN 节点的流量 | 通常对象存储或源站出流量费用 |
|
||||
| 指标 | 定义 | 计费关系 |
|
||||
| :--------------- | :---------------------- | :--------------------------- |
|
||||
| **CDN 下行带宽** | 从 CDN 节点到用户的流量 | 通常按流量计费的 CDN 费用 |
|
||||
| **回源带宽** | 从源站到 CDN 节点的流量 | 通常对象存储或源站出流量费用 |
|
||||
|
||||
**省钱技巧**:
|
||||
|
||||
- 提高 CDN 命中率(让更多请求命中缓存,减少回源)
|
||||
- 设置合理的缓存时间(TTL)
|
||||
- 使用预热功能,在用户访问前就缓存热点内容
|
||||
@@ -362,10 +384,12 @@ CDN 边缘节点
|
||||
CDN 如何判断两次请求是否应该返回同一个缓存副本?靠的就是**缓存键**。
|
||||
|
||||
**默认缓存键通常包括**:
|
||||
|
||||
- URL 路径(不含查询参数)
|
||||
- 例如:`/images/photo.jpg`
|
||||
|
||||
**问题场景**:
|
||||
|
||||
```
|
||||
用户 A 请求:/images/photo.jpg?w=100&h=100 (100x100 缩略图)
|
||||
用户 B 请求:/images/photo.jpg?w=800&h=600 (800x600 大图)
|
||||
@@ -375,14 +399,15 @@ CDN 如何判断两次请求是否应该返回同一个缓存副本?靠的就
|
||||
|
||||
**解决方案:自定义缓存键规则**
|
||||
|
||||
| 规则 | 示例 | 效果 |
|
||||
| :--- | :--- | :--- |
|
||||
| **保留指定查询参数** | 保留 `w`、`h` | 不同尺寸分别缓存 |
|
||||
| **保留所有查询参数** | 保留全部 | 完全精确匹配 |
|
||||
| 规则 | 示例 | 效果 |
|
||||
| :------------------- | :------------------------ | :------------------------ |
|
||||
| **保留指定查询参数** | 保留 `w`、`h` | 不同尺寸分别缓存 |
|
||||
| **保留所有查询参数** | 保留全部 | 完全精确匹配 |
|
||||
| **忽略特定查询参数** | 忽略 `token`、`timestamp` | 带时间戳的 URL 能命中缓存 |
|
||||
| **包含请求头** | 包含 `Accept-Language` | 不同语言返回不同内容 |
|
||||
| **包含请求头** | 包含 `Accept-Language` | 不同语言返回不同内容 |
|
||||
|
||||
**实战配置示例**(阿里云 CDN):
|
||||
|
||||
```
|
||||
缓存键规则:
|
||||
- URL 路径:/images/*
|
||||
@@ -396,13 +421,13 @@ TTL(Time To Live)决定了内容在 CDN 节点上缓存多久。设置太短
|
||||
|
||||
**按文件类型设置 TTL 的建议**:
|
||||
|
||||
| 文件类型 | 建议 TTL | 原因 |
|
||||
| :--- | :--- | :--- |
|
||||
| HTML 页面 | 0-5 分钟 | 内容频繁更新,需要实时 |
|
||||
| 文件类型 | 建议 TTL | 原因 |
|
||||
| :---------- | :---------------------- | :----------------------------- |
|
||||
| HTML 页面 | 0-5 分钟 | 内容频繁更新,需要实时 |
|
||||
| JS/CSS 文件 | 1 年(配合文件名 hash) | 内容不变,文件名变化即缓存失效 |
|
||||
| 图片/视频 | 7-30 天 | 更新频率低,可长期缓存 |
|
||||
| 字体文件 | 1 年 | 几乎不变 |
|
||||
| API 响应 | 0-5 分钟(视业务) | 数据实时性要求高 |
|
||||
| 图片/视频 | 7-30 天 | 更新频率低,可长期缓存 |
|
||||
| 字体文件 | 1 年 | 几乎不变 |
|
||||
| API 响应 | 0-5 分钟(视业务) | 数据实时性要求高 |
|
||||
|
||||
**前端工程化配合 CDN 的最佳实践**:
|
||||
|
||||
@@ -425,11 +450,11 @@ output: {
|
||||
|
||||
当你更新了源站内容,但 CDN 缓存还没过期,用户看到的还是旧内容:
|
||||
|
||||
| 刷新类型 | 效果 | 耗时 | 适用场景 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **URL 刷新** | 指定 URL 的缓存失效 | 5-10 分钟 | 单个文件更新 |
|
||||
| **目录刷新** | 指定目录下所有内容失效 | 10-30 分钟 | 批量更新 |
|
||||
| **全站刷新** | 整个域名的缓存全部失效 | 30 分钟以上 | 紧急回滚 |
|
||||
| 刷新类型 | 效果 | 耗时 | 适用场景 |
|
||||
| :----------- | :--------------------- | :---------- | :----------- |
|
||||
| **URL 刷新** | 指定 URL 的缓存失效 | 5-10 分钟 | 单个文件更新 |
|
||||
| **目录刷新** | 指定目录下所有内容失效 | 10-30 分钟 | 批量更新 |
|
||||
| **全站刷新** | 整个域名的缓存全部失效 | 30 分钟以上 | 紧急回滚 |
|
||||
|
||||
**重要提醒**:刷新只是让缓存失效,下次请求会回源拉取新内容。不要在高峰期大批量刷新,否则可能导致源站被打爆。
|
||||
|
||||
@@ -458,12 +483,14 @@ output: {
|
||||
### 4.1 智能 DNS 调度
|
||||
|
||||
传统 DNS 解析:
|
||||
|
||||
```
|
||||
用户问:cdn.example.com 的 IP 是什么?
|
||||
DNS 答:1.2.3.4(固定的)
|
||||
```
|
||||
|
||||
智能 DNS 解析:
|
||||
|
||||
```
|
||||
用户(北京联通)问:cdn.example.com 的 IP 是什么?
|
||||
智能 DNS:让我查查... 北京联通的 CDN 节点是 1.2.3.4
|
||||
@@ -486,6 +513,7 @@ DNS 答:1.2.3.4(固定的)
|
||||
传统 DNS 有个问题:**DNS 劫持和解析延迟**。
|
||||
|
||||
**HTTP DNS 方案**:
|
||||
|
||||
```
|
||||
客户端 → 绕过系统 DNS → 直接问 HTTP DNS 服务(如 223.5.5.5:80)
|
||||
↓
|
||||
@@ -495,11 +523,13 @@ DNS 答:1.2.3.4(固定的)
|
||||
```
|
||||
|
||||
优势:
|
||||
|
||||
- 防劫持:不走运营商 DNS
|
||||
- 更精准:可以按客户端网络质量选择 IP
|
||||
- 实时性:故障切换更快
|
||||
|
||||
**实战建议**:
|
||||
|
||||
- 移动端 APP 强烈建议接入 HTTP DNS
|
||||
- Web 端可以使用 CDN 提供的 CNAME 调度
|
||||
- 关键业务可以做多 IP 容灾(一个域名返回多个 IP)
|
||||
@@ -513,6 +543,7 @@ DNS 答:1.2.3.4(固定的)
|
||||
### 5.1 为什么 CDN 上 HTTPS 很重要?
|
||||
|
||||
**场景对比**:
|
||||
|
||||
```
|
||||
无 HTTPS:
|
||||
用户访问 http://cdn.example.com/image.jpg
|
||||
@@ -539,14 +570,15 @@ HTTP/2 多路复用生效
|
||||
|
||||
#### 证书管理
|
||||
|
||||
| 方案 | 说明 | 成本 | 适用场景 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **云厂商免费证书** | 阿里云/腾讯云等提供 | 免费 | 单域名,快速上手 |
|
||||
| **Let's Encrypt** | 社区免费证书 | 免费 | 自动化部署 |
|
||||
| 方案 | 说明 | 成本 | 适用场景 |
|
||||
| :--------------------- | :-------------------- | :------------- | :--------------- |
|
||||
| **云厂商免费证书** | 阿里云/腾讯云等提供 | 免费 | 单域名,快速上手 |
|
||||
| **Let's Encrypt** | 社区免费证书 | 免费 | 自动化部署 |
|
||||
| **商业 DV/OV/EV 证书** | 赛门铁克、GeoTrust 等 | ¥几百-几万/年 | 企业级、需要绿条 |
|
||||
| **泛域名证书** | *.example.com | ¥几千/年 | 多子域名 |
|
||||
| **泛域名证书** | \*.example.com | ¥几千/年 | 多子域名 |
|
||||
|
||||
**实战建议**:
|
||||
|
||||
- 测试环境:Let's Encrypt 或云厂商免费证书
|
||||
- 生产环境:泛域名证书(省事)或单域名 OV 证书(省钱)
|
||||
- 注意证书过期时间,设置自动续期提醒
|
||||
@@ -554,18 +586,21 @@ HTTP/2 多路复用生效
|
||||
#### HTTPS 优化配置
|
||||
|
||||
**TLS 版本选择**:
|
||||
|
||||
```
|
||||
推荐配置:仅 TLS 1.2 和 TLS 1.3
|
||||
兼容配置:TLS 1.1 + TLS 1.2 + TLS 1.3(兼容老旧浏览器)
|
||||
```
|
||||
|
||||
**密码套件**:
|
||||
|
||||
```
|
||||
推荐:ECDHE 密钥交换 + AES-GCM 加密
|
||||
禁用:DES、RC4、MD5、SHA1
|
||||
```
|
||||
|
||||
**OCSP Stapling**:
|
||||
|
||||
```
|
||||
功能:CDN 节点预获取证书吊销状态
|
||||
效果:减少客户端验证时间 200-500ms
|
||||
@@ -573,6 +608,7 @@ HTTP/2 多路复用生效
|
||||
```
|
||||
|
||||
**TLS 会话复用**:
|
||||
|
||||
```
|
||||
Session ID 复用:客户端带着上次 Session ID,服务端恢复会话
|
||||
Session Ticket 复用:服务端把会话状态加密发给客户端,下次带来
|
||||
@@ -582,6 +618,7 @@ Session Ticket 复用:服务端把会话状态加密发给客户端,下次
|
||||
### 5.3 HTTP/2 与 HTTP/3 在 CDN 上的应用
|
||||
|
||||
**HTTP/2 多路复用**:
|
||||
|
||||
```
|
||||
HTTP/1.1:
|
||||
请求 1 (index.html) ────────────────→
|
||||
@@ -603,6 +640,7 @@ HTTP/2:
|
||||
```
|
||||
|
||||
**HTTP/2 服务端推送**:
|
||||
|
||||
```
|
||||
场景:用户请求 index.html,里面引用了 style.css 和 script.js
|
||||
|
||||
@@ -620,6 +658,7 @@ HTTP/2 推送:
|
||||
```
|
||||
|
||||
**HTTP/3 (QUIC)**:
|
||||
|
||||
```
|
||||
HTTP/2 的问题:基于 TCP,队头阻塞
|
||||
→ 一个 TCP 包丢失,整个连接等待重传
|
||||
@@ -654,6 +693,7 @@ CDN 带宽 = 所有边缘节点的出流量总和
|
||||
```
|
||||
|
||||
**带宽与流量的关系**:
|
||||
|
||||
```
|
||||
1 Mbps 带宽持续跑 1 小时 = 450 MB 流量
|
||||
(计算:1,000,000 bps × 3600s ÷ 8 ÷ 1024 ÷ 1024 ≈ 429 MB)
|
||||
@@ -689,13 +729,13 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
|
||||
**命中率低的常见原因**:
|
||||
|
||||
| 原因 | 现象 | 解决方案 |
|
||||
| :--- | :--- | :--- |
|
||||
| 缓存时间太短 | TTL 只有几分钟 | 根据文件类型调整 TTL |
|
||||
| 查询参数变化 | URL 带随机数 | 配置忽略特定参数 |
|
||||
| 缓存键设置不当 | 不该区分的被区分了 | 优化缓存键规则 |
|
||||
| 内容更新频繁 | 文件经常被覆盖 | 使用版本号或 hash 文件名 |
|
||||
| 首次访问多 | 新内容或新节点 | 提前预热 |
|
||||
| 原因 | 现象 | 解决方案 |
|
||||
| :------------- | :----------------- | :----------------------- |
|
||||
| 缓存时间太短 | TTL 只有几分钟 | 根据文件类型调整 TTL |
|
||||
| 查询参数变化 | URL 带随机数 | 配置忽略特定参数 |
|
||||
| 缓存键设置不当 | 不该区分的被区分了 | 优化缓存键规则 |
|
||||
| 内容更新频繁 | 文件经常被覆盖 | 使用版本号或 hash 文件名 |
|
||||
| 首次访问多 | 新内容或新节点 | 提前预热 |
|
||||
|
||||
### 6.2 日志分析与问题排查
|
||||
|
||||
@@ -712,18 +752,19 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
|
||||
关键字段解释:
|
||||
|
||||
| 字段 | 说明 | 分析价值 |
|
||||
| :--- | :--- | :--- |
|
||||
| `cache_status` | 缓存状态 | HIT(命中)、MISS(未命中)、EXPIRED(过期) |
|
||||
| `response_time` | 响应时间(ms) | 判断用户体验,>500ms 需优化 |
|
||||
| `http_status` | HTTP 状态码 | 404/500 错误排查 |
|
||||
| `bytes_sent` | 发送字节数 | 带宽统计 |
|
||||
| 字段 | 说明 | 分析价值 |
|
||||
| :-------------- | :------------- | :------------------------------------------- |
|
||||
| `cache_status` | 缓存状态 | HIT(命中)、MISS(未命中)、EXPIRED(过期) |
|
||||
| `response_time` | 响应时间(ms) | 判断用户体验,>500ms 需优化 |
|
||||
| `http_status` | HTTP 状态码 | 404/500 错误排查 |
|
||||
| `bytes_sent` | 发送字节数 | 带宽统计 |
|
||||
|
||||
#### 常见问题排查
|
||||
|
||||
**问题 1:用户反映访问慢**
|
||||
|
||||
排查步骤:
|
||||
|
||||
```
|
||||
1. 看日志 response_time
|
||||
- 如果很大(>500ms):检查是缓存 MISS 还是源站慢
|
||||
@@ -739,6 +780,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
**问题 2:缓存不生效,每次都回源**
|
||||
|
||||
排查清单:
|
||||
|
||||
```
|
||||
□ 源站响应头是否有 Cache-Control: no-cache / private?
|
||||
□ URL 是否带随机参数(如 ?_=123456)?
|
||||
@@ -750,6 +792,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
**问题 3:费用暴涨**
|
||||
|
||||
排查方向:
|
||||
|
||||
```
|
||||
1. 看账单明细
|
||||
- CDN 流量费高:检查是否有大文件被频繁访问,或被盗链
|
||||
@@ -848,6 +891,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
#### 对象存储配置
|
||||
|
||||
**存储桶规划**:
|
||||
|
||||
```
|
||||
Bucket: myapp-images-prod
|
||||
├─ 目录结构:
|
||||
@@ -872,6 +916,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
```
|
||||
|
||||
**CORS 跨域配置**:
|
||||
|
||||
```xml
|
||||
<CORSConfiguration>
|
||||
<CORSRule>
|
||||
@@ -890,6 +935,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
#### CDN 加速配置
|
||||
|
||||
**缓存策略配置**:
|
||||
|
||||
```
|
||||
全局默认规则:
|
||||
├─ 缓存键:URL 路径 + 保留 w、h、format 查询参数
|
||||
@@ -916,6 +962,7 @@ CDN QPS = 所有边缘节点每秒处理的 HTTP 请求总数
|
||||
```
|
||||
|
||||
**HTTPS 优化配置**:
|
||||
|
||||
```
|
||||
证书配置:
|
||||
├─ 证书类型:泛域名证书 *.myapp.com
|
||||
@@ -1031,14 +1078,14 @@ rules:
|
||||
```yaml
|
||||
# 带宽封顶配置
|
||||
bandwidth_cap:
|
||||
daily_limit: 500 # Mbps,日峰值超过则自动停用 CDN
|
||||
monthly_limit: 10000 # GB,月流量超过则停用
|
||||
daily_limit: 500 # Mbps,日峰值超过则自动停用 CDN
|
||||
monthly_limit: 10000 # GB,月流量超过则停用
|
||||
|
||||
# 告警阈值
|
||||
alerts:
|
||||
- threshold: 70% # 达到 70% 发告警
|
||||
- threshold: 70% # 达到 70% 发告警
|
||||
channels: [sms, email]
|
||||
- threshold: 90% # 达到 90% 打电话
|
||||
- threshold: 90% # 达到 90% 打电话
|
||||
channels: [phone]
|
||||
```
|
||||
|
||||
@@ -1049,12 +1096,14 @@ bandwidth_cap:
|
||||
### 8.1 架构设计原则
|
||||
|
||||
**原则 1:动静分离**
|
||||
|
||||
```
|
||||
动态内容(API、HTML)→ 走源站或边缘函数
|
||||
静态内容(图片、JS、CSS、视频)→ 走 CDN + 对象存储
|
||||
```
|
||||
|
||||
**原则 2:就近服务**
|
||||
|
||||
```
|
||||
用户在哪里,内容就缓存到哪里
|
||||
→ 选择覆盖广的 CDN 服务商
|
||||
@@ -1063,6 +1112,7 @@ bandwidth_cap:
|
||||
```
|
||||
|
||||
**原则 3:分层缓存**
|
||||
|
||||
```
|
||||
浏览器本地缓存(最强)
|
||||
↓
|
||||
@@ -1074,6 +1124,7 @@ CDN 中间层/区域节点(兜底)
|
||||
```
|
||||
|
||||
**原则 4:成本与体验的平衡**
|
||||
|
||||
```
|
||||
存储分级:热数据标准存储,冷数据归档存储
|
||||
缓存策略:高频内容长 TTL,低频内容短 TTL
|
||||
@@ -1084,24 +1135,28 @@ CDN 中间层/区域节点(兜底)
|
||||
### 8.2 避坑清单
|
||||
|
||||
**存储桶命名与权限**
|
||||
|
||||
- [ ] 桶名全局唯一,避免被占用
|
||||
- [ ] 私有文件不要设置为公共读
|
||||
- [ ] AccessKey 不要写在前端代码里,用 STS 临时凭证
|
||||
- [ ] 启用服务端加密(SSE)保护敏感数据
|
||||
|
||||
**CDN 缓存配置**
|
||||
|
||||
- [ ] HTML 文件 TTL 不要太长(建议 < 5 分钟)
|
||||
- [ ] JS/CSS 建议用带 hash 的文件名,TTL 设为 1 年
|
||||
- [ ] 缓存键要合理,不要把用户信息等变量放进去
|
||||
- [ ] 重要更新后记得刷新缓存或预热
|
||||
|
||||
**HTTPS 安全**
|
||||
|
||||
- [ ] 证书不要过期,设置自动续期
|
||||
- [ ] 最低 TLS 版本建议 1.2
|
||||
- [ ] 开启 HSTS 防止降级攻击
|
||||
- [ ] 敏感 Cookie 设置 Secure 和 HttpOnly
|
||||
|
||||
**成本控制**
|
||||
|
||||
- [ ] 开启带宽封顶告警,防止异常流量
|
||||
- [ ] 低频/归档存储有最小存储时间和提前删除费,注意规则
|
||||
- [ ] 回源流量费也很贵,努力提高 CDN 命中率
|
||||
@@ -1170,7 +1225,12 @@ class DirectUploader {
|
||||
const formData = new FormData()
|
||||
|
||||
// 构建表单字段(不同厂商字段名不同)
|
||||
const formFields = this._buildFormFields(credentials, fileKey, file.type, options)
|
||||
const formFields = this._buildFormFields(
|
||||
credentials,
|
||||
fileKey,
|
||||
file.type,
|
||||
options
|
||||
)
|
||||
Object.entries(formFields).forEach(([key, value]) => {
|
||||
formData.append(key, value)
|
||||
})
|
||||
@@ -1210,7 +1270,11 @@ class DirectUploader {
|
||||
const fileKey = this._generateFileKey(file, options.directory)
|
||||
|
||||
// 1. 初始化分片上传
|
||||
const uploadId = await this._initMultipartUpload(credentials, fileKey, file.type)
|
||||
const uploadId = await this._initMultipartUpload(
|
||||
credentials,
|
||||
fileKey,
|
||||
file.type
|
||||
)
|
||||
|
||||
// 2. 计算分片
|
||||
const parts = []
|
||||
@@ -1232,21 +1296,30 @@ class DirectUploader {
|
||||
|
||||
// 支持断点续传:检查哪些分片已上传
|
||||
if (options.resume) {
|
||||
const existingParts = await this._listParts(credentials, fileKey, uploadId)
|
||||
const existingParts = await this._listParts(
|
||||
credentials,
|
||||
fileKey,
|
||||
uploadId
|
||||
)
|
||||
for (const part of existingParts) {
|
||||
uploadedParts.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤出未上传的分片
|
||||
const pendingParts = parts.filter(p =>
|
||||
!uploadedParts.some(up => up.partNumber === p.number)
|
||||
const pendingParts = parts.filter(
|
||||
(p) => !uploadedParts.some((up) => up.partNumber === p.number)
|
||||
)
|
||||
|
||||
// 并发上传
|
||||
const uploadPart = async (part) => {
|
||||
try {
|
||||
const etag = await this._uploadPart(credentials, fileKey, uploadId, part)
|
||||
const etag = await this._uploadPart(
|
||||
credentials,
|
||||
fileKey,
|
||||
uploadId,
|
||||
part
|
||||
)
|
||||
return { partNumber: part.number, etag }
|
||||
} catch (error) {
|
||||
failedParts.push({ part, error })
|
||||
@@ -1271,11 +1344,18 @@ class DirectUploader {
|
||||
|
||||
// 检查是否所有分片都上传成功
|
||||
if (uploadedParts.length !== totalParts) {
|
||||
throw new Error(`Upload incomplete: ${uploadedParts.length}/${totalParts} parts uploaded`)
|
||||
throw new Error(
|
||||
`Upload incomplete: ${uploadedParts.length}/${totalParts} parts uploaded`
|
||||
)
|
||||
}
|
||||
|
||||
// 4. 完成分片上传(合并分片)
|
||||
await this._completeMultipartUpload(credentials, fileKey, uploadId, uploadedParts)
|
||||
await this._completeMultipartUpload(
|
||||
credentials,
|
||||
fileKey,
|
||||
uploadId,
|
||||
uploadedParts
|
||||
)
|
||||
|
||||
return {
|
||||
url: this._getFileUrl(fileKey),
|
||||
@@ -1316,7 +1396,11 @@ class DirectUploader {
|
||||
|
||||
_getFileUrl(key) {
|
||||
return `https://${this.bucket}.${this.provider === 'oss' ? 'oss' : 'cos'}-${this.region}.${
|
||||
this.provider === 'oss' ? 'aliyuncs.com' : this.provider === 'cos' ? 'myqcloud.com' : 'amazonaws.com'
|
||||
this.provider === 'oss'
|
||||
? 'aliyuncs.com'
|
||||
: this.provider === 'cos'
|
||||
? 'myqcloud.com'
|
||||
: 'amazonaws.com'
|
||||
}/${key}`
|
||||
}
|
||||
|
||||
@@ -1385,7 +1469,9 @@ async function uploadVideo(file) {
|
||||
parallel: 3, // 3 个并发
|
||||
resume: true, // 支持断点续传
|
||||
onProgress: (progress) => {
|
||||
console.log(`上传进度: ${progress.percent}%, 已传 ${progress.loaded}/${progress.total}`)
|
||||
console.log(
|
||||
`上传进度: ${progress.percent}%, 已传 ${progress.loaded}/${progress.total}`
|
||||
)
|
||||
},
|
||||
onPartComplete: (part) => {
|
||||
console.log(`分片 ${part.number} 上传完成`)
|
||||
@@ -1464,9 +1550,7 @@ router.post('/credentials', async (req, res) => {
|
||||
'oss:AbortMultipartUpload',
|
||||
'oss:ListParts'
|
||||
],
|
||||
Resource: [
|
||||
`acs:oss:*:*:${config.oss.bucket}/${prefix}*`
|
||||
]
|
||||
Resource: [`acs:oss:*:*:${config.oss.bucket}/${prefix}*`]
|
||||
}
|
||||
],
|
||||
Version: '1'
|
||||
@@ -1495,7 +1579,13 @@ router.post('/credentials', async (req, res) => {
|
||||
prefix: prefix, // 文件路径前缀
|
||||
// 安全限制
|
||||
maxSize: 100 * 1024 * 1024, // 最大 100MB
|
||||
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4']
|
||||
allowedTypes: [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'video/mp4'
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1565,27 +1655,27 @@ module.exports = router
|
||||
const refererConfig = {
|
||||
// 白名单模式:只允许以下 Referer 访问
|
||||
allowList: [
|
||||
'*.myapp.com', // 主站
|
||||
'*.myapp.cn', // 国内站
|
||||
'localhost:*', // 本地开发
|
||||
'*.myapp.com', // 主站
|
||||
'*.myapp.cn', // 国内站
|
||||
'localhost:*', // 本地开发
|
||||
'127.0.0.1:*'
|
||||
],
|
||||
|
||||
// 黑名单模式(可选):禁止以下 Referer
|
||||
blockList: [
|
||||
'*. competitor.com', // 竞争对手
|
||||
'*. competitor.com', // 竞争对手
|
||||
'spam-site.com'
|
||||
],
|
||||
|
||||
// 空 Referer 处理:是否允许直接访问(浏览器地址栏输入 URL)
|
||||
allowEmptyReferer: false // 生产环境建议 false,测试环境可 true
|
||||
allowEmptyReferer: false // 生产环境建议 false,测试环境可 true
|
||||
}
|
||||
|
||||
// 2. URL 鉴权(更安全的防盗链,带时间戳和签名)
|
||||
class URLAuth {
|
||||
constructor(config) {
|
||||
this.key = config.key // 鉴权密钥,只在服务端保存
|
||||
this.expireTime = config.expireTime || 3600 // 默认 1 小时有效期
|
||||
this.key = config.key // 鉴权密钥,只在服务端保存
|
||||
this.expireTime = config.expireTime || 3600 // 默认 1 小时有效期
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,11 +1735,14 @@ class URLAuth {
|
||||
// 使用示例
|
||||
const auth = new URLAuth({
|
||||
key: 'your-secret-key-only-known-by-server',
|
||||
expireTime: 3600 // 1 小时有效期
|
||||
expireTime: 3600 // 1 小时有效期
|
||||
})
|
||||
|
||||
// 服务端生成带签名的 URL
|
||||
const signedUrl = auth.sign('https://cdn.example.com/private/document.pdf', 7200)
|
||||
const signedUrl = auth.sign(
|
||||
'https://cdn.example.com/private/document.pdf',
|
||||
7200
|
||||
)
|
||||
// 结果:https://cdn.example.com/private/document.pdf?sign=xxxxx&t=1699123456
|
||||
|
||||
// CDN 边缘或源站验证
|
||||
@@ -1662,15 +1755,12 @@ if (!result.valid) {
|
||||
const ipConfig = {
|
||||
// 只允许特定 IP 访问(适合内部系统)
|
||||
whiteList: [
|
||||
'192.168.1.0/24', // 内网网段
|
||||
'192.168.1.0/24', // 内网网段
|
||||
'10.0.0.0/8'
|
||||
],
|
||||
|
||||
// 禁止特定 IP 访问(封禁攻击者)
|
||||
blackList: [
|
||||
'1.2.3.4',
|
||||
'5.6.7.8'
|
||||
]
|
||||
blackList: ['1.2.3.4', '5.6.7.8']
|
||||
}
|
||||
|
||||
// 4. UA(User-Agent)黑白名单
|
||||
@@ -1687,7 +1777,7 @@ const uaConfig = {
|
||||
|
||||
// 只允许浏览器访问(严格模式)
|
||||
whiteList: [
|
||||
'Mozilla/*', // 现代浏览器
|
||||
'Mozilla/*', // 现代浏览器
|
||||
'AppleWebKit/*'
|
||||
]
|
||||
}
|
||||
@@ -1697,28 +1787,28 @@ const uaConfig = {
|
||||
|
||||
## 10. 名词对照表
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Object Storage** | 对象存储 | 一种数据存储架构,将数据作为对象管理,而非文件系统层级结构。适合存储图片、视频、备份等非结构化数据。 |
|
||||
| **Bucket** | 存储桶 | 对象存储中的顶级容器,用于组织和隔离数据。每个桶有独立的权限控制和配置。 |
|
||||
| **Object** | 对象/文件对象 | 对象存储的基本单元,包含数据本身、元数据(Metadata)和全局唯一键(Key)。 |
|
||||
| **CDN** | 内容分发网络 | Content Delivery Network,通过在全球部署边缘节点,将网站内容缓存到离用户最近的位置,加速访问速度。 |
|
||||
| **Edge Node** | 边缘节点 | CDN 网络中部署在各地的缓存服务器,直接为用户提供内容访问服务。 |
|
||||
| **Origin** | 源站 | CDN 回源获取内容的服务器,可以是对象存储、ECS 或自建服务器。 |
|
||||
| **Cache Hit** | 缓存命中 | 用户请求的内容在 CDN 边缘节点已存在,直接返回,无需回源。 |
|
||||
| **Cache Miss** | 缓存未命中 | 边缘节点没有请求的内容,需要回源获取。 |
|
||||
| **Hit Ratio** | 命中率 | 缓存命中次数占总请求次数的比例。命中率越高,回源越少,成本越低。 |
|
||||
| **TTL** | 生存时间/缓存时间 | Time To Live,内容在 CDN 节点上缓存的有效期。过期后需要重新回源。 |
|
||||
| **Back to Source** | 回源 | CDN 边缘节点向源站请求内容的过程。 |
|
||||
| **Purge/Refresh** | 刷新缓存 | 强制使 CDN 缓存失效,下次请求回源获取最新内容。 |
|
||||
| **Preheat** | 预热 | 在正式发布前,主动将内容推送到 CDN 节点,让用户第一次访问就能命中缓存。 |
|
||||
| **CORS** | 跨域资源共享 | Cross-Origin Resource Sharing,浏览器的安全机制,控制不同域之间的资源访问。 |
|
||||
| **Referer** | 来源页面 | HTTP 请求头字段,指示请求是从哪个页面链接过来的。用于防盗链。 |
|
||||
| **STS** | 安全令牌服务 | Security Token Service,颁发临时访问凭证的服务,用于前端直传等场景。 |
|
||||
| **Multipart Upload** | 分片上传 | 将大文件切分成多个小分片并行上传,支持断点续传,提高上传效率和可靠性。 |
|
||||
| **ETag** | 实体标签 | HTTP 响应头,用于标识资源的特定版本,常用于缓存验证。 |
|
||||
| **S3 API** | S3 兼容接口 | AWS S3 的对象存储 API 规范,多数云厂商的对象存储都兼容此接口。 |
|
||||
| **Canonical Query String** | 规范查询字符串 | 签名字符串的一部分,用于计算请求签名,确保请求不被篡改。 |
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :------------------------- | :---------------- | :--------------------------------------------------------------------------------------------------- |
|
||||
| **Object Storage** | 对象存储 | 一种数据存储架构,将数据作为对象管理,而非文件系统层级结构。适合存储图片、视频、备份等非结构化数据。 |
|
||||
| **Bucket** | 存储桶 | 对象存储中的顶级容器,用于组织和隔离数据。每个桶有独立的权限控制和配置。 |
|
||||
| **Object** | 对象/文件对象 | 对象存储的基本单元,包含数据本身、元数据(Metadata)和全局唯一键(Key)。 |
|
||||
| **CDN** | 内容分发网络 | Content Delivery Network,通过在全球部署边缘节点,将网站内容缓存到离用户最近的位置,加速访问速度。 |
|
||||
| **Edge Node** | 边缘节点 | CDN 网络中部署在各地的缓存服务器,直接为用户提供内容访问服务。 |
|
||||
| **Origin** | 源站 | CDN 回源获取内容的服务器,可以是对象存储、ECS 或自建服务器。 |
|
||||
| **Cache Hit** | 缓存命中 | 用户请求的内容在 CDN 边缘节点已存在,直接返回,无需回源。 |
|
||||
| **Cache Miss** | 缓存未命中 | 边缘节点没有请求的内容,需要回源获取。 |
|
||||
| **Hit Ratio** | 命中率 | 缓存命中次数占总请求次数的比例。命中率越高,回源越少,成本越低。 |
|
||||
| **TTL** | 生存时间/缓存时间 | Time To Live,内容在 CDN 节点上缓存的有效期。过期后需要重新回源。 |
|
||||
| **Back to Source** | 回源 | CDN 边缘节点向源站请求内容的过程。 |
|
||||
| **Purge/Refresh** | 刷新缓存 | 强制使 CDN 缓存失效,下次请求回源获取最新内容。 |
|
||||
| **Preheat** | 预热 | 在正式发布前,主动将内容推送到 CDN 节点,让用户第一次访问就能命中缓存。 |
|
||||
| **CORS** | 跨域资源共享 | Cross-Origin Resource Sharing,浏览器的安全机制,控制不同域之间的资源访问。 |
|
||||
| **Referer** | 来源页面 | HTTP 请求头字段,指示请求是从哪个页面链接过来的。用于防盗链。 |
|
||||
| **STS** | 安全令牌服务 | Security Token Service,颁发临时访问凭证的服务,用于前端直传等场景。 |
|
||||
| **Multipart Upload** | 分片上传 | 将大文件切分成多个小分片并行上传,支持断点续传,提高上传效率和可靠性。 |
|
||||
| **ETag** | 实体标签 | HTTP 响应头,用于标识资源的特定版本,常用于缓存验证。 |
|
||||
| **S3 API** | S3 兼容接口 | AWS S3 的对象存储 API 规范,多数云厂商的对象存储都兼容此接口。 |
|
||||
| **Canonical Query String** | 规范查询字符串 | 签名字符串的一部分,用于计算请求签名,确保请求不被篡改。 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,424 +1,461 @@
|
||||
# 前端开发入门:从"贴海报"到"搭乐高" (Interactive Intro)
|
||||
# 前端开发演进史:从"贴海报"到"搭乐高"
|
||||
|
||||
> 💡 **学习指南**:本章节无需编程基础,通过交互式演示带你回顾前端开发的 20 年变迁。我们将从最基础的 HTML 讲起,一直到现代的 Vue/React 组件化开发。
|
||||
::: tip 🎯 核心问题
|
||||
**为什么网页越来越复杂?前端技术为什么要不断演进?** 这个问题会带你理解从简单网页到现代 Web 应用的技术演变之路。
|
||||
:::
|
||||
|
||||
先把几个最常见的新名词说清楚(后面会反复出现):
|
||||
---
|
||||
|
||||
- **HTML**:网页的"骨架",负责内容和结构(标题、段落、图片、按钮)。
|
||||
- **CSS**:网页的"皮肤",负责样式(颜色、大小、布局、动画)。
|
||||
- **JavaScript**:网页的"肌肉",负责交互与逻辑(点击、输入、请求数据)。
|
||||
- **框架(Framework)**:一套成熟的开发方式和工具,让你更高效地做复杂页面(比如 Vue/React)。
|
||||
## 1. 为什么要关注前端演进史?
|
||||
|
||||
### 1.1 从"电子海报"到"桌面应用"
|
||||
|
||||
想象一下你在街上看到的**海报**:
|
||||
|
||||
- ✅ 有内容(文字、图片)
|
||||
- ✅ 有设计(颜色、排版)
|
||||
- ❌ 但你跟它说话,它不会回应
|
||||
- ❌ 你点击某个地方,不会发生什么
|
||||
|
||||
**最早的网页**就是这样的"电子海报":只能看、不能改、内容固定。
|
||||
|
||||
**现代网页**完全不同了。它们像**桌面应用**(VS Code、Figma):
|
||||
|
||||
- ✅ 可以编辑文档、画图、玩游戏
|
||||
- ✅ 实时响应你的每个操作
|
||||
- ✅ 甚至可以离线工作
|
||||
|
||||
**这种转变的核心原因: 网页的功能越来越复杂,需要更高效的技术和开发方式。**
|
||||
|
||||
### 1.2 一个生活的比喻:盖房子
|
||||
|
||||
前端技术的演进,就像盖房子方式的进化:
|
||||
|
||||
| 时代 | 🏠 盖房比喻 | 实际特点 | 优缺点 |
|
||||
| --------- | ------------------ | ---------------------------- | --------------------------- |
|
||||
| **2000s** | **贴海报** | 静态网页,写好 HTML 就行 | ✅ 简单 ❌ 不能互动 |
|
||||
| **2010s** | **请工人手动装修** | jQuery 时代,手动操作每个元素 | ✅ 能互动 ❌ 代码乱、难维护 |
|
||||
| **2020s** | **用乐高搭房子** | Vue/React 时代,组件化开发 | ✅ 高效、可维护 ❌ 学习曲线 |
|
||||
|
||||
::: tip 💡 从表格中你能看到什么?
|
||||
|
||||
**阶段一 → 阶段二**: 从"不能动"到"能动"。这是质的飞跃——网页开始有交互,但代价是代码变得混乱。
|
||||
|
||||
**阶段二 → 阶段三**: 从"能用"到"好用"。组件化让代码像积木一样可复用,大幅提升开发效率。
|
||||
|
||||
**核心思想**: 技术演进不是"为了新而新",而是为了解决上一个阶段的痛点。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 第一阶段:静态网页与"切图"(2000s)
|
||||
|
||||
<FrontendEvolutionDemo />
|
||||
|
||||
## 0. 引言:网页为什么越来越难做?
|
||||
### 2.1 这个时代是什么样的?
|
||||
|
||||
最早的网页,只是**电子海报**——就像你在街上看到的纸质海报,只能看、不能互动。
|
||||
**开发方式**:
|
||||
|
||||
现在的网页,是**桌面级应用** (如 VS Code, Figma)——可以编辑文档、画图、玩游戏,甚至剪辑视频。
|
||||
- 写几个 HTML 文件
|
||||
- 内嵌一些 CSS 和 JavaScript
|
||||
- 直接把文件拖到浏览器就能看效果
|
||||
- 上传文件夹到服务器就完成部署
|
||||
|
||||
为了支撑这种转变,前端技术经历了一场从 "手工作坊" 到 "工业化生产" 的革命。
|
||||
**特点**:
|
||||
|
||||
### 一个生活的比喻
|
||||
- ✅ **优点**: 简单直接,没有学习成本,写完就能跑
|
||||
- ❌ **缺点**: 无法实现复杂交互,代码一多就乱
|
||||
|
||||
想象你要盖房子:
|
||||
::: details 查看当时的项目结构
|
||||
|
||||
- **2000 年代(静态网页)**:就像**贴海报**。你画好一张图,贴到墙上就完事了,不能改动。
|
||||
- **2010 年代(jQuery 时代)**:就像**请工人手动装修**。你需要亲自告诉工人:"把这块墙涂成蓝色"、"把那扇窗户打开"。工人很多、指令很杂,容易出错。
|
||||
- **2020 年代(Vue/React 时代)**:就像**用乐高积木搭房子**。你先设计好"房子长什么样"(设计图),然后乐高积木(组件)会自动按设计图组装好,不需要你一块一块手动拼。
|
||||
```
|
||||
project/
|
||||
├── index.html
|
||||
├── login.html
|
||||
├── css/
|
||||
│ ├── bootstrap.css
|
||||
│ └── custom.css
|
||||
├── js/
|
||||
│ ├── jquery.js
|
||||
│ └── app.js
|
||||
└── images/
|
||||
```
|
||||
|
||||
**核心变化只有一点:页面越来越复杂,我们需要更高效的"组织方式"和"开发方式"。**
|
||||
**遇到的问题**:
|
||||
|
||||
### 0.1 前端 vs 大前端(你到底在学什么?)
|
||||
1. **全局变量污染**: 所有变量都在全局命名空间,容易互相覆盖
|
||||
2. **依赖管理混乱**: 必须按正确顺序加载 JS 文件,否则会报错
|
||||
3. **代码难以复用**: 想复用某个功能,只能复制粘贴
|
||||
:::
|
||||
|
||||
很多人说"我学前端",但不同公司口径不一样。
|
||||
### 2.2 "切图"是什么?
|
||||
|
||||
- **前端(Web Frontend)**:主要指"在浏览器里跑的那部分"。典型产物是网站和 H5 页面。
|
||||
- **大前端(Big Frontend)**:泛指"所有用户界面相关的开发"。不只 Web,还包括小程序、App、桌面应用等。
|
||||
<SliceRequestDemo />
|
||||
|
||||
这里的几个新词(后面也会用到):
|
||||
你可能听说过"切图"这个词。它是早期前端的主要工作:
|
||||
|
||||
- **端**:平台/运行环境的意思,比如 Web 端、移动端、桌面端。
|
||||
- **H5**:手机网页(本质也是 Web),通常用来做活动页/落地页,传播快、迭代快。
|
||||
- **WebView**:App 里用来显示网页的"内置浏览器控件"。很多 App 的部分页面其实就是 WebView。
|
||||
- **跨端**:用一套代码同时做多个端(比如同时做 iOS + Android)。
|
||||
- **原生**:直接用平台官方语言/能力开发(iOS 的 Swift、Android 的 Kotlin)。
|
||||
**什么是切图?**
|
||||
|
||||
<BigFrontendScopeDemo />
|
||||
设计师用 Photoshop 设计好页面 → 前端把设计切成小图片 → 用 HTML 把图片拼成页面
|
||||
|
||||
**关键点**:大前端不是一个"新岗位名字",而是一种范围:把体验交付到更多平台。
|
||||
**为什么这么慢?**
|
||||
|
||||
网页上的每张小图片,浏览器都要发一次**网络请求**。请求越多,加载越慢。
|
||||
|
||||
::: tip 💡 雪碧图(Sprite)
|
||||
|
||||
为了减少请求数,出现了"雪碧图"技术:把很多小图合成一张大图。
|
||||
|
||||
优点是请求数变少,缺点是制作和维护都很麻烦。
|
||||
|
||||
这个阶段的教训:**请求太多是性能大敌**。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. 第一阶段:静态网页与切图 (2000s-)
|
||||
## 3. 第二阶段:jQuery 时代 - "手动搬砖"(2010s)
|
||||
|
||||
早期网页开发像做报纸:设计师把 UI 设计切成很多小图,前端把这些图片拼成页面。
|
||||
这就叫**切图**。
|
||||
### 3.1 为什么需要 jQuery?
|
||||
|
||||
这里的几个新词:
|
||||
随着网页变复杂,原生 JavaScript 的问题暴露出来:
|
||||
|
||||
- **静态网页**:页面内容基本固定,打开就是一份 HTML 文件(不像现在很多页面是"数据驱动、可交互"的)。
|
||||
- **UI**:User Interface,用户界面。也就是你看到的按钮、颜色、布局。
|
||||
- ❌ **API 繁琐**: 简单的操作也要写很多代码
|
||||
- ❌ **浏览器兼容**: 不同浏览器的 API 不一样,要写很多兼容代码
|
||||
- ❌ **选择器弱**: 找元素很麻烦
|
||||
|
||||
### 1.1 为什么会慢?
|
||||
**jQuery** 诞生了。它让 JavaScript 变得简单:
|
||||
|
||||
网页上的每一张小图,浏览器都要发一次**网络请求**。
|
||||
请求越多,加载越慢。
|
||||
```javascript
|
||||
// 原生 JavaScript (繁琐)
|
||||
const element = document.getElementById('title')
|
||||
|
||||
想象一下你点外卖:
|
||||
- 如果你一次性下单 10 道菜,餐厅可以一起做完送过来。
|
||||
- 但如果你分 10 次下单,每次只点 1 道菜,骑手要跑 10 趟!
|
||||
// jQuery (简洁)
|
||||
const element = $('#title')
|
||||
```
|
||||
|
||||
早期的网页就像"分 10 次下单",每张图片都要单独"下单"(发请求)。
|
||||
### 3.2 jQuery 的思路:亲手改页面
|
||||
|
||||
<EvolutionSliceRequestDemo />
|
||||
<JQueryVsStateDemo />
|
||||
|
||||
补充一个常见技巧:**雪碧图 (Sprite)**。
|
||||
把很多小图合成一张大图,这样请求数会变少(但制作和维护更麻烦)。
|
||||
jQuery 的核心思路是**命令式**: 你告诉浏览器"怎么做"。
|
||||
|
||||
**关键点**:早期网页慢,常见原因之一是"请求太多"。(图片、脚本、样式都会产生请求)
|
||||
```javascript
|
||||
// 找到标题元素
|
||||
$('#title').text('新标题')
|
||||
|
||||
// 找到按钮并禁用
|
||||
$('#submit-btn').attr('disabled', true)
|
||||
|
||||
// 找到列表并添加一项
|
||||
$('ul').append('<li>新项目</li>')
|
||||
```
|
||||
|
||||
**问题**: 你需要记住页面上有哪些元素,每次数据变化都要手动更新所有相关元素。
|
||||
|
||||
::: warning ⚠️ jQuery 的痛点
|
||||
|
||||
想象你在做一个购物车:
|
||||
|
||||
```javascript
|
||||
// 用户点击"添加到购物车"
|
||||
function addToCart() {
|
||||
cartCount++ // 数据变化
|
||||
|
||||
// 你要手动更新所有相关地方
|
||||
$('#cart-count').text(cartCount) // 右上角小红点
|
||||
$('#cart-page-count').text(cartCount) // 购物车页面
|
||||
$('#checkout-price').text(calculatePrice()) // 结算按钮
|
||||
|
||||
// 如果漏了一个地方,页面就不一致了!
|
||||
}
|
||||
```
|
||||
|
||||
**这就是"手动搬砖"的代价**: 容易出错,难以维护。
|
||||
:::
|
||||
|
||||
### 3.3 移动端普及:响应式设计的出现
|
||||
|
||||
这个阶段还有一个重要变化:**手机和平板开始流行**。
|
||||
|
||||
<ResponsiveGridDemo />
|
||||
|
||||
网页必须适配不同屏幕。这需要**响应式布局**: 同一套 HTML/CSS,自动根据屏幕宽度变换布局。
|
||||
|
||||
**响应式布局的核心: 媒体查询 (Media Query)**
|
||||
|
||||
```css
|
||||
/* 电脑屏幕(大于 640px) */
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机屏幕(小于 640px) */
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip 💡 响应式就像"智能相框"
|
||||
|
||||
想象你在不同房间看同一张照片:
|
||||
|
||||
- 在**大客厅**(电脑屏幕),照片可以摆大一些,旁边还能放其他装饰品
|
||||
- 在**小卧室**(手机屏幕),照片需要缩小,其他装饰品要收起来
|
||||
|
||||
**响应式布局**就是"智能相框",它会自动根据房间大小调整展示方式。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 第二阶段:移动端普及与响应式布局 (2010s)
|
||||
## 4. 第三阶段:从"手动搬砖"到"数据驱动"(Vue/React)
|
||||
|
||||
手机和平板开始流行以后,网页必须适配不同屏幕。
|
||||
这就需要**响应式布局**:同一套 HTML/CSS,自动根据屏幕宽度变换布局。
|
||||
### 4.1 为什么需要新框架?
|
||||
|
||||
这里用到了**媒体查询 (Media Query)**:
|
||||
它是 CSS 里的"条件判断",比如"如果屏幕小于 640px,就用 1 列布局"。
|
||||
jQuery 时代的问题积累到一定程度:
|
||||
|
||||
想象一下你在不同房间看同一张照片:
|
||||
- 在**大客厅**(电脑屏幕),照片可以摆大一些,旁边还能放其他装饰品。
|
||||
- 在**小卧室**(手机屏幕),照片需要缩小,其他装饰品要收起来,否则会挤不下。
|
||||
- **代码一多就乱**: 到处都是 DOM 操作,难以维护
|
||||
- **容易出 bug**: 漏更新一个地方,页面就不一致
|
||||
- **协作困难**: 多人修改同一个文件,容易冲突
|
||||
|
||||
**响应式布局**就是"智能相框",它会自动根据房间大小调整展示方式。
|
||||
**Vue / React** 的核心思路:**只改数据,页面自动更新**。
|
||||
|
||||
<EvolutionResponsiveGridDemo />
|
||||
|
||||
**关键点**:响应式让网页"会变形",不再只适配电脑。
|
||||
|
||||
---
|
||||
|
||||
## 3. 第三阶段:从"手动搬砖"到"数据驱动" (jQuery -> Vue/React)
|
||||
|
||||
网页开始像 App 一样复杂之后,最麻烦的事变成了:**同一份数据变化,要改很多地方**。
|
||||
|
||||
举个最常见的例子:购物车数量从 1 变成 2。
|
||||
|
||||
- 右上角小红点要变
|
||||
- 购物车页面的数量要变
|
||||
- 结算按钮的价格要变
|
||||
|
||||
下面这个可视化演示,专门用来解释:**什么是 jQuery(以及它为什么会累)**。
|
||||
|
||||
想象一下你在餐厅当服务员:
|
||||
- **jQuery 时代**:客人点了一份牛排,你要亲自跑厨房告诉厨师、跑吧台拿饮料、跑收银台记账。客人加菜,你又要重新跑一遍。**你成了"跑腿王",累得半死**。
|
||||
- **Vue/React 时代**:你只需在单子上写好"客人要什么"(数据),厨房、吧台、收银台会自动看单子做事。客人加菜,你只需改单子,其他地方**自动更新**。**你成了"指挥家",轻松优雅**。
|
||||
|
||||
<EvolutionJQueryVsStateDemo />
|
||||
|
||||
### 3.1 jQuery 的思路:我来"亲手改页面"
|
||||
|
||||
在 jQuery 时代(2005+),你通常会写很多"命令"去改页面:
|
||||
"找到某个元素,把文字改掉;找到某个按钮,把它禁用……"
|
||||
|
||||
它的问题不是"写不出来",而是:**只要漏改一个地方,页面就会出现前后不一致的 bug**。
|
||||
页面越大,这种 bug 越多。
|
||||
|
||||
就像你手动修一栋大楼:
|
||||
- 你要记住"每个房间的长什么样"。
|
||||
- 你要亲自"一间一间地修"。
|
||||
- 如果你忘了修某间房,或者修错了,整栋楼就不一致了。
|
||||
|
||||
这里用到的新词(先解释清楚):
|
||||
|
||||
- **jQuery**:早期非常流行的 JavaScript 工具库,特点是"很方便地找元素、改元素"。
|
||||
- **DOM**:浏览器里保存页面结构的一棵"树"(按钮、文字、图片都在这棵树上)。
|
||||
- **ID**:HTML 元素的唯一名字(类似"身份证号"),方便你定位某一个元素。
|
||||
- **div**:HTML 里最常用的"盒子"标签,用来做布局和容器。
|
||||
|
||||
### 3.2 Vue/React 的思路:我只改"数据",页面自己变
|
||||
|
||||
后来大家意识到:与其到处改页面,不如只维护一份**状态 (State)**。
|
||||
状态变了,页面自动刷新到正确的样子。
|
||||
|
||||
这就是"数据驱动 UI"的核心:
|
||||
|
||||
- **State(状态)**:页面的"数据",比如购物车数量、登录状态、输入框内容。
|
||||
- **数据驱动**:你只改 State,不直接改 DOM;框架负责把界面同步到正确状态。
|
||||
- **Vue/React**:现代前端框架,主要解决"状态变化 -> 界面自动更新"。
|
||||
|
||||
想象一下你在玩"模拟城市"游戏:
|
||||
- 你不需要"手动画每一栋房子"(手动改 DOM)。
|
||||
- 你只需要调整"城市数据"(比如人口、资金、政策),游戏画面**自动更新**。
|
||||
|
||||
**这就是数据驱动的魅力:你只关心"数据长什么样",不关心"页面怎么画"。**
|
||||
|
||||
### 3.3 什么是"命令式"和"声明式"?
|
||||
|
||||
这就好比你要画一幅画:
|
||||
|
||||
- **命令式**:你告诉画家"拿起笔,蘸红颜料,在坐标(10,10)画一个圈"。
|
||||
- **声明式**:你直接给画家一张照片,"给我画成这样"。
|
||||
|
||||
想象一下你点披萨:
|
||||
- **命令式**:你亲自和面、撒料、烤披萨、切披萨。你要记住每一步,很累。
|
||||
- **声明式**:你只需说"我要一个芝士披萨",披萨店自动做好。你只需"声明"你要什么,不关心"怎么做"。
|
||||
|
||||
### 3.4 交互演示:两种写法的区别
|
||||
|
||||
下方的演示展示了两种思维的巨大差异。
|
||||
|
||||
- **左边 (jQuery)**:你需要手动关注每一步 DOM 操作。忘了更新 DOM?界面就不对了。
|
||||
- **右边 (Vue)**:你只管修改数据 `count`,界面自动变。
|
||||
### 4.2 Vue/React 的思路:声明式 UI
|
||||
|
||||
<ImperativeVsDeclarativeDemo />
|
||||
|
||||
**关键点**:从 jQuery 到 Vue/React,变化的核心不是"语法",而是**思维方式**:从"我去改页面"变成"我只改数据"。
|
||||
**jQuery (命令式)**:
|
||||
|
||||
### 3.5 Vue 和 React 怎么选?先把差异理解清楚
|
||||
```javascript
|
||||
// 你要告诉浏览器每一步怎么做
|
||||
$('#title').text('新标题')
|
||||
$('#title').css('color', 'red')
|
||||
$('#title').show()
|
||||
```
|
||||
|
||||
很多初学者会纠结:"我到底学 Vue 还是 React?"
|
||||
先别急着站队。你先把它们的"共同点"和"差异点"理解清楚,就不会被带节奏了。
|
||||
**Vue (声明式)**:
|
||||
|
||||
**共同点(它们都在解决同一件事)**:
|
||||
```javascript
|
||||
// 你只需告诉浏览器"要显示什么"
|
||||
data() {
|
||||
return {
|
||||
title: "新标题",
|
||||
color: "red",
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 都是为了解决:页面复杂时,如何可靠地管理状态、更新界面
|
||||
- 都强调:组件化(把页面拆成积木)
|
||||
::: tip 💡 命令式 vs 声明式
|
||||
|
||||
**差异点(你会在写代码时真实感受到)**:
|
||||
就像画一幅画:
|
||||
|
||||
- **写 UI 的方式**:Vue 常用 Template;React 常用 JSX
|
||||
- **状态变化时怎么更新**:Vue 更偏"依赖追踪";React 更偏"重新渲染组件函数"
|
||||
- **命令式**: 你告诉画家"拿起笔,蘸红颜料,在坐标(10,10)画一个圈"
|
||||
- **声明式**: 你直接给画家一张照片,"给我画成这样"
|
||||
|
||||
这里的几个新词(像课件一样解释清楚):
|
||||
Vue/React 就是"声明式": 你描述"页面长什么样",框架负责"怎么把它画出来"。
|
||||
:::
|
||||
|
||||
- **Template**:Vue 常见写法,用类似 HTML 的语法来写界面。
|
||||
- **JSX**:React 常见写法,用"像写 JS 一样"的方式写界面结构。
|
||||
- **Hook**:React 的一套函数式能力(比如 `useState`),用来保存状态、处理副作用。
|
||||
- **SFC**:Single File Component,Vue 常见的单文件组件(一个 `.vue` 文件里写模板/逻辑/样式)。
|
||||
### 4.3 组件化:像搭乐高一样写页面
|
||||
|
||||
<VueReactComparisonDemo />
|
||||
**Vue / React** 最强大的特性是**组件化**: 把页面拆成一个个独立的"积木"。
|
||||
|
||||
**关键点**:别死记名词。你只要记住一句话:它们都能做同样的产品,只是"写法和心智模型"不一样。
|
||||
想象一下你在搭乐高:
|
||||
|
||||
---
|
||||
- 你不需要"从头开始雕刻每一块积木"(从头写 HTML/CSS)
|
||||
- 你只需要"按说明书把积木拼在一起"(把组件组合起来)
|
||||
- 每个积木都是**独立的**,你可以在不同的套装里**重复使用**
|
||||
|
||||
## 4. 第四阶段:组件化(像搭积木一样写页面)
|
||||
**组件的好处**:
|
||||
|
||||
解决了"怎么更新页面"的问题,接下来是"怎么组织代码"。
|
||||
以前一个页面可能是一个超大的 HTML 文件,改一个按钮可能牵连全局。
|
||||
- **复用**: 写一个"商品卡片"组件,可以用 100 次
|
||||
- **封装**: 组件内部的状态不影响别人
|
||||
- **维护**: 修改一个组件,所有用到它的地方都会更新
|
||||
|
||||
### 4.1 "积木"是什么?
|
||||
|
||||
现代前端把页面拆成了**组件**。
|
||||
一个按钮、一个导航栏、一个商品卡片,都是独立的积木。
|
||||
|
||||
想象一下你在搭乐高:
|
||||
- 你不需要"从头开始雕刻每一块积木"(从头写 HTML/CSS)。
|
||||
- 你只需要"按说明书把积木拼在一起"(把组件组合起来)。
|
||||
- 每个积木都是**独立的**,你可以在不同的套装里**重复使用**。
|
||||
|
||||
### 4.2 为什么组件能复用?
|
||||
|
||||
定义好一个"商品卡片"组件后,你可以由它生成 100 个实例。每个实例都有自己独立的状态(比如点赞状态),互不干扰。
|
||||
|
||||
想象一下你有一个"万能开关"组件:
|
||||
- 你可以把这个开关放在客厅、卧室、厨房。
|
||||
- 每个开关都是**独立的**:你按客厅的开关,不会影响到卧室的灯。
|
||||
- 但它们都是**同一个组件**,你只需要设计一次"开关长什么样",就可以到处使用。
|
||||
|
||||
<ComponentReusabilityDemo />
|
||||
|
||||
**新名词解释**:
|
||||
|
||||
- **组件 (Component)**:页面里的"积木块",可以单独复用。
|
||||
- **封装**:组件内部的状态不影响别人。
|
||||
- **复用**:同一个组件可以用很多次。
|
||||
|
||||
**关键点**:组件化让页面像搭积木一样搭出来。
|
||||
|
||||
---
|
||||
|
||||
## 5. 第五阶段:页面切换体验(MPA vs SPA)
|
||||
|
||||
用户不再想要"每点一次就刷新整页"的体验。
|
||||
于是出现了**单页应用 (SPA)**:整个网站只加载一次,之后只是切换内容。
|
||||
|
||||
与之对应的是**多页应用 (MPA)**:每点一次都会重新加载整个页面。
|
||||
|
||||
这里的一个新词:**路由 (Routing)**。
|
||||
简单理解:就是"从 A 页面切到 B 页面"的规则和过程。
|
||||
|
||||
再补两个新词(非常重要):
|
||||
|
||||
- **前端路由**:页面切换主要由浏览器里的 JavaScript 控制(常见于 SPA)。
|
||||
- **后端路由**:页面切换主要由服务器决定"返回哪个页面"(常见于 MPA)。
|
||||
|
||||
想象一下你在看一本书:
|
||||
- **MPA(多页应用)**:就像**翻书**。每翻一页,你都要把旧书合上、去书架上拿一本新书。慢,而且你之前在书上做的笔记(比如折页)都会消失。
|
||||
- **SPA(单页应用)**:就像**在同一页纸上换内容**。你只需要擦掉旧内容、写上新内容,**纸还是那张纸**。快,而且你做的笔记一直都在。
|
||||
### 4.4 SPA:单页应用的诞生
|
||||
|
||||
<RoutingModeDemo />
|
||||
|
||||
### 5.1 MPA 是什么?(多页应用)
|
||||
**Vue / React** 时代还有一个重要变化:**从 MPA 到 SPA**。
|
||||
|
||||
MPA 的直觉很像"翻书":
|
||||
**MPA (Multi-Page Application)**:
|
||||
|
||||
- 点"商品页" -> 浏览器向服务器要一个新的页面(新的 HTML)
|
||||
- 旧页面被替换掉 -> 原来的输入、滚动位置、临时数据往往会消失
|
||||
- 点一个链接 → 整页刷新 → 显示新页面
|
||||
- 就像**翻书**: 每翻一页都要把旧书合上、去书架拿新书
|
||||
|
||||
想象一下你在逛商场:
|
||||
- 每进一家店(点一个链接),你都要**走出商场、重新排队进门**(整页刷新)。
|
||||
- 你在上一家店试过的衣服(输入的内容)、拿过的购物车,**全部清空**。
|
||||
**SPA (Single-Page Application)**:
|
||||
|
||||
**优点(为什么很多网站仍在用)**:
|
||||
- 点一个链接 → 只刷新内容区域 → 页面不刷新
|
||||
- 就像**同一本书里换章节**: 只擦掉旧内容、写上新内容
|
||||
|
||||
- 结构简单:服务器负责"出页面",浏览器负责"展示"
|
||||
- SEO 友好:搜索引擎更容易直接看到页面内容
|
||||
- 首屏容易快:因为服务器直接给了 HTML
|
||||
**SPA 的优点**:
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 体验偏"跳":整页刷新会白一下、加载一下
|
||||
- 复杂交互会变难:页面之间共享状态不方便
|
||||
|
||||
### 5.2 SPA 是什么?(单页应用)
|
||||
|
||||
SPA 更像"同一本书里换章节":
|
||||
|
||||
- 第一次打开:加载一个"外壳页面"(HTML + CSS + JS)
|
||||
- 之后切换页面:通常只换内容区域,整页不刷新
|
||||
|
||||
想象一下你在用手机的 App:
|
||||
- 打开微信(第一次加载),之后你刷朋友圈、看聊天、进公众号,**页面不会重新加载**,只是内容在切换。
|
||||
- 你输入了一半的消息、看到的滚动位置,**切换后再回来还在**。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 体验丝滑:页面切换快
|
||||
- 状态好管理:同一个页面里,数据更容易共享(登录态、购物车等)
|
||||
|
||||
**缺点(也要知道)**:
|
||||
|
||||
- 首次加载可能更慢:需要先下载一堆 JS
|
||||
- SEO 要额外处理:通常需要 SSR/SSG 方案配合(后面第 7 阶段会讲)
|
||||
|
||||
### 5.3 交互演示:状态会不会丢?
|
||||
|
||||
下面这个小实验更直观:输入一段文字,然后切换页面再回来,看看有没有被清空。
|
||||
|
||||
想象一下你正在填写一张申请表:
|
||||
- **MPA(翻书模式)**:你填到一半,去另一页查资料,回来发现**表格被清空了**,要重新填。
|
||||
- **SPA(同一页模式)**:你填到一半,去另一页查资料,回来发现**表格还在**,继续填就行。
|
||||
|
||||
<SpaStatePreservationDemo />
|
||||
|
||||
**关键点**:从"整页刷新"到"局部更新",带来的不仅是速度,更是"状态能不能保留"的体验差异。
|
||||
- ✅ **体验丝滑**: 页面切换快
|
||||
- ✅ **状态好管理**: 输入的内容、滚动位置都在
|
||||
- ❌ **首屏可能慢**: 需要先下载 JavaScript
|
||||
- ❌ **SEO 要额外处理**: 搜索引擎可能抓不到内容(需要 SSR/SSG)
|
||||
|
||||
---
|
||||
|
||||
## 6. 第六阶段:工程化(从"手工作坊"到"现代化工厂")
|
||||
|
||||
前端项目越来越大,不能再靠手动引入脚本文件。
|
||||
于是有了**打包工具**(Webpack/Vite):把多个文件和依赖打成一个或多个"优化后的包"。
|
||||
|
||||
想象一下你在整理行李:
|
||||
- **以前(手动引入)**:你要出门,把衣服、裤子、袜子一件一件拿在手里,容易丢、容易乱。
|
||||
- **现在(工程化打包)**:你把所有东西**打包进一个行李箱**,拉着就走,整齐又方便。
|
||||
|
||||
**依赖**就是你用到的第三方库,比如图表库、编辑器。
|
||||
|
||||
想象一下你在做饭:
|
||||
- 你要做蛋糕,需要面粉、鸡蛋、糖(**依赖**)。
|
||||
- 你不需要自己种小麦、养鸡(**自己写所有代码**),而是去超市买现成的(**使用第三方库**)。
|
||||
- **工程化**就是"超市购物清单",帮你自动把所有需要的食材买齐、分类放好。
|
||||
|
||||
<BundlerSizeDemo />
|
||||
|
||||
这里的几个新词:
|
||||
|
||||
- **工程化**:用工具和规范把项目"像工程一样"管理(目录结构、构建、发布、代码规范等)。
|
||||
- **Bundle(包)**:打包后的产物文件。
|
||||
- **Tree Shaking**:把"没用到的代码"从包里摇掉,体积更小。
|
||||
|
||||
**关键点**:工程化让多人协作的大项目变得可控。
|
||||
|
||||
---
|
||||
|
||||
## 7. 第七阶段:渲染方式(CSR / SSR / SSG)
|
||||
|
||||
为了更快的首屏、更好的搜索排名,渲染方式也在进化。
|
||||
|
||||
想象一下你在餐厅吃饭,有三种服务模式:
|
||||
|
||||
- **CSR(客户端渲染)**:服务员给你一个**半成品食材包**(JS 文件),你自己在桌上做饭。等菜时间长(要下载 JS),但做完后你可以随时"热更新"(交互流畅)。
|
||||
|
||||
- **SSR(服务端渲染)**:服务员在**厨房做好菜**(服务器渲染 HTML),直接端给你。上菜快(首屏快),但你想加辣(交互),还得等厨师(服务器响应)。
|
||||
|
||||
- **SSG(静态生成)**:餐厅**提前把所有菜做好**,放在保温柜里。你来点餐,立刻就能吃(最快)。但菜单是固定的(静态内容),不能临时加菜。
|
||||
|
||||
- **首屏**:用户打开网站时,最先看到的那一屏内容。
|
||||
- **SEO**:Search Engine Optimization,搜索引擎优化。让页面更容易被搜索到。
|
||||
- **TTFB**:Time To First Byte,浏览器收到"第一口数据"的时间(越小越快)。
|
||||
- **TTI**:Time To Interactive,页面变得"可以点、可以用"的时间。
|
||||
## 5. 渲染策略:从 CSR 到 SSR/SSG
|
||||
|
||||
<RenderingStrategyDemo />
|
||||
|
||||
**关键点**:不同渲染策略适配不同业务场景。
|
||||
## 6. 第四阶段:工程化与构建工具(2015s-2020s)
|
||||
|
||||
### 6.1 为什么需要"工程化"?
|
||||
|
||||
前端项目越来越大,不能再靠"手动引入脚本"。
|
||||
|
||||
**工程化**就是用工具和规范,让开发更高效、代码更可靠、协作更顺畅。
|
||||
|
||||
::: tip 💡 工程化 = 从"手工作坊"到"现代化工厂"
|
||||
|
||||
想象一下你在家做饭 vs 开餐厅:
|
||||
|
||||
- **在家做饭**: 想吃什么就做什么,很自由
|
||||
- **开餐厅**: 需要标准化的菜谱、规范的操作流程、统一的原材料采购
|
||||
|
||||
前端开发也一样:
|
||||
|
||||
- **小项目**: 怎么写都行
|
||||
- **大项目**: 需要统一的代码规范、自动化工具、标准化流程
|
||||
:::
|
||||
|
||||
### 6.2 构建工具:Webpack → Vite
|
||||
|
||||
**Webpack** (传统):
|
||||
|
||||
- 工作方式:**先打包,后服务**
|
||||
- 启动时: 打包所有代码 → 启动服务器
|
||||
- 问题:**慢**。项目越大,启动越慢(可能要等 30 秒)
|
||||
|
||||
**Vite** (现代):
|
||||
|
||||
- 工作方式:**按需编译**
|
||||
- 启动时: 不打包,直接启动服务器
|
||||
- 浏览器请求哪个文件,就实时编译哪个
|
||||
- 优势:**快**。通常 1 秒内启动
|
||||
|
||||
| 对比项 | Webpack | Vite | 提升 |
|
||||
| -------- | ------- | ------ | ------------ |
|
||||
| 冷启动 | 30s+ | <1s | **快 30 倍** |
|
||||
| 热更新 | 3-5s | <100ms | **快 30 倍** |
|
||||
| 配置文件 | 几百行 | 几十行 | **大幅简化** |
|
||||
|
||||
::: tip 💡 为什么 Vite 这么快?
|
||||
|
||||
**Webpack** 就像**整备家当搬家**:先把所有东西打包,再出门。
|
||||
|
||||
**Vite** 就像**轻装旅行**:只带必需品,用到什么再买什么。
|
||||
|
||||
在开发环境,大多数时候你只需要修改几个文件,Vite 只编译这几个文件,当然快。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 8. 小结与学习建议
|
||||
## 7. 总结:演进的本质
|
||||
|
||||
前端技术的进化,本质上是在解决两个问题:
|
||||
前端技术的演进,本质上是在解决两个问题:
|
||||
|
||||
1. **效率**:从 手动操作 DOM -> 数据驱动 (MVVM)。
|
||||
2. **规模**:从 巨型面条代码 -> 组件化 + 工程化。
|
||||
### 7.1 效率:从手动到自动
|
||||
|
||||
**MVVM 是什么?**
|
||||
简单理解:**Model(数据)变了,View(界面)自动变**。
|
||||
| 时代 | 开发方式 | 效率 |
|
||||
| --------- | ------------------------ | ---------- |
|
||||
| **2000s** | 手写 HTML/CSS/JS | ⭐ |
|
||||
| **2010s** | jQuery + 手动 DOM 操作 | ⭐⭐ |
|
||||
| **2020s** | Vue/React + 数据驱动 | ⭐⭐⭐ |
|
||||
| **现在** | 组件化 + 工程化 + 自动化 | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
现在你可以把前端理解成三件事:
|
||||
### 7.2 规模:从个人到团队
|
||||
|
||||
1. **写页面**:HTML + CSS(结构与样式)
|
||||
2. **让页面动起来**:JavaScript(交互与状态)
|
||||
3. **把复杂项目做好**:组件化 + 工程化(协作与质量)
|
||||
| 时代 | 项目规模 | 协作方式 |
|
||||
| --------- | ---------- | ----------------------- |
|
||||
| **2000s** | 几个文件 | 单人就能维护 |
|
||||
| **2010s** | 几十个文件 | 小团队,容易冲突 |
|
||||
| **2020s** | 几百个文件 | 中团队,需要规范 |
|
||||
| **现在** | 几千个文件 | 大团队,需要完整工程体系 |
|
||||
|
||||
如果你刚开始学,建议按这个顺序:
|
||||
---
|
||||
|
||||
- 先把 HTML/CSS 写熟(布局、响应式)
|
||||
- 再学 JavaScript 的基础(变量、函数、事件)
|
||||
- 最后上手一个框架(Vue/React),理解"状态驱动 UI"
|
||||
## 8. 学习路线图
|
||||
|
||||
### 8.1 如果你是零基础
|
||||
|
||||
**第 1 步: HTML/CSS/JavaScript 基础**
|
||||
|
||||
- 理解网页的三大基石
|
||||
- 能写出简单的静态页面
|
||||
|
||||
**第 2 步: 学习一个框架(Vue 推荐)**
|
||||
|
||||
- 理解"数据驱动"的思想
|
||||
- 掌握组件化开发
|
||||
|
||||
**第 3 步: 实战项目**
|
||||
|
||||
- 做一个完整的单页应用
|
||||
- 熟悉路由、状态管理、API 调用
|
||||
|
||||
### 8.2 如果你有基础
|
||||
|
||||
**进阶方向**:
|
||||
|
||||
- **工程化**: 学习 Vite/Webpack,理解构建流程
|
||||
- **性能优化**: 学习懒加载、代码分割、缓存策略
|
||||
- **TypeScript**: 为代码加上类型,提升可靠性
|
||||
- **服务端渲染**: 学习 Nuxt/Next.js,解决 SEO 和首屏问题
|
||||
|
||||
---
|
||||
|
||||
## 9. 名词速查表 (Glossary)
|
||||
|
||||
| 名词 | 全称 | 解释 |
|
||||
| :----------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------- |
|
||||
| **HTML** | HyperText Markup Language | 超文本标记语言。网页的骨架,定义内容和结构。 |
|
||||
| **CSS** | Cascading Style Sheets | 层叠样式表。网页的皮肤,定义颜色、布局、动画。 |
|
||||
| **JS** | JavaScript | 网页的肌肉,负责交互和逻辑。 |
|
||||
| **DOM** | Document Object Model | 文档对象模型。浏览器内部表示页面结构的树形对象。 |
|
||||
| **jQuery** | - | 早期流行的 JS 库,简化了 DOM 操作。 |
|
||||
| **Vue/React** | - | 现代前端框架,采用数据驱动和组件化开发。 |
|
||||
| **State** | - | 状态。组件或应用的数据,状态变化驱动 UI 更新。 |
|
||||
| **组件** | Component | 可复用的 UI 单元,如按钮、卡片、导航栏。 |
|
||||
| **MPA** | Multi-Page Application | 多页应用。每次跳转都重新加载整个页面。 |
|
||||
| **SPA** | Single-Page Application | 单页应用。只加载一次,后续切换不刷新页面。 |
|
||||
| **路由** | Routing | 管理页面之间切换的规则和过程。 |
|
||||
| **CSR** | Client-Side Rendering | 客户端渲染。浏览器下载 JS 后执行生成页面。 |
|
||||
| **SSR** | Server-Side Rendering | 服务端渲染。服务器生成 HTML 后发给浏览器。 |
|
||||
| **SSG** | Static Site Generation | 静态站点生成。构建时预渲染页面为静态 HTML。 |
|
||||
| **Bundle** | - | 包。打包工具将多个文件合并后的产物。 |
|
||||
| **Tree Shaking** | - | 摇树优化。自动移除未使用的代码,减小包体积。 |
|
||||
| **H5** | HTML5 | 通常指手机网页或基于 HTML5 的移动页面。 |
|
||||
| **WebView** | - | 内嵌网页视图。App 中用于显示网页内容的组件。 |
|
||||
| **跨端** | Cross-Platform | 一套代码运行在多个平台(iOS、Android、Web 等)。 |
|
||||
| **原生** | Native | 使用平台官方语言和 API 开发的应用。 |
|
||||
| **MVVM** | Model-View-ViewModel | 一种架构模式,实现数据(Model)和视图(View)的自动同步。 |
|
||||
| **SEO** | Search Engine Optimization | 搜索引擎优化,提高网页在搜索结果中的排名。 |
|
||||
| **TTFB** | Time To First Byte | 首字节时间,从请求到收到第一个字节数据的耗时。 |
|
||||
| **TTI** | Time To Interactive | 可交互时间,页面变为完全可交互状态所需的时间。 |
|
||||
| 名词 | 英文 | 用人话解释 |
|
||||
| ---------------- | ----------------------- | --------------------------------------------- |
|
||||
| **DOM** | Document Object Model | 文档对象模型。用对象树表示页面,可被 JS 读写。 |
|
||||
| **jQuery** | - | 早期流行的 JS 库,简化了 DOM 操作。 |
|
||||
| **Vue/React** | - | 现代前端框架,采用数据驱动和组件化开发。 |
|
||||
| **组件** | Component | 可复用的 UI 单元,如按钮、卡片、导航栏。 |
|
||||
| **MPA** | Multi-Page Application | 多页应用。每次跳转都重新加载整个页面。 |
|
||||
| **SPA** | Single-Page Application | 单页应用。只加载一次,后续切换不刷新页面。 |
|
||||
| **路由** | Routing | 管理页面之间切换的规则和过程。 |
|
||||
| **SSR** | Server-Side Rendering | 服务端渲染。服务器生成 HTML 后发给浏览器。 |
|
||||
| **SSG** | Static Site Generation | 静态站点生成。构建时预渲染页面为静态 HTML。 |
|
||||
| **Webpack** | - | 传统打包工具,先打包后服务。 |
|
||||
| **Vite** | - | 现代构建工具,按需编译,速度极快。 |
|
||||
| **响应式** | Responsive Design | 页面自动适配不同屏幕尺寸的设计。 |
|
||||
| **媒体查询** | Media Query | CSS 的条件判断,根据屏幕宽度应用不同样式。 |
|
||||
| **命令式** | Imperative | 告诉程序"怎么做"。 |
|
||||
| **声明式** | Declarative | 告诉程序"要什么"。 |
|
||||
| **数据驱动** | Data-Driven | 只修改数据,界面自动更新。 |
|
||||
| **Tree Shaking** | - | 摇树优化。自动移除未使用的代码,减小包体积。 |
|
||||
| **代码分割** | Code Splitting | 把代码分成多个小块,按需加载。 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
前端技术的演进,本质上是**从"手工"到"工业化"的进化**:
|
||||
|
||||
- **2000s**: 手工时代,简单直接
|
||||
- **2010s**: 工具化时代,开始有框架
|
||||
- **2020s**: 工业化时代,组件化 + 工程化
|
||||
- **现在**: 智能化时代,AI 辅助开发
|
||||
|
||||
理解这个演进,你就能:
|
||||
|
||||
- 知道为什么要有 Vue/React
|
||||
- 理解"数据驱动"的价值
|
||||
- 明白工程化的必要性
|
||||
- 快速上手新技术
|
||||
|
||||
**下一步建议**:
|
||||
|
||||
- 如果你想快速上手,学习 **Vue 3** (推荐) 或 **React**
|
||||
- 如果你想深入理解,学习 **Vite** 构建流程
|
||||
- 如果你想提升代码质量,学习 **TypeScript**
|
||||
|
||||
祝你学习愉快!
|
||||
|
||||
+429
-1307
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,24 @@
|
||||
# Git 版本控制:时间的后悔药
|
||||
# Git 版本控制:代码世界的时光机
|
||||
|
||||
> 💡 **一句话解释**:Git 就是代码世界的**游戏存档管理器**。它能让你随时“读档”重来,也能让你和队友在各自的“平行宇宙”里互不干扰地开发。
|
||||
::: tip 🎯 核心问题
|
||||
**写代码时最怕什么?** 写错了想回退、改崩了想重来、多人同时改同一个文件...这些头疼的事,Git 都能帮你搞定!它就像是代码世界的"时光机",让你随时回到过去,又能和队友在各自的"平行宇宙"里安全开发。
|
||||
:::
|
||||
|
||||
> ✅ **安全说明**:本页所有交互组件都是“模拟器”,不会对你电脑上的真实 Git 仓库执行任何操作;但真实项目里建议严格按步骤来,不要依赖“自动下一步”。
|
||||
---
|
||||
|
||||
## 0. 最常用的 5 个场景(直接照抄)
|
||||
## 0. 最常用的 5 个场景(直接照抄)
|
||||
|
||||
如果你只想“立刻能用”,先把这块过一遍:每个场景都是现实工作中最常见的 Git 流程。
|
||||
如果你只想"立刻能用",先把这块过一遍:每个场景都是现实工作中最常见的 Git 流程。
|
||||
|
||||
<GitScenariosDemo />
|
||||
|
||||
## 1. 为什么我们需要它?
|
||||
---
|
||||
|
||||
你是否经历过这种绝望?
|
||||
## 1. 为什么要学 Git?三大痛点
|
||||
|
||||
### 1.1 痛点一:版本混乱
|
||||
|
||||
你是否经历过这种绝望?
|
||||
|
||||
```text
|
||||
论文_初稿.doc
|
||||
@@ -22,80 +28,322 @@
|
||||
论文_绝对是最后一次修改版.doc
|
||||
```
|
||||
|
||||
**Git 完美解决了三个问题**:
|
||||
**Git 的解决方案**:不需要复制副本,一个文件夹搞定所有历史版本。想回到哪次修改,一键恢复。
|
||||
|
||||
1. **版本混乱**:不需要复制副本,一个文件夹搞定所有历史版本。
|
||||
2. **无法后悔**:删错了代码?一秒钟找回三天前的状态。
|
||||
3. **协作冲突**:你改了 A 文件,我改了 B 文件,Git 帮我们自动合并。
|
||||
### 1.2 痛点二:无法后悔
|
||||
|
||||
::: tip 💡 这个场景你一定遇到过
|
||||
写代码写了 3 小时,突然发现之前的思路更好,但已经改不回去了...或者删错了一段代码,想找回原来的版本。
|
||||
|
||||
有了 Git,这种情况永远不会发生。每次重要节点都能"存档",出问题随时"读档"重来。
|
||||
:::
|
||||
|
||||
### 1.3 痛点三:协作冲突
|
||||
|
||||
你和队友同时改同一个文件:
|
||||
|
||||
- 你改了 A 文件的第 10 行
|
||||
- 队友改了 A 文件的第 15 行
|
||||
- 怎么合并?谁覆盖谁?
|
||||
|
||||
**Git 的解决方案**:智能合并,自动处理大部分冲突。只有当你们真的改了同一行代码时,才需要手动决定用谁的。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心概念:三个箱子
|
||||
## 2. 核心概念:三区模型
|
||||
|
||||
Git 的设计哲学其实很像**寄快递**。
|
||||
|
||||
<GitThreeAreasDemo />
|
||||
|
||||
- **工作区 (Working Dir)**:你的**书桌**。你正在这里写代码,想怎么乱改都行。
|
||||
- **暂存区 (Staging Area)**:**快递盒**。你把写好的文件放进去(`git add`),准备打包。
|
||||
- **仓库 (Repository)**:**快递柜**。一旦你封箱寄出(`git commit`),这个版本就被永久记录下来了。
|
||||
### 2.1 三个区域是什么?
|
||||
|
||||
> 🔑 **关键点**:只有提交(Commit)到仓库的内容,才是安全的。工作区里没提交的内容,丢了就真丢了。
|
||||
::: tip 📦 用快递理解 Git
|
||||
想象你在寄快递:
|
||||
|
||||
- **工作区(Working Dir)** = 你的**书桌**。你在这里整理要寄的东西,想怎么乱改都行。
|
||||
- **暂存区(Staging Area)** = **快递盒**。你把要寄的文件放进去(`git add`),准备打包。
|
||||
- **仓库(Repository)** = **快递柜**。一旦你封箱寄出(`git commit`),这个版本就被永久记录下来了。
|
||||
:::
|
||||
|
||||
| 区域 | 作用 | 对应命令 | 状态 |
|
||||
| ---------- | ------------------ | --------------------- | ------------- |
|
||||
| **工作区** | 你当前正在改的代码 | `git status` 查看修改 | 红色 = 未暂存 |
|
||||
| **暂存区** | 准备提交的文件 | `git add` 添加 | 绿色 = 已暂存 |
|
||||
| **仓库** | 永久保存的历史版本 | `git commit` 提交 | 只读,不能改 |
|
||||
|
||||
::: tip 💡 关键理解
|
||||
只有提交到**仓库**的内容才是安全的。工作区里没提交的内容,丢了就真丢了。所以经常`git commit`是好习惯!
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 基础工作流:存档三步走
|
||||
## 3. 基础工作流:存档三步走
|
||||
|
||||
日常开发中,你 90% 的时间都在重复这三个动作。
|
||||
日常开发中,你 90% 的时间都在重复这三个动作。
|
||||
|
||||
<GitWorkflowDemo />
|
||||
|
||||
1. **修改代码**:在工作区写写画画。
|
||||
2. **`git add`**:挑选你要保存的文件,放入暂存区。
|
||||
3. **`git commit`**:给这次修改起个名字(比如“修复了登录 Bug”),永久存档。
|
||||
### 3.1 第一步:修改代码(工作区)
|
||||
|
||||
在工作区写写画画,想怎么改就怎么改。这时候修改只在你本地,还没记录。
|
||||
|
||||
### 3.2 第二步:挑选文件(git add → 暂存区)
|
||||
|
||||
::: tip 🤔 为什么要先 add 再 commit?
|
||||
你可能问:为什么不能直接 commit 所有修改?
|
||||
|
||||
**答案**:因为有时候你不想一次性提交所有改动。
|
||||
|
||||
- 今天改了 5 个文件,但只想提交其中 3 个(完成了一个功能)
|
||||
- 另外 2 个文件还在调试中,不想现在提交
|
||||
|
||||
`git add` 让你有选择权:决定这次提交包含哪些文件。
|
||||
:::
|
||||
|
||||
**常用命令**:
|
||||
|
||||
```bash
|
||||
# 添加单个文件
|
||||
git add index.html
|
||||
|
||||
# 添加所有修改
|
||||
git add .
|
||||
|
||||
# 查看哪些文件被暂存了
|
||||
git status
|
||||
```
|
||||
|
||||
### 3.3 第三步:封箱提交(git commit → 仓库)
|
||||
|
||||
给这次修改起个名字(比如"修复了登录 Bug"),永久存档。
|
||||
|
||||
**重要:commit message 要写清楚!**
|
||||
|
||||
```bash
|
||||
# ❌ 不好的写法
|
||||
git commit -m "update"
|
||||
|
||||
# ✅ 好的写法
|
||||
git commit -m "feat: 添加用户登录功能"
|
||||
git commit -m "fix: 修复首页在 iOS 的显示问题"
|
||||
git commit -m "docs: 更新 README 的部署说明"
|
||||
```
|
||||
|
||||
::: tip 💡 commit message 规范
|
||||
推荐用**类型+描述**的格式:
|
||||
|
||||
- `feat:` 新功能
|
||||
- `fix:` 修复 bug
|
||||
- `docs:` 文档更新
|
||||
- `style:` 代码格式(不影响功能)
|
||||
- `refactor:` 重构(不改变功能)
|
||||
- `test:` 测试相关
|
||||
- `chore:` 构建/工具相关
|
||||
|
||||
这样以后翻历史记录,一眼就知道每次提交做了什么。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 4. 平行宇宙:分支 (Branch)
|
||||
## 4. 平行宇宙:分支(Branch)的魔法
|
||||
|
||||
这是 Git 最强大的功能。想象你在玩游戏,前面有个大 Boss(上线新功能),你怕打不过导致游戏结束(系统崩溃)。
|
||||
这是 Git 最强大的功能!
|
||||
|
||||
这时候,你可以开一个**分支 (Branch)**,相当于**复制了一个平行世界**。
|
||||
::: tip 🌌 用游戏理解分支
|
||||
想象你在玩游戏,前面有个大 Boss(上线新功能),你怕打不过导致游戏结束(系统崩溃)。
|
||||
|
||||
这时候,你可以开一个**分支(Branch)**,相当于**复制了一个平行世界**:
|
||||
|
||||
- 在**平行世界**(新分支)里打 Boss,输了也不怕,因为主世界(主分支)没影响
|
||||
- 打赢了就把成果"合并"回主世界
|
||||
- 多个队友可以在各自的平行世界开发,互不干扰
|
||||
:::
|
||||
|
||||
<GitBranchMergeDemo />
|
||||
|
||||
- **主分支 (Main/Master)**:稳定的线上版本,只有测试通过的代码才能进来。
|
||||
- **开发分支 (Feature)**:你的试验田。你在这里炸了地球也没关系,不会影响主分支。
|
||||
- **合并 (Merge)**:你在试验田里测试成功了,就把改动“合并”回主分支。
|
||||
### 4.1 主分支 vs 开发分支
|
||||
|
||||
| 分支类型 | 作用 | 特点 |
|
||||
| ------------------- | -------------- | ------------------------------------ |
|
||||
| **main/master** | 稳定的线上版本 | 只有测试通过的代码才能进来 |
|
||||
| **dev/feature-xxx** | 你的试验田 | 这里炸了地球也没关系,不影响主分支 |
|
||||
| **hotfix-xxx** | 紧急修复 | 生产出 bug 时,从 main 开分支快速修复 |
|
||||
|
||||
### 4.2 分支操作流程
|
||||
|
||||
**创建分支并切换**:
|
||||
|
||||
```bash
|
||||
# 创建新分支
|
||||
git branch feature-login
|
||||
|
||||
# 切换到新分支
|
||||
git checkout feature-login
|
||||
|
||||
# 或者一步到位:创建并切换
|
||||
git checkout -b feature-login
|
||||
```
|
||||
|
||||
**在分支上开发**:
|
||||
|
||||
```bash
|
||||
# 在 feature-login 分支上改代码...
|
||||
git add .
|
||||
git commit -m "feat: 添加登录表单"
|
||||
```
|
||||
|
||||
**合并回主分支**:
|
||||
|
||||
```bash
|
||||
# 切回主分支
|
||||
git checkout main
|
||||
|
||||
# 合并 feature-login
|
||||
git merge feature-login
|
||||
|
||||
# 删除已合并的分支(可选)
|
||||
git branch -d feature-login
|
||||
```
|
||||
|
||||
::: tip 💡 什么时候用分支?
|
||||
**个人开发**:
|
||||
|
||||
- 要尝试新想法,不确定会不会搞崩现有代码 → 开分支
|
||||
- 修一个复杂 bug,需要多次实验 → 开分支
|
||||
|
||||
**团队开发**:
|
||||
|
||||
- 每个功能一个分支,互不干扰
|
||||
- 开发完提 Pull Request,队友 review 后再合并
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 常用命令速查
|
||||
## 5. 常用命令速查表
|
||||
|
||||
| 命令 | 作用 | 人话解释 |
|
||||
| :-------------------- | :------------- | :------------------------------------- |
|
||||
| `git init` | 初始化 | "我要在这里建个新仓库" |
|
||||
| `git status` | 查看状态 | "现在书桌上乱不乱?有没有东西没装箱?" |
|
||||
| `git add .` | 添加所有 | "把桌上所有文件都扔进快递盒" |
|
||||
| `git commit -m "..."` | 提交 | "封箱!贴上标签,写上这次改了啥" |
|
||||
| `git log` | 查看历史 | "翻翻以前的日记" |
|
||||
| `git checkout -b dev` | 以此创建新分支 | "我要去平行宇宙 dev 探险了" |
|
||||
| `git checkout main` | 切换分支 | "回地球(主分支)看看" |
|
||||
| `git merge dev` | 合并分支 | "把平行宇宙的成果带回地球" |
|
||||
| 命令 | 作用 | 人话解释 | 使用频率 |
|
||||
| ----------------------- | ---------- | ------------------------------ | --------------------- |
|
||||
| `git init` | 初始化 | "在这里建个新仓库" | 项目开始时用一次 |
|
||||
| `git status` | 查看状态 | "现在乱不乱?有没有东西没提交?" | ⭐⭐⭐⭐⭐ 极高频 |
|
||||
| `git add .` | 添加所有 | "把桌上所有文件都扔进快递盒" | ⭐⭐⭐⭐⭐ 每次提交前 |
|
||||
| `git add file.txt` | 添加单个 | "只要这个文件" | ⭐⭐⭐⭐ 选择性添加 |
|
||||
| `git commit -m "..."` | 提交 | "封箱!贴上标签,写上这次改了啥" | ⭐⭐⭐⭐⭐ 完成功能时 |
|
||||
| `git log` | 查看历史 | "翻翻以前的日记" | ⭐⭐⭐ 回顾历史 |
|
||||
| `git checkout -b dev` | 创建新分支 | "我要去平行宇宙 dev 探险了" | ⭐⭐⭐⭐ 开新功能 |
|
||||
| `git checkout main` | 切换分支 | "回地球(主分支)看看" | ⭐⭐⭐⭐ 切换任务 |
|
||||
| `git merge dev` | 合并分支 | "把平行宇宙的成果带回地球" | ⭐⭐⭐ 完成功能 |
|
||||
| `git branch` | 查看分支 | "现在有哪些平行世界?" | ⭐⭐⭐ 查看状态 |
|
||||
| `git branch -d feature` | 删除分支 | "这个平行世界不需要了,删掉" | ⭐⭐ 合并后清理 |
|
||||
| `git push` | 推送 | "把本地存档上传到云端" | ⭐⭐⭐⭐⭐ 团队协作 |
|
||||
| `git pull` | 拉取 | "把云端最新存档下载到本地" | ⭐⭐⭐⭐⭐ 团队协作 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 进阶:解决冲突与远程协作
|
||||
## 6. 进阶:解决冲突与远程协作
|
||||
|
||||
当你和队友同时修改了同一个文件的同一行代码,Git 就会懵逼:“我该听谁的?” 这就是**冲突 (Conflict)**。
|
||||
### 6.1 冲突(Conflict)是什么?
|
||||
|
||||
当你和队友**同时修改了同一个文件的同一行代码**,Git 就会懵:"我该听谁的?"这就是**冲突(Conflict)**。
|
||||
|
||||
<GitConflictDemo />
|
||||
|
||||
此时你需要手动打开文件,保留需要的代码,删除 Git 自动生成的 `<<<<<<<` 标记,然后重新提交。
|
||||
### 6.2 怎么解决冲突?
|
||||
|
||||
至于**远程仓库 (Remote)**(比如 GitHub/GitLab),它就是云端的备份中心。
|
||||
**Step 1**:打开冲突文件,会看到这样的标记:
|
||||
|
||||
- `git push`:把本地存档上传到云端。
|
||||
- `git pull`:把云端最新的存档拉取到本地。
|
||||
```text
|
||||
<<<<<<< HEAD
|
||||
你的代码
|
||||
=======
|
||||
队友的代码
|
||||
>>>>>>> feature-branch
|
||||
```
|
||||
|
||||
**Step 2**:手动选择要保留的代码,或合并两者:
|
||||
|
||||
```text
|
||||
# 保留你的代码 → 删除队友的部分和标记
|
||||
# 保留队友的 → 删除你的部分和标记
|
||||
# 合并两者 → 综合两边的代码
|
||||
```
|
||||
|
||||
**Step 3**:删除所有标记,保存文件
|
||||
|
||||
**Step 4**:重新提交
|
||||
|
||||
```bash
|
||||
git add 解决冲突的文件
|
||||
git commit # Git 会自动生成合并提交
|
||||
```
|
||||
|
||||
::: tip 💡 避免冲突的最佳实践
|
||||
|
||||
- **频繁沟通**:队友改同一个文件前,先打个招呼
|
||||
- **小步提交**:不要攒着大量代码最后才提交,增加冲突概率
|
||||
- **分支隔离**:不同功能用不同分支,减少直接冲突
|
||||
- **用 Pull Request**:合并前让队友 review,提前发现问题
|
||||
:::
|
||||
|
||||
### 6.3 远程仓库(Remote)
|
||||
|
||||
**远程仓库**(比如 GitHub/GitLab)就是**云端的备份中心**。
|
||||
|
||||
<GitRemoteDemo />
|
||||
|
||||
**核心操作**:
|
||||
|
||||
| 操作 | 命令 | 作用 |
|
||||
| ------------ | ---------------------------------------------- | ------------------------ |
|
||||
| **关联远程** | `git remote add origin https://github.com/...` | 第一次连接云端 |
|
||||
| **推送** | `git push -u origin main` | 把本地存档上传 |
|
||||
| **拉取** | `git pull` | 把云端最新存档下载并合并 |
|
||||
| **克隆** | `git clone https://github.com/...` | 复制整个仓库到本地 |
|
||||
|
||||
::: tip 💡 push 和 pull 的区别
|
||||
|
||||
- **push**:你的本地代码 → 云端(你改了东西,要同步给队友)
|
||||
- **pull**:云端代码 → 你的本地(队友改了东西,你要同步下来)
|
||||
|
||||
**最佳实践**:每天开始工作前先`git pull`,下班前`git push`,这样减少冲突。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结:Git 的核心思想
|
||||
|
||||
Git 不是简单的"版本备份",而是一个**完整的代码协作系统**:
|
||||
|
||||
| 特性 | 解决的问题 | 生活类比 |
|
||||
| ------------ | ------------------------------- | --------------------- |
|
||||
| **版本管理** | 代码改错了怎么办? | 时光机,随时回退 |
|
||||
| **分支** | 多人同时改同一个文件怎么办? | 平行宇宙,互不干扰 |
|
||||
| **暂存区** | 这次提交不想包含所有修改怎么办? | 快递盒,挑拣要寄的东西 |
|
||||
| **远程协作** | 怎么和队友共享代码? | 云备份,随时随地同步 |
|
||||
| **冲突处理** | 真的改到同一行了怎么办? | 智能合并 + 手动协调 |
|
||||
|
||||
**学习建议**:
|
||||
|
||||
1. **先用起来**:不要等"完全理解"再用,一边用一边理解
|
||||
2. **从简单开始**:个人项目先掌握`add/commit/push/pull`,团队项目再学分支
|
||||
3. **看 Git 图形化工具**:SourceTree、GitHub Desktop 等,可视化帮助理解
|
||||
4. **遇到问题不要慌**:Git 的设计就是为了让你能安全地尝试,大不了`git reset`
|
||||
|
||||
---
|
||||
|
||||
## 附录:名词速查表
|
||||
|
||||
| 名词 | 英文 | 用人话解释 |
|
||||
| -------- | ---------- | ------------------------------------- |
|
||||
| **仓库** | Repository | 存放所有版本历史的数据库 |
|
||||
| **提交** | Commit | 一次完整的版本记录,像存档点 |
|
||||
| **分支** | Branch | 独立的开发线,像平行宇宙 |
|
||||
| **合并** | Merge | 把一个分支的改动整合到另一个分支 |
|
||||
| **冲突** | Conflict | 同一行代码被修改多次,Git 不知道选哪个 |
|
||||
| **暂存** | Stage | 把修改加入"准备提交"的列表 |
|
||||
| **远程** | Remote | 云端的仓库副本(GitHub/GitLab) |
|
||||
| **克隆** | Clone | 复制整个远程仓库到本地 |
|
||||
| **推送** | Push | 本地 → 远程,上传代码 |
|
||||
| **拉取** | Pull | 远程 → 本地,下载代码 |
|
||||
| **检出** | Checkout | 切换到某个分支或版本 |
|
||||
| **HEAD** | - | 当前所在的分支/版本的指针 |
|
||||
|
||||
@@ -1,497 +1,401 @@
|
||||
# 负载均衡与多实例部署示意图
|
||||
# 负载均衡与多实例部署
|
||||
|
||||
> 💡 **学习指南**:本文将带你理解现代分布式系统中,如何通过负载均衡技术把流量"聪明地"分配到多个服务器实例上。我们会从四层/七层负载均衡讲起,逐步深入到健康检查、会话保持、自动扩缩容,最后到异地多活部署。建议你先阅读 [后端架构演进](./backend-evolution.md) 了解基本概念。
|
||||
|
||||
在开始之前,建议你先补充两块"基础砖":
|
||||
|
||||
- **网络基础**:可以先阅读 [网络基础概念](./network-basics.md) 了解 TCP/IP、HTTP 等协议。
|
||||
- **容器与编排**:如果你还不熟悉 Docker 和 Kubernetes,可以先看 [容器化部署](./container-deployment.md)。
|
||||
::: tip 🎯 核心问题
|
||||
**当单台服务器扛不住时,如何把流量"聪明地"分配到多个服务器实例?** 负载均衡是现代分布式系统的"分发员"。本文通过真实案例(奶茶店收银、快递分拣、交通指挥)深入理解负载均衡的设计哲学和工程实践。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 0. 引言:当一台服务器扛不住的时候
|
||||
## 1. 为什么要"负载均衡"?
|
||||
|
||||
<ClientOnly>
|
||||
<LoadBalancerTypesDemo />
|
||||
</ClientOnly>
|
||||
### 1.1 从一个真实案例说起:某网站的架构演进
|
||||
|
||||
想象你开了一家网红奶茶店。刚开业时,店里只有一个收银台,顾客排队点单,一切井然有序。但随着口碑传播,排队的人越来越多,一个收银台根本应付不过来——顾客等得不耐烦,抱怨连连,甚至有人转身离开。
|
||||
某创业公司在用户量快速增长时遇到了严重的性能问题:
|
||||
|
||||
**这时候你有两个选择:**
|
||||
**场景还原:**
|
||||
|
||||
1. **换一台更快的收银机(垂直扩展)**:但再快的机器也有极限,而且贵得离谱。
|
||||
2. **多开几个收银台,让顾客分流(水平扩展)**:每个收银台处理一部分顾客,整体效率大幅提升。
|
||||
```
|
||||
阶段一:单台服务器
|
||||
用户 → 服务器(1核2G)
|
||||
↓
|
||||
日活1000 → 活跃时间:1000人同时访问
|
||||
↓
|
||||
问题:CPU 100%,响应慢,经常宕机
|
||||
```
|
||||
|
||||
**负载均衡(Load Balancing)就是第二个方案的"总指挥"。** 它站在所有收银台前面,帮顾客决定:"你去1号台,你去2号台..." 确保每个收银台的 workload 相对平均,不让任何一个台累垮。
|
||||
::: warning ⚠️ 单台服务器的致命问题
|
||||
|
||||
<ClientOnly>
|
||||
<IntroProblemReasonSolution />
|
||||
</ClientOnly>
|
||||
- **性能瓶颈**: CPU 100%,响应时间> 5秒
|
||||
- **单点故障**: 服务器挂了,整个网站不可用
|
||||
- **扩展困难**: 只能垂直升级(加CPU、内存),贵且有限
|
||||
:::
|
||||
|
||||
**改进后的架构(引入负载均衡):**
|
||||
|
||||
```
|
||||
阶段二:多台服务器 + 负载均衡
|
||||
用户 → 负载均衡器(Nginx)
|
||||
↓
|
||||
├→ 服务器1 (1核2G)
|
||||
├→ 服务器2 (1核2G)
|
||||
└→ 服务器3 (1核2G)
|
||||
```
|
||||
|
||||
::: tip ✨ 改进后的效果
|
||||
|
||||
- **性能提升**: 3台服务器并行处理,响应时间< 1秒
|
||||
- **高可用**: 1台服务器挂了,其他服务器继续服务
|
||||
- **水平扩展**: 需要更多性能?加服务器就行
|
||||
:::
|
||||
|
||||
### 1.2 负载均衡的生活化比喻
|
||||
|
||||
**奶茶店收银台**
|
||||
|
||||
想象你开了一家网红奶茶店:
|
||||
|
||||
- **1个收银台**: 顾客排队,后面的人等不及,差评
|
||||
- **3个收银台**: 员工分配顾客到不同收银台,效率提升3倍
|
||||
|
||||
**负载均衡就是"收银台分配员"**:
|
||||
|
||||
- **用户**(顾客) → 请求服务
|
||||
- **负载均衡器**(分配员) → 把请求分配到不同服务器
|
||||
- **服务器**(收银台) → 处理请求
|
||||
|
||||
<LoadBalancerTypesDemo />
|
||||
|
||||
---
|
||||
|
||||
## 1. 负载均衡器的"分层": L4 vs L7
|
||||
## 2. 什么是负载均衡?
|
||||
|
||||
就像快递分拣有"只看邮编"和"检查包裹内容"两种策略,负载均衡也分不同"层次":
|
||||
### 2.1 四层负载均衡(L4):只看门牌号
|
||||
|
||||
### 1.1 四层负载均衡(L4):"只看门牌号"
|
||||
**工作在传输层(TCP/UDP)**,就像快递小哥只看你家的**门牌号(IP地址+端口号)**,不关心你家是做什么。
|
||||
|
||||
**工作在传输层(TCP/UDP)**,就像快递小哥只看你家的**门牌号(IP地址+端口号)**,不关心你家是做什么的。
|
||||
**特点:**
|
||||
|
||||
**特点:**
|
||||
- **速度超快**:只做简单的地址转发,不解析数据包内容
|
||||
- **适用场景**:数据库连接、Redis缓存、长连接游戏服务器
|
||||
- **代表产品**:LVS(Linux Virtual Server)、AWS NLB、Azure Load Balancer
|
||||
- **速度超快**: 只做简单的地址转发,不解析数据包内容
|
||||
- **适用场景**: 数据库连接、Redis缓存、长连接游戏服务器
|
||||
- **代表产品**: LVS(Linux Virtual Server)、AWS NLB、Azure Load Balancer
|
||||
|
||||
**真实案例:电商大促的流量入口**
|
||||
::: details 工作原理
|
||||
|
||||
某头部电商在双11期间,使用L4负载均衡处理每秒数百万的TCP连接。由于L4不解析HTTP内容,处理速度极快,确保用户在秒杀开始瞬间就能建立连接,不因为负载均衡本身的处理延迟而错过抢购。
|
||||
```
|
||||
客户端请求 → L4负载均衡器 → 后端服务器
|
||||
↓
|
||||
只看IP + Port
|
||||
↓
|
||||
快速转发(不解包内容)
|
||||
```
|
||||
|
||||
### 1.2 七层负载均衡(L7):"检查包裹内容"
|
||||
:::
|
||||
|
||||
**工作在应用层(HTTP/HTTPS)**,就像快递小哥不仅看门牌号,还会**打开包裹检查内容**,根据内容决定怎么送。
|
||||
### 2.2 七层负载均衡(L7):检查包裹内容
|
||||
|
||||
**特点:**
|
||||
- **智能路由**:可以根据URL路径、HTTP头、Cookie等做精细化路由
|
||||
- **高级功能**:SSL卸载、内容缓存、压缩、安全WAF
|
||||
- **适用场景**:Web应用、API网关、微服务架构
|
||||
- **代表产品**:Nginx、HAProxy、AWS ALB、Envoy
|
||||
**工作在应用层(HTTP/HTTPS)**,就像快递小哥不仅看门牌号,还会**打开包裹检查内容**,根据内容决定怎么送。
|
||||
|
||||
**真实案例:SaaS平台的多租户路由**
|
||||
**特点:**
|
||||
|
||||
某SaaS公司使用Nginx作为L7负载均衡,根据HTTP Header中的`X-Tenant-ID`将不同租户的数据请求路由到对应的数据库集群。tenant-a 的请求去 db-cluster-1,tenant-b 的请求去 db-cluster-2,实现了完全的数据隔离。
|
||||
- **智能路由**: 可以根据URL路径、HTTP头、Cookie等做精细化路由
|
||||
- **高级功能**: SSL卸载、内容缓存、压缩、安全WAF
|
||||
- **适用场景**: Web应用、API网关、微服务架构
|
||||
- **代表产品**: Nginx、HAProxy、AWS ALB、Envoy
|
||||
|
||||
### 1.3 L4 vs L7 对比一览
|
||||
::: details 工作原理
|
||||
|
||||
| 维度 | 四层负载均衡 (L4) | 七层负载均衡 (L7) |
|
||||
|:---|:---|:---|
|
||||
| **工作层级** | 传输层 (TCP/UDP) | 应用层 (HTTP/HTTPS) |
|
||||
| **决策依据** | IP地址 + 端口号 | URL、Header、Cookie、Body |
|
||||
| **处理速度** | 极快(内核态处理) | 较快(用户态解析) |
|
||||
| **功能丰富度** | 基础转发 | SSL卸载、缓存、压缩、WAF |
|
||||
| **典型场景** | 数据库、游戏、长连接 | Web应用、API网关、微服务 |
|
||||
| **代表产品** | LVS、AWS NLB | Nginx、HAProxy、AWS ALB |
|
||||
```
|
||||
客户端请求 → L7负载均衡器 → 解析HTTP内容
|
||||
↓
|
||||
检查URL、Header、Cookie
|
||||
↓
|
||||
智能路由到特定服务器
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 2.3 L4 vs L7 对比一览
|
||||
|
||||
| 维度 | 四层负载均衡(L4) | 七层负载均衡(L7) |
|
||||
| :------------- | :------------------- | :------------------------ |
|
||||
| **工作层级** | 传输层(TCP/UDP) | 应用层(HTTP/HTTPS) |
|
||||
| **决策依据** | IP地址 + 端口号 | URL、Header、Cookie、Body |
|
||||
| **处理速度** | 极快(内核态处理) | 较快(用户态解析) |
|
||||
| **功能丰富度** | 基础转发 | SSL卸载、缓存、压缩、WAF |
|
||||
| **典型场景** | 数据库、游戏、长连接 | Web应用、API网关、微服务 |
|
||||
| **代表产品** | LVS、AWS NLB | Nginx、HAProxy、AWS ALB |
|
||||
|
||||
---
|
||||
|
||||
## 2. 健康检查:别让"坏掉"的服务器继续接客
|
||||
## 3. 核心问题一:如何避免"坏掉"的服务器继续接客?
|
||||
|
||||
想象一下,你的某个收银台突然坏了,但顾客不知道,还在源源不断地排过去。结果队伍越来越长,顾客怨声载道。
|
||||
### 3.1 健康检查:别让"生病"的服务器拖累系统
|
||||
|
||||
**健康检查(Health Check)就是防止这种情况发生的"哨兵"。** 它定期"体检"每台服务器,发现"生病"的立即从队列中移除,等"康复"了再请回来。
|
||||
想象一下,你的某个收银台突然坏了,但分配员不知道,还在源源不断地把顾客分过去。结果队伍越来越长,顾客怨声载道。
|
||||
|
||||
<ClientOnly>
|
||||
<HealthCheckDemo />
|
||||
</ClientOnly>
|
||||
**健康检查(Health Check)就是防止这种情况发生的"哨兵"**。它定期"体检"每台服务器,发现"生病"的立即从队列中移除,等"康复"了再请回来。
|
||||
|
||||
### 2.1 主动健康检查 vs 被动健康检查
|
||||
<!-- <HealthCheckDemo /> -->
|
||||
|
||||
**主动健康检查(Active Health Check)**:负载均衡器主动"敲门"问服务器"你还在吗?"
|
||||
- 定期发送探测请求(如 HTTP /health、TCP ping)
|
||||
### 3.2 主动健康检查 vs 被动健康检查
|
||||
|
||||
**主动健康检查(Active Health Check)**: 负载均衡器主动"敲门"问服务器"你还在吗?"
|
||||
|
||||
- 定期发送探测请求(如 HTTP /health、TCP ping)
|
||||
- 响应超时或返回错误码则认为不健康
|
||||
- **优点**:检测结果准确可靠
|
||||
- **缺点**:产生额外的探测流量
|
||||
- **优点**: 检测结果准确可靠
|
||||
- **缺点**: 产生额外的探测流量
|
||||
|
||||
**被动健康检查(Passive Health Check)**: 负载均衡器"观察"真实业务流量的响应情况
|
||||
|
||||
**被动健康检查(Passive Health Check)**:负载均衡器"观察"真实业务流量的响应情况
|
||||
- 统计实际请求的响应时间、错误率
|
||||
- 连续多次失败则认为不健康
|
||||
- **优点**:不产生额外流量
|
||||
- **缺点**:需要足够的流量样本才能判定
|
||||
|
||||
### 2.2 阈值设定:别让"小病"也触发告警
|
||||
|
||||
健康检查的阈值就像体温计:37度是正常,38度是低烧,40度是高烧。
|
||||
|
||||
**常见的阈值配置:**
|
||||
- **优点**: 不产生额外流量
|
||||
- **缺点**: 需要足够的流量样本才能判定
|
||||
|
||||
::: details 阈值设定表
|
||||
| 指标 | 健康阈值 | 不健康阈值 | 说明 |
|
||||
|:---|:---|:---|:---|
|
||||
| **HTTP 状态码** | 200-399 | 400+ 或超时 | 4xx/5xx 都认为失败 |
|
||||
| **TCP 连接** | 成功建立 | 连接超时 | 检查端口是否可达 |
|
||||
| **响应时间** | < 500ms | > 2000ms | 超时时间通常设为2-5s |
|
||||
| **HTTP状态码** | 200-399 | 400+或超时 | 4xx/5xx都认为失败 |
|
||||
| **TCP连接** | 成功建立 | 连接超时 | 检查端口是否可达 |
|
||||
| **响应时间** | < 500ms | > 2000ms | 超时时间通常设为2-5秒 |
|
||||
| **连续失败次数** | - | 3次 | 避免单次抖动误判 |
|
||||
| **检查间隔** | - | 5s | 太频繁会增加负载 |
|
||||
|
||||
**踩坑经验:阈值设置太"敏感"的教训**
|
||||
::: tip 💡 踸见坑:阈值设置太"敏感"
|
||||
某团队将健康检查的响应时间阈值设为100ms,而他们的应用平均响应时间在80-120ms之间波动。结果是服务器频繁被标记为"不健康",导致流量在健康和不健康之间反复横跳,系统整体可用率反而下降。
|
||||
|
||||
某团队将健康检查的响应时间阈值设为 100ms,而他们的应用平均响应时间在 80-120ms 之间波动。结果是服务器频繁被标记为"不健康",导致流量在健康和不健康之间反复横跳,系统整体可用率反而下降。
|
||||
|
||||
**正确的做法:** 阈值应该设置为**P99 响应时间的 2-3 倍**,给正常波动留出足够的缓冲空间。
|
||||
**正确的做法**: 阈值应该设置为**P99响应时间的2-3倍**,给正常波动留出足够的缓冲空间。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 会话保持:让"老顾客"一直找同一个"收银员"
|
||||
## 4. 核心问题二:如何保证"老顾客"一直找同一个"收银员"?
|
||||
|
||||
想象你是奶茶店的常客,每次来都由同一个店员接待。她知道你的口味偏好(半糖、去冰),服务起来又快又贴心。但如果每次来都换一个新人,你得一遍遍重复同样的要求,效率大打折扣。
|
||||
### 4.1 会话保持:让"老顾客"一直找同一个"收银员"
|
||||
|
||||
**会话保持(Session Persistence/Sticky Session)** 就是解决这个问题的方法:确保同一个用户的请求,始终被路由到同一台后端服务器。
|
||||
想象你是奶茶店的常客,每次来都由同一个店员接待。她知道你的口味偏好(半糖、去冰),服务起来又快又贴心。但如果每次来都换一个新人,你得一遍遍重复同样的要求,效率大打折扣。
|
||||
|
||||
<ClientOnly>
|
||||
<SessionPersistenceDemo />
|
||||
</ClientOnly>
|
||||
**会话保持(Session Persistence/Sticky Session)** 就是解决这个问题的方法:确保同一个用户的请求,始终被路由到同一台后端服务器。
|
||||
|
||||
### 3.1 三种会话保持机制对比
|
||||
<SessionPersistenceDemo />
|
||||
|
||||
| 机制 | 实现原理 | 优点 | 缺点 | 适用场景 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| **Cookie 插入** | LB在响应中插入Cookie,后续请求携带此Cookie | 不受IP变化影响,首次请求即可保持 | 客户端需支持Cookie,可能被禁用 | 电商购物车、登录态保持 |
|
||||
| **IP 哈希** | 对客户端IP做哈希计算,映射到特定服务器 | 无需客户端支持,无状态 | IP变化会丢失会话,难以均匀分布 | 无Cookie环境、WebSocket |
|
||||
| **粘性会话表** | LB维护会话到服务器的映射表 | 支持会话复制和故障转移 | 占用LB内存,需要额外同步 | 高可用要求严格的场景 |
|
||||
### 4.2 三种会话保持机制对比
|
||||
|
||||
### 3.2 真实案例:电商大促期间的会话保持策略
|
||||
| 机制 | 实现原理 | 优点 | 缺点 | 适用场景 |
|
||||
| :------------- | :---------------------------------------- | :------------------------------ | :---------------------------- | :---------------------- |
|
||||
| **Cookie插入** | LB在响应中插入Cookie,后续请求携带此Cookie | 不受IP变化影响,首次请求即可保持 | 客户端需支持Cookie,可能被禁用 | 电商购物车、登录态保持 |
|
||||
| **IP哈希** | 对客户端IP做哈希计算,映射到特定服务器 | 无需客户端支持,无状态 | IP变化会丢失会话,难以均匀分布 | 无Cookie环境、WebSocket |
|
||||
| **粘性会话表** | LB维护会话到服务器的映射表 | 支持会话复制和故障转移 | 占用LB内存,需要额外同步 | 高可用要求严格的场景 |
|
||||
|
||||
某电商平台在大促期间面临以下挑战:
|
||||
::: tip 💡 使用建议
|
||||
|
||||
1. **购物车数据需要保持**:用户可能跨多个页面添加商品,需要保证请求都落在同一台服务器,购物车数据才能正确累计。
|
||||
|
||||
2. **秒杀场景下服务器动态扩容**:大促期间服务器数量从平时的10台动态扩展到50台。
|
||||
|
||||
3. **部分服务器可能故障**:需要能够快速剔除故障节点,同时不影响用户会话。
|
||||
|
||||
**他们的解决方案**:
|
||||
|
||||
1. **采用 Cookie 插入机制**:负载均衡器(Nginx)在首次响应时设置 `SERVERID` Cookie,值为后端服务器的唯一标识。
|
||||
|
||||
2. **会话表持久化**:将会话映射表存储在 Redis 集群中,即使某台 Nginx 重启,也能从 Redis 恢复会话映射关系。
|
||||
|
||||
3. **故障转移策略**:当后端服务器健康检查失败时,将其从可用列表移除。对于已经绑定到该服务器的会话,下次请求时重新哈希分配到新的健康节点(牺牲一次会话保持,换取服务可用性)。
|
||||
- **Cookie插入**: 优先推荐,兼容性好
|
||||
- **IP哈希**: 只用于WebSocket等特殊场景
|
||||
- **粘性会话表**: 配合Cookie,提供故障转移能力
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 4. 部署策略:蓝绿部署与金丝雀发布
|
||||
## 5. 核心问题三:如何实现零停机部署?
|
||||
|
||||
当新版本上线时,如何确保零停机?当新版本有 Bug 时,如何快速回滚?这涉及到两种经典的部署策略。
|
||||
### 5.1 蓝绿部署:"一键切换"的零停机发布
|
||||
|
||||
### 4.1 蓝绿部署:"一键切换"的零停机发布
|
||||
**核心思想**: 同时维护两套完全相同的生产环境(蓝环境和绿环境),但只有一个环境对外提供服务。
|
||||
|
||||
**核心思想**:同时维护两套完全相同的生产环境(蓝环境和绿环境),但只有一个环境对外提供服务。
|
||||
<BlueGreenDeploymentDemo />
|
||||
|
||||
<ClientOnly>
|
||||
<BlueGreenDeploymentDemo />
|
||||
</ClientOnly>
|
||||
**工作流程:**
|
||||
|
||||
**工作流程**:
|
||||
|
||||
1. **初始状态**:蓝环境运行 v1.0(生产),绿环境待命。
|
||||
|
||||
2. **部署新版本**:在绿环境部署 v1.1,进行内部冒烟测试。
|
||||
|
||||
3. **切换流量**:将负载均衡器指向绿环境,流量瞬间切换到 v1.1。
|
||||
|
||||
4. **监控观察**:观察绿环境运行状态,确认无异常。
|
||||
|
||||
5. **保留旧版本**:蓝环境保持 v1.0 一段时间(如24小时),作为快速回滚的保险。
|
||||
|
||||
**优缺点分析**:
|
||||
1. **初始状态**: 蓝环境运行v1.0(生产),绿环境待命。
|
||||
2. **部署新版本**: 在绿环境部署v1.1,进行内部冒烟测试。
|
||||
3. **切换流量**: 将负载均衡器指向绿环境,流量瞬间切换到v1.1。
|
||||
4. **监控观察**: 观察绿环境运行状态,确认无异常。
|
||||
5. **保留旧版本**: 蓝环境保持v1.0一段时间(如24小时),作为快速回滚的保险。
|
||||
|
||||
::: tip ✨ 优缺点分析
|
||||
| 优点 | 缺点 |
|
||||
|:---|:---|
|
||||
| ✅ 零停机时间,切换在毫秒级完成 | ❌ 资源成本高,需要同时维护两套环境 |
|
||||
| ✅ 快速回滚,发现问题立即切回原环境 | ❌ 数据库Schema变更时需要特别处理兼容性 |
|
||||
| ✅ 新环境可完整测试后再接管流量 | ❌ 不适用于有状态服务(如WebSocket长连接) |
|
||||
| ✅ 零停机时间,切换在毫秒级完成 | ❌ 资源成本高,需要同时维护两套环境 |
|
||||
| ✅ 快速回滚,发现问题立即切回原环境 | ❌ 数据库Schema变更时需要特别处理兼容性 |
|
||||
| ✅ 新环境可完整测试后再接管流量 | ❌ 不适用于有状态服务(如WebSocket长连接) |
|
||||
|
||||
**适用场景**:
|
||||
- 对可用性要求极高的金融、电商核心交易系统
|
||||
- 需要频繁发布但无法接受停机的 SaaS 服务
|
||||
- 有充足的硬件/云资源预算
|
||||
:::
|
||||
|
||||
### 4.2 金丝雀发布:"小步快跑"的灰度策略
|
||||
### 5.2 金丝雀发布:"小步快跑"的灰度策略
|
||||
|
||||
金丝雀发布得名于历史上的"煤矿金丝雀"——矿工带着金丝雀下井,如果金丝雀出现异常,说明有毒气体泄漏,矿工立即撤离。在软件发布中,金丝雀发布就是先让一小部分用户试用新版本,观察没有问题后再逐步扩大范围。
|
||||
金丝雀发布得名于历史上的"煤矿金丝雀"——矿工带着金丝雀下井,如果金丝雀出现异常,说明有毒气体泄漏,矿工立即撤离。在软件发布中,金丝雀发布就是先让一小部分用户试用新版本,观察没有问题后再逐步扩大范围。
|
||||
|
||||
<ClientOnly>
|
||||
<CanaryReleaseDemo />
|
||||
</ClientOnly>
|
||||
<CanaryReleaseDemo />
|
||||
|
||||
**核心思想**:
|
||||
**核心思想:**
|
||||
|
||||
1. **小流量先行**:先将 1% 的流量导入新版本服务器。
|
||||
|
||||
2. **观察指标**:持续监控错误率、延迟、业务关键指标。
|
||||
|
||||
3. **逐步放量**:如果一切正常,逐步将比例提升到 5%、10%、25%、50%、100%。
|
||||
|
||||
4. **快速回滚**:一旦发现异常,立即将所有流量切回旧版本。
|
||||
|
||||
**金丝雀发布的优势:**
|
||||
1. **小流量先行**: 先将1%的流量导入新版本服务器。
|
||||
2. **观察指标**: 持续监控错误率、延迟、业务关键指标。
|
||||
3. **逐步放量**: 如果一切正常,逐步将比例提升到5%、10%、25%、50%、100%。
|
||||
4. **快速回滚**: 一旦发现异常,立即将所有流量切回旧版本。
|
||||
|
||||
::: tip 💡 金丝雀发布的优势
|
||||
| 优势 | 说明 |
|
||||
|:---|:---|
|
||||
| 🎯 **风险可控** | 即使新版本有严重 Bug,也只影响少量用户 |
|
||||
| 📊 **真实验证** | 在真实生产环境验证,比测试环境更可靠 |
|
||||
| 🎯 **风险可控** | 即使新版本有严重Bug,也只影响少量用户 |
|
||||
| 📊 **真实验证** | 在真实生产环境验证,比测试环境更可靠 |
|
||||
| 🚀 **快速迭代** | 团队可以更自信地频繁发布新功能 |
|
||||
| 💰 **资源友好** | 不需要像蓝绿部署那样准备两套完整环境 |
|
||||
|
||||
**金丝雀发布的典型流量分配策略:**
|
||||
|
||||
```
|
||||
阶段 1 (5分钟): 1% 新版本 → 99% 旧版本
|
||||
阶段 2 (15分钟): 5% 新版本 → 95% 旧版本
|
||||
阶段 3 (30分钟): 10% 新版本 → 90% 旧版本
|
||||
阶段 4 (1小时): 25% 新版本 → 75% 旧版本
|
||||
阶段 5 (2小时): 50% 新版本 → 50% 旧版本
|
||||
阶段 6 (全量): 100%新版本
|
||||
```
|
||||
|
||||
**注意**:每个阶段都需要持续监控关键指标,只有确认无异常后才进入下一阶段。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 自动扩缩容:让系统自己"呼吸"
|
||||
## 6. 核心问题四:如何让系统自己"呼吸"?
|
||||
|
||||
想象你开了一家餐厅。午餐高峰期需要10个服务员,但下午3点闲时只需要2个。如果一直维持10个人,人工成本爆炸;如果一直只有2个人,高峰期顾客等得不耐烦全跑了。
|
||||
### 6.1 自动扩缩容:让系统像餐厅一样"灵活排班"
|
||||
|
||||
**自动扩缩容(Auto Scaling)就是让系统像餐厅一样"灵活排班"**——忙的时候自动加服务器,闲的时候自动减服务器。
|
||||
想象你开了一家餐厅:
|
||||
|
||||
<ClientOnly>
|
||||
<AutoScalingDemo />
|
||||
</ClientOnly>
|
||||
- **午餐高峰期**: 需要10个服务员,但下午3点闲时只需要2个
|
||||
- 如果一直维持10个\*\*: 人工成本爆炸
|
||||
- 如果一直只有2个: 高峰期顾客等不及,全跑了
|
||||
|
||||
### 5.1 扩容指标的选择
|
||||
**自动扩缩容(Auto Scaling)** 就是让系统像餐厅一样"灵活排班"——忙的时候自动加服务器,闲的时候自动减服务器。
|
||||
|
||||
自动扩缩容的核心是回答一个问题:**什么时候该加机器?什么时候该减机器?**
|
||||
<AutoScalingDemo />
|
||||
|
||||
常见的决策指标:
|
||||
### 6.2 扩容指标的选择
|
||||
|
||||
| 指标 | 扩容阈值 | 缩容阈值 | 适用场景 |
|
||||
|:---|:---|:---|:---|
|
||||
| **CPU 使用率** | > 70% | < 30% | 计算密集型应用 |
|
||||
| **内存使用率** | > 75% | < 40% | 内存密集型应用 |
|
||||
| **QPS (每秒请求数)** | > 1000/s | < 400/s | API 网关、Web 服务 |
|
||||
| **连接数** | > 5000 | < 1000 | 数据库、消息队列 |
|
||||
| **自定义业务指标** | 视业务而定 | 视业务而定 | 特定业务场景 |
|
||||
自动扩缩容的核心是回答一个问题:\*\* **什么时候该加机器?什么时候该减机器?**
|
||||
|
||||
### 5.2 扩容策略的"坑"与"解"
|
||||
常见的决策指标:
|
||||
|
||||
**踩坑1:扩容反应太慢,流量洪峰已经把系统打挂了**
|
||||
| 指标 | 扩容阈值 | 缩容阈值 | 适用场景 |
|
||||
| :------------------ | :--------- | :--------- | :--------------- |
|
||||
| **CPU使用率** | > 70% | < 30% | 计算密集型应用 |
|
||||
| **内存使用率** | > 75% | < 40% | 内存密集型应用 |
|
||||
| **QPS(每秒请求数)** | > 1000/s | < 400/s | API网关、Web服务 |
|
||||
| **连接数** | > 5000 | < 1000 | 数据库、消息队列 |
|
||||
| **自定义业务指标** | 视业务而定 | 视业务而定 | 特定业务场景 |
|
||||
|
||||
某电商大促期间,设置 CPU > 80% 触发扩容,但监控采集有1分钟延迟,新实例启动需要3分钟。结果流量来得太快,扩容还没完成,服务器已经被打挂。
|
||||
::: tip 💡 扩容策略的"坑"与"解"
|
||||
|
||||
**解决方案**:
|
||||
- **提前扩容**:基于历史数据预测流量高峰,提前30分钟开始扩容
|
||||
- **多级阈值**:设置 60% 预警(开始预热新实例)、70% 正式扩容、80% 紧急扩容
|
||||
- **快速扩容**:使用容器化部署,新实例30秒内启动(相比虚拟机3-5分钟)
|
||||
**坑1:扩容反应太慢,流量洪峰已经把系统打挂了**
|
||||
|
||||
**踩坑2:扩容太激进,成本爆炸**
|
||||
某电商大促期间,设置CPU > 80%触发扩容,但监控采集有1分钟延迟,新实例启动需要3分钟。结果流量来得太快,扩容还没完成,服务器已经被打挂。
|
||||
|
||||
某创业公司设置了激进的自动扩容策略:CPU > 50% 就扩容。结果一个正常的业务波动就触发了扩容,服务器数量从5台膨胀到30台,月底云账单吓哭了 CTO。
|
||||
**解决方案:**
|
||||
|
||||
**解决方案**:
|
||||
- **设置扩容冷却时间**:一次扩容后,至少等待5分钟才能再次扩容
|
||||
- **设置最大实例数**:max = 当前实例数 × 2,防止无限膨胀
|
||||
- **区分突刺和趋势**:只有连续3个周期都超过阈值才扩容,避免单点突刺触发
|
||||
- **提前扩容**: 基于历史数据预测流量高峰,提前30分钟开始扩容
|
||||
- **多级阈值**: 设置60%预警(开始预热新实例)、70%正式扩容、80%紧急扩容
|
||||
- **快速扩容**: 使用容器化部署,新实例30秒内启动(相比虚拟机3-5分钟)
|
||||
|
||||
**踩坑3:缩容太快,刚扩容的机器马上就缩了**
|
||||
**坑2:扩容太激进,成本爆炸**
|
||||
|
||||
某团队设置了 CPU < 30% 缩容。扩容后流量还在消化,CPU 短暂回落到 25%,触发了缩容。刚缩完 CPU 又飙到 80%,又触发扩容——系统在"扩容-缩容-扩容"中疯狂震荡。
|
||||
某创业公司设置了激进的自动扩容策略:CPU > 50%就扩容。结果一个正常的业务波动就触发了扩容,服务器数量从5台膨胀到30台,月底云账单吓哭了CTO。
|
||||
|
||||
**解决方案**:
|
||||
- **缩容更保守**:扩容阈值 70%,缩容阈值 25%,中间有足够的缓冲带
|
||||
- **缩容冷却时间更长**:扩容后至少等待10分钟才能缩容
|
||||
- **渐进式缩容**:一次只缩 1 台,观察后再决定要不要继续缩
|
||||
**解决方案:**
|
||||
|
||||
- **设置扩容冷却时间**: 一次扩容后,至少等待5分钟才能再次扩容
|
||||
- **设置最大实例数**: max = 当前实例数 × 2,防止无限膨胀
|
||||
- **区分突刺和趋势**: 只有连续3个周期都超过阈值才扩容,避免单点突刺触发
|
||||
|
||||
**坑3:缩容太快,刚扩容的机器马上就缩了**
|
||||
|
||||
某团队设置了CPU < 30%缩容。扩容后流量还在消化,CPU短暂回落到25%,触发了缩容。刚缩完CPU又飙到80%,又触发扩容——系统在"扩容-缩容-扩容"中疯狂震荡。
|
||||
|
||||
**解决方案:**
|
||||
|
||||
- **缩容更保守**: 扩容阈值70%,缩容阈值25%,中间有足够的缓冲带
|
||||
- **缩容冷却时间更长**: 扩容后至少等待10分钟才能缩容
|
||||
- **渐进式缩容**: 一次只缩1台,观察后再决定要不要继续缩
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 6. 多区域部署:当"灾难"来临时
|
||||
## 7. 实战:如何选择负载均衡器?
|
||||
|
||||
想象你的奶茶店生意火爆,但你只有一个店面。某天突如其来的暴雨把店淹了,你得停业整修两周。这两周里,所有顾客都跑去竞争对手那里了,等你重新开业,客源已经流失大半。
|
||||
### 7.1 主流负载均衡器对比
|
||||
|
||||
**单点故障是系统架构中的"阿喀琉斯之踵"**。多区域部署(Multi-Region Deployment)就是解决这个问题的方法:在不同地理位置部署多个数据中心,即使一个区域完全不可用,其他区域也能继续提供服务。
|
||||
| 特性 | Nginx | HAProxy | Envoy | 云厂商负载均衡 |
|
||||
| -------------- | ------------------------------- | --------------------- | -------------- | -------------- |
|
||||
| **定位** | 高性能反向代理/负载均衡 | 开源负载均衡 | 云原生代理 | 托管负载均衡 |
|
||||
| **性能** | 极高(C语言,事件驱动) | 高(事件驱动) | 高(C++/Rust) | 极高 |
|
||||
| **功能丰富度** | 基础负载均衡、静态文件、缓存 | 丰富的负载均衡算法 | 高级路由、观测 | 功能全面 |
|
||||
| **配置** | 配置文件(nginx.conf) | 配置文件(haproxy.cfg) | API/配置文件 | UI控制台 |
|
||||
| **扩展** | C模块/Lua脚本 | Lua脚本 | WASM/Filter | 插件 |
|
||||
| **适用场景** | 静态资源、七层负载均衡、SSL终结 | 七层负载均衡、高可用 | 服务网格、多云 | 快速上手 |
|
||||
|
||||
<ClientOnly>
|
||||
<MultiRegionDemo />
|
||||
</ClientOnly>
|
||||
|
||||
### 6.1 异地多活架构的核心概念
|
||||
|
||||
**主备模式(Active-Standby)**:
|
||||
- 只有一个区域对外提供服务(主),其他区域待命(备)
|
||||
- 备区实时同步数据,但不处理流量
|
||||
- 主区故障时,手动或自动切换到备区
|
||||
- **优点**:架构简单,数据一致性好
|
||||
- **缺点**:备区资源利用率低,切换时有中断
|
||||
|
||||
**多活模式(Active-Active)**:
|
||||
- 多个区域同时对外提供服务
|
||||
- 用户请求被路由到最近的区域
|
||||
- 区域之间实时同步数据
|
||||
- **优点**:资源利用率高,故障影响小
|
||||
- **缺点**:架构复杂,数据一致性挑战大
|
||||
|
||||
### 6.2 数据同步:多活架构的"阿喀琉斯之踵"
|
||||
|
||||
多活架构最大的挑战是**数据一致性**。当两个区域同时处理写入请求时,如何保证数据不会冲突?
|
||||
|
||||
**场景示例**:
|
||||
- 北京区域:用户A给账户充值 100 元,余额从 200 变为 300
|
||||
- 上海区域:几乎同时,用户A消费 50 元,余额从 200 变为 150
|
||||
|
||||
如果两个区域分别执行后同步,最终余额应该是多少?300?150?还是其他值?
|
||||
|
||||
**解决方案对比:**
|
||||
|
||||
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| **主从复制** | 只有一个主库可写,从库只读 | 实现简单,数据一致性好 | 主库单点,跨地域延迟大 | 读多写少,对一致性要求高 |
|
||||
| **多主复制** | 多个主库可同时写,异步同步 | 写入性能高,就近写入 | 冲突解决复杂,可能丢数据 | 写入频繁,可接受短暂不一致 |
|
||||
| **分布式事务** | 使用 2PC/3PC/TCC 等协议保证跨库事务 | 强一致性 | 性能开销大,复杂度高 | 金融交易等对一致性要求极高 |
|
||||
| **CRDT(无冲突复制数据类型)** | 数学上保证无冲突的数据结构 | 自动合并,无需锁 | 数据类型受限,实现复杂 | 计数器、集合等特定场景 |
|
||||
|
||||
**真实案例:全球电商平台的订单系统**
|
||||
|
||||
某跨境电商在全球5个区域部署了数据中心。订单系统的架构设计如下:
|
||||
|
||||
- **订单创建**:使用"分区路由"策略,根据用户ID哈希确定主处理区域,该区域负责订单创建和初始状态变更,避免跨区域的写入冲突。
|
||||
|
||||
- **库存扣减**:使用分布式锁 + 乐观锁。库存数据以用户所在区域的副本为主,当跨区域访问时,先获取分布式锁,检查版本号,避免超卖。
|
||||
|
||||
- **最终一致性**:非关键数据(如推荐、统计)采用异步同步,允许秒级的延迟;关键数据(如支付状态)采用强同步,确保跨区一致性。
|
||||
|
||||
这套架构在实践中实现了 99.99% 的可用性,同时控制了跨区域同步的平均延迟在 100ms 以内。
|
||||
|
||||
---
|
||||
|
||||
## 7. 实战模板:从零搭建负载均衡架构
|
||||
|
||||
看完了理论,我们来动手实践。以下是一套可直接落地的架构方案。
|
||||
|
||||
### 7.1 中小型 Web 应用的推荐架构
|
||||
|
||||
**场景**:日活 10万 的电商平台,预算有限,团队规模 10人左右。
|
||||
|
||||
**架构方案:**
|
||||
::: tip 💡 选型建议
|
||||
**决策树:**
|
||||
|
||||
```
|
||||
用户请求
|
||||
↓
|
||||
[DNS 轮询] 多地域就近访问
|
||||
↓
|
||||
[CDN] 静态资源缓存(图片、JS、CSS)
|
||||
↓
|
||||
[L7 负载均衡 - Nginx] SSL卸载、URL路由、限流
|
||||
↓
|
||||
[Web 服务器 - Node.js/Java] 业务逻辑处理
|
||||
↓
|
||||
[缓存层 - Redis Cluster] 会话、热点数据
|
||||
↓
|
||||
[数据库 - MySQL 主从] 读写分离
|
||||
选择负载均衡器:
|
||||
│
|
||||
├─ 只需要基础的四层负载均衡?
|
||||
│ ├─ 是 → LVS(开源免费)或 云厂商NLB
|
||||
│ └─ 否 → 继续
|
||||
│
|
||||
├─ 需要服务网格、多云部署?
|
||||
│ ├─ 是 → Envoy
|
||||
│ └─ 否 → 继续
|
||||
│
|
||||
├─ 需要极其复杂的配置和插件?
|
||||
│ ├─ 是 → HAProxy
|
||||
│ └─ 否 → 继续
|
||||
│
|
||||
├─ 需要高性能+简单配置?
|
||||
│ ├─ 是 → Nginx(首选)
|
||||
│ └─ 继续
|
||||
│
|
||||
├─ 想要托管运维?
|
||||
│ ├─ 是 → 云厂商负载均衡(AWS ALB、阿里SLB)
|
||||
│ └─ Nginx自建
|
||||
```
|
||||
|
||||
**关键配置:**
|
||||
|
||||
**Nginx 负载均衡配置示例:**
|
||||
|
||||
```nginx
|
||||
upstream backend {
|
||||
# 加权轮询,性能好的服务器权重更高
|
||||
server 10.0.1.10 weight=5;
|
||||
server 10.0.1.11 weight=3;
|
||||
server 10.0.1.12 weight=2 backup; # backup 标记为备用
|
||||
|
||||
keepalive 32; # 长连接复用
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.example.com;
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# 限流配置
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 会话保持配置(基于IP哈希)
|
||||
# 注意:在 upstream 中配置 ip_hash; 代替加权轮询
|
||||
}
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 系统架构演进建议
|
||||
|
||||
**阶段一:起步期(日活 < 1万)**
|
||||
- 单台服务器部署
|
||||
- Nginx 做反向代理 + 负载均衡
|
||||
- 重点关注:监控告警、日志收集
|
||||
|
||||
**阶段二:成长期(日活 1万-10万)**
|
||||
- 横向扩展:2-3 台 Web 服务器
|
||||
- 引入 Redis 做缓存和会话存储
|
||||
- MySQL 主从复制,读写分离
|
||||
- 引入 CDN 加速静态资源
|
||||
|
||||
**阶段三:成熟期(日活 10万-100万)**
|
||||
- 多地域部署,就近访问
|
||||
- 引入消息队列削峰填谷
|
||||
- 数据库分库分表
|
||||
- 自动化运维:CI/CD、自动扩缩容
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 8. 名词对照表
|
||||
## 8. 总结:负载均衡的核心思维
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
|:---|:---|:---|
|
||||
| **Load Balancer** | 负载均衡器 | 将流量分发到多个后端服务器的设备或软件 |
|
||||
| **L4 Load Balancing** | 四层负载均衡 | 基于传输层(TCP/UDP)的负载均衡 |
|
||||
| **L7 Load Balancing** | 七层负载均衡 | 基于应用层(HTTP/HTTPS)的负载均衡 |
|
||||
| **Health Check** | 健康检查 | 定期检查后端服务器健康状态的机制 |
|
||||
| **Session Persistence** | 会话保持 | 确保同一用户的请求始终路由到同一台服务器 |
|
||||
| **Sticky Session** | 粘性会话 | 另一种称呼,同 Session Persistence |
|
||||
| **Blue-Green Deployment** | 蓝绿部署 | 两套环境切换的零停机发布策略 |
|
||||
| **Canary Release** | 金丝雀发布 | 小流量先行验证的灰度发布策略 |
|
||||
| **Auto Scaling** | 自动扩缩容 | 根据负载自动增加或减少服务器数量 |
|
||||
| **Horizontal Scaling** | 水平扩展 | 增加服务器数量来提升处理能力 |
|
||||
| **Vertical Scaling** | 垂直扩展 | 提升单机配置(CPU、内存)来提升处理能力 |
|
||||
| **Multi-Region** | 多区域 | 在多个地理区域部署服务 |
|
||||
| **Active-Active** | 多活 | 多个区域同时对外提供服务 |
|
||||
| **Active-Standby** | 主备 | 只有一个区域提供服务,其他待命 |
|
||||
| **Data Replication** | 数据同步 | 跨区域的数据复制机制 |
|
||||
| **RTO** | 恢复时间目标 | 系统故障后需要在多长时间内恢复 |
|
||||
| **RPO** | 恢复点目标 | 系统故障后可以接受的数据丢失量 |
|
||||
### 8.1 核心原则回顾
|
||||
|
||||
| 原则 | 含义 | 实践要点 |
|
||||
| -------- | -------------------------- | ------------------------------------- |
|
||||
| **分层** | L4处理"快递分拣"(快但简单) | L4处理数据库、游戏;L7处理Web、API |
|
||||
| **冗余** | 单点故障是架构的敌人 | 通过多实例、多区域部署提升可用性 |
|
||||
| **渐进** | 发布新版本不要"一刀切" | 蓝绿部署实现零停机;金丝雀实现风险可控 |
|
||||
| **弹性** | 系统应该像生命体一样"呼吸" | 忙时自动扩容,闲时自动缩容 |
|
||||
|
||||
### 8.2 设计检查清单
|
||||
|
||||
在引入负载均衡前,问自己以下问题:
|
||||
|
||||
- [ ] 是否真的需要负载均衡?(单机性能是否真的不够)
|
||||
- [ ] 选择L4还是L7?(根据业务场景)
|
||||
- [ ] 如何处理会话保持?(Cookie、IP哈希、会话表)
|
||||
- [ ] 如何实现健康检查?(主动、被动、阈值设置)
|
||||
- [ ] 如何实现零停机?(蓝绿部署、金丝雀)
|
||||
- [ ] 如何实现弹性?(扩缩指标、冷却时间、最大实例数)
|
||||
|
||||
---
|
||||
|
||||
## 总结:负载均衡的核心思维
|
||||
## 9. 名词速查表
|
||||
|
||||
通过本文的学习,我们可以提炼出负载均衡设计的几个核心思维:
|
||||
|
||||
**1. 分层思维**
|
||||
- L4 处理"快递分拣"(快但简单)
|
||||
- L7 处理"内容检查"(慢但智能)
|
||||
- 根据场景选择合适的层次
|
||||
|
||||
**2. 冗余思维**
|
||||
- 单点故障是架构的敌人
|
||||
- 通过多实例、多区域部署提升可用性
|
||||
- 健康检查确保"坏节点"及时剔除
|
||||
|
||||
**3. 渐进思维**
|
||||
- 发布新版本不要"一刀切"
|
||||
- 蓝绿部署实现零停机
|
||||
- 金丝雀发布实现风险可控
|
||||
|
||||
**4. 弹性思维**
|
||||
- 系统应该像生命体一样"呼吸"
|
||||
- 忙时自动扩容,闲时自动缩容
|
||||
- 多区域部署实现就近服务和容灾
|
||||
|
||||
负载均衡不是简单的"流量分发",而是一套关于**高可用、高性能、高弹性**的系统工程思维。希望本文能帮助你在实际工作中做出更好的架构决策。
|
||||
| 名词 | 英文 | 解释 |
|
||||
| ---------------- | --------------------- | ---------------------------------------- | ------------------------------ |
|
||||
| **负载均衡器** | Load Balancer | 将流量分发到多个后端服务器的设备或软件 |
|
||||
| **四层负载均衡** | L4 Load Balancing | 基于传输层(TCP/UDP)的负载均衡 |
|
||||
| **七层负载均衡** | L7 Load Balancing | 基于应用层(HTTP/HTTPS)的负载均衡 |
|
||||
| **健康检查** | Health Check | 定期检查后端服务器的健康状态的机制 |
|
||||
| **会话保持** | Session Persistence | 确保同一用户的请求始终路由到同一台服务器 |
|
||||
| **粘性会话** | Sticky Session | 另一种称呼,同Session Persistence |
|
||||
| **蓝绿部署** | Blue-Green Deployment | 两套环境切换的零停机发布策略 |
|
||||
| **金丝雀发布** | Canary Release | 小流量先行验证的灰度发布策略 |
|
||||
| **自动扩缩容** | Auto Scaling | 根据负载自动增加或减少服务器数量 |
|
||||
| **水平扩展** | Horizontal Scaling | 增加服务器数量来提升处理能力 |
|
||||
| **垂直扩展** | Vertical Scaling | 提升单机配置(CPU、内存)来提升处理能力 |
|
||||
| **多区域** | Multi-Region | 在多个地理区域部署服务 |
|
||||
| **多活** | Active-Active | 多个区域同时对外提供服务 |
|
||||
| **主备** | Active-Standby | 只有一个区域提供服务,其他待命 |
|
||||
| **数据同步** | Data Replication | 跨区域的数据复制机制 |
|
||||
| **RTO** | RTO | 恢复时间目标 | 系统故障后需要在多长时间内恢复 |
|
||||
| **RPO** | RPO | 恢复点目标 | 系统故障后可以接受的数据丢失量 |
|
||||
|
||||
+279
-617
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,6 @@
|
||||
|
||||
<script setup>
|
||||
import UrlToBrowserQuickStart from '../../.vitepress/theme/components/appendix/url-to-browser/UrlToBrowserQuickStart.vue'
|
||||
import UrlParserDemo from '../../.vitepress/theme/components/appendix/url-to-browser/UrlParserDemo.vue'
|
||||
import DnsLookupDemo from '../../.vitepress/theme/components/appendix/url-to-browser/DnsLookupDemo.vue'
|
||||
import TcpHandshakeDemo from '../../.vitepress/theme/components/appendix/url-to-browser/TcpHandshakeDemo.vue'
|
||||
import HttpExchangeDemo from '../../.vitepress/theme/components/appendix/url-to-browser/HttpExchangeDemo.vue'
|
||||
import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url-to-browser/BrowserRenderingDemo.vue'
|
||||
</script>
|
||||
|
||||
> **学习指南**:本章节无需编程基础。我们将用**"网购"**的生活化比喻,配合**真实的技术过程**,带你一步步理解浏览器如何将一行网址变成丰富多彩的页面。
|
||||
@@ -16,6 +11,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
## 0. 引言:当你按下回车键的那一刻
|
||||
|
||||
想象你正在进行一次**网购**。你需要:
|
||||
|
||||
1. **填写订单**(选好商品,确认收货地址)
|
||||
2. **系统查找仓库**(根据店铺名找到具体的发货仓库)
|
||||
3. **建立物流通道**(确保仓库正常营业且能发货)
|
||||
@@ -35,6 +31,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
### 生活比喻:填写购物单
|
||||
|
||||
假设你只在订单上写"买鞋子",仓库肯定不知道发哪双。你需要写清楚:
|
||||
|
||||
- **店铺类型**(官方旗舰店/普通店)
|
||||
- **店铺名称**(Nike 官方店)
|
||||
- **商品位置**(男鞋区/跑鞋系列)
|
||||
@@ -45,14 +42,14 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
|
||||
**URL(Uniform Resource Locator,统一资源定位符)**就是浏览器世界的"商品定位码"。当你在地址栏输入 `https://www.example.com:8080/path/page.html?id=123#section`,浏览器会立即拆解它:
|
||||
|
||||
| URL 部分 | 示例值 | 网购类比 | 技术作用 |
|
||||
|----------|--------|-----------|----------|
|
||||
| **协议** `https://` | 安全超文本传输协议 | **物流方式**:保密配送(HTTPS)vs 普通配送(HTTP) | 决定使用什么规则通信。`http` 是普通传输,`https` 是加密传输 |
|
||||
| **域名** `www.example.com` | 服务器的人类可读名字 | **店铺名称**:京东超市 | 告诉浏览器要找哪台服务器。域名是为了让人记住,最终要转换成 IP 地址 |
|
||||
| **端口** `:8080` | 服务器的具体"门牌号" | **柜台编号**:3号柜台(默认不写) | 服务器上可能有多个服务,端口指定访问哪一个。HTTP 默认 80,HTTPS 默认 443 |
|
||||
| **路径** `/path/page.html` | 服务器上的文件位置 | **货架位置**:日用品区/第三排 | 指定服务器上的具体资源位置 |
|
||||
| **查询参数** `?id=123` | 附加信息 | **订单备注**:红色、XL码 | 传递给服务器的额外数据,如搜索关键词、页码等 |
|
||||
| **锚点** `#section` | 页面内的位置 | **说明书页码**:翻到第5页 | 页面加载后自动滚动到指定位置,不发送给服务器 |
|
||||
| URL 部分 | 示例值 | 网购类比 | 技术作用 |
|
||||
| -------------------------- | -------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| **协议** `https://` | 安全超文本传输协议 | **物流方式**:保密配送(HTTPS)vs 普通配送(HTTP) | 决定使用什么规则通信。`http` 是普通传输,`https` 是加密传输 |
|
||||
| **域名** `www.example.com` | 服务器的人类可读名字 | **店铺名称**:京东超市 | 告诉浏览器要找哪台服务器。域名是为了让人记住,最终要转换成 IP 地址 |
|
||||
| **端口** `:8080` | 服务器的具体"门牌号" | **柜台编号**:3号柜台(默认不写) | 服务器上可能有多个服务,端口指定访问哪一个。HTTP 默认 80,HTTPS 默认 443 |
|
||||
| **路径** `/path/page.html` | 服务器上的文件位置 | **货架位置**:日用品区/第三排 | 指定服务器上的具体资源位置 |
|
||||
| **查询参数** `?id=123` | 附加信息 | **订单备注**:红色、XL码 | 传递给服务器的额外数据,如搜索关键词、页码等 |
|
||||
| **锚点** `#section` | 页面内的位置 | **说明书页码**:翻到第5页 | 页面加载后自动滚动到指定位置,不发送给服务器 |
|
||||
|
||||
<UrlParserDemo />
|
||||
|
||||
@@ -65,6 +62,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
### 生活比喻:查仓库地址
|
||||
|
||||
你下单写的是"Nike 官方店",但物流系统不知道仓库在哪。它需要查地址簿:
|
||||
|
||||
1. 先查**常用地址**(最近买过这家吗)→ 浏览器缓存
|
||||
2. 没有的话问**小区快递点**(他们知道大区域的分配)→ 本地 DNS 服务器
|
||||
3. 问**总部调度中心**(知道.com类店铺归谁管)→ 根域名服务器
|
||||
@@ -89,6 +87,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
```
|
||||
|
||||
**查询类型说明:**
|
||||
|
||||
- **递归查询(Recursive Query)**:浏览器只发一次请求,本地 DNS 负责层层查询后返回结果
|
||||
- **迭代查询(Iterative Query)**:每一层只告诉下一层去哪查,浏览器需要多次查询
|
||||
- **缓存机制**:查询结果会被缓存,下次直接返回,大大加速访问
|
||||
@@ -104,6 +103,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
### 生活比喻:建立物流通道
|
||||
|
||||
假设物流车直接开到仓库,结果:
|
||||
|
||||
- 仓库关门了 → 白跑一趟
|
||||
- 仓库爆仓不接单 → 无法发货
|
||||
- 找不到卸货口 → 无法对接
|
||||
@@ -135,6 +135,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
三次握手确保:**双方都能发、双方都能收** —— 四个条件都满足,才能可靠传输。
|
||||
|
||||
**TCP 还负责:**
|
||||
|
||||
- **数据分包**:大数据拆成小数据包传输
|
||||
- **顺序重组**:确保数据包按正确顺序组装
|
||||
- **错误重传**:丢包后自动重新发送
|
||||
@@ -158,6 +159,7 @@ import BrowserRenderingDemo from '../../.vitepress/theme/components/appendix/url
|
||||
**HTTP(HyperText Transfer Protocol,超文本传输协议)**是浏览器和服务器之间的"对话规则"。通道建立后,浏览器发送**取货请求**,**核心目标是拿回网页的源代码(HTML 文件)**:
|
||||
|
||||
**HTTP 请求示例:**
|
||||
|
||||
```http
|
||||
GET /index.html HTTP/1.1 ← 请求方法 + 路径 + 协议版本
|
||||
Host: www.example.com ← 目标主机(支持虚拟主机,一台服务器可托管多个网站)
|
||||
@@ -174,8 +176,9 @@ Cookie: session_id=abc123 ← 身份凭证
|
||||
你平时写的 API 调用(`fetch` / `axios`)和浏览器访问网页,在 **HTTP 层面完全是同一个东西**。
|
||||
|
||||
它们都是发送一个请求,服务器返回一段文本数据。
|
||||
* 如果服务器给的是 **HTML**,浏览器就把它**画出来**(变成网页)。
|
||||
* 如果服务器给的是 **JSON**,你的代码就把它**存起来**(用于逻辑处理)。
|
||||
|
||||
- 如果服务器给的是 **HTML**,浏览器就把它**画出来**(变成网页)。
|
||||
- 如果服务器给的是 **JSON**,你的代码就把它**存起来**(用于逻辑处理)。
|
||||
|
||||
**根本就没有"两种"请求,只有同一种 HTTP 请求,只是返回的数据格式(Content-Type)不同而已。**
|
||||
这也是为什么理解了 HTTP,你就理解了 90% 的后端 API 原理。
|
||||
@@ -184,6 +187,7 @@ Cookie: session_id=abc123 ← 身份凭证
|
||||
:::
|
||||
|
||||
**常见 HTTP 方法:**
|
||||
|
||||
- `GET`:获取资源(安全、幂等,可被缓存)
|
||||
- `POST`:提交数据(创建资源,如注册、登录)
|
||||
- `PUT`:更新资源(完整替换)
|
||||
@@ -192,6 +196,7 @@ Cookie: session_id=abc123 ← 身份凭证
|
||||
- `HEAD`:获取响应头(不返回主体,用于检查资源是否存在)
|
||||
|
||||
**服务器返回 HTTP 响应:**
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK ← 协议版本 + 状态码 + 状态描述
|
||||
Date: Mon, 23 May 2025 12:00:00 GMT ← 服务器时间
|
||||
@@ -205,18 +210,18 @@ Set-Cookie: user_id=xyz789 ← 设置 Cookie
|
||||
|
||||
**HTTP 状态码分类:**
|
||||
|
||||
| 状态码 | 类别 | 含义 | 生活类比 |
|
||||
|--------|------|------|----------|
|
||||
| **200** | 成功 | 请求成功处理 | "订单确认,马上发货" |
|
||||
| **301/302** | 重定向 | 资源已移动 | "本店搬家了,请去新店下单" |
|
||||
| **304** | 未修改 | 缓存仍有效 | "你上次买的还能用,不用重新发货" |
|
||||
| **400** | 客户端错误 | 请求格式错误 | "订单填写模糊,看不懂" |
|
||||
| **401** | 未授权 | 需要身份验证 | "请先出示会员卡" |
|
||||
| **403** | 禁止访问 | 权限不足 | "非内部人员禁止入内" |
|
||||
| **404** | 未找到 | 资源不存在 | "仓库里没这款商品" |
|
||||
| **500** | 服务器错误 | 服务器内部错误 | "仓库起火了,暂时发不了货" |
|
||||
| **502** | 网关错误 | 上游服务器无响应 | "总仓没货了,分仓也调不到" |
|
||||
| **503** | 服务不可用 | 服务器过载或维护 | "爆单了,暂停接单" |
|
||||
| 状态码 | 类别 | 含义 | 生活类比 |
|
||||
| ----------- | ---------- | ---------------- | -------------------------------- |
|
||||
| **200** | 成功 | 请求成功处理 | "订单确认,马上发货" |
|
||||
| **301/302** | 重定向 | 资源已移动 | "本店搬家了,请去新店下单" |
|
||||
| **304** | 未修改 | 缓存仍有效 | "你上次买的还能用,不用重新发货" |
|
||||
| **400** | 客户端错误 | 请求格式错误 | "订单填写模糊,看不懂" |
|
||||
| **401** | 未授权 | 需要身份验证 | "请先出示会员卡" |
|
||||
| **403** | 禁止访问 | 权限不足 | "非内部人员禁止入内" |
|
||||
| **404** | 未找到 | 资源不存在 | "仓库里没这款商品" |
|
||||
| **500** | 服务器错误 | 服务器内部错误 | "仓库起火了,暂时发不了货" |
|
||||
| **502** | 网关错误 | 上游服务器无响应 | "总仓没货了,分仓也调不到" |
|
||||
| **503** | 服务不可用 | 服务器过载或维护 | "爆单了,暂停接单" |
|
||||
|
||||
<HttpExchangeDemo />
|
||||
|
||||
@@ -263,22 +268,29 @@ Document
|
||||
浏览器解析所有的 CSS(内联、外部文件),构建**CSSOM(CSS Object Model)树**。这就像理解说明书上的样式规则:
|
||||
|
||||
```css
|
||||
.header { color: blue; font-size: 24px; } /* 标题要是蓝色的 */
|
||||
.content { display: none; } /* 内容暂时隐藏 */
|
||||
.header {
|
||||
color: blue;
|
||||
font-size: 24px;
|
||||
} /* 标题要是蓝色的 */
|
||||
.content {
|
||||
display: none;
|
||||
} /* 内容暂时隐藏 */
|
||||
```
|
||||
|
||||
#### 步骤3:合并 → 渲染树 (准备组装)
|
||||
|
||||
DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
关键点:**只有"可见"的元素才会在渲染树中**。
|
||||
* `.header`:在渲染树中(可见)。
|
||||
* `.content`:**不在**渲染树中(因为 `display: none`,就像被扔掉的包装纸,不需要组装)。
|
||||
|
||||
- `.header`:在渲染树中(可见)。
|
||||
- `.content`:**不在**渲染树中(因为 `display: none`,就像被扔掉的包装纸,不需要组装)。
|
||||
|
||||
#### 步骤4:布局 (Layout / Reflow) —— 测量尺寸
|
||||
|
||||
浏览器计算渲染树中每个节点在屏幕上的**精确坐标和大小**。
|
||||
* "这个标题框宽 100px,高 50px,放在屏幕左上角 (0,0) 位置。"
|
||||
* 这个过程叫**重排 (Reflow)**。如果窗口大小变了(比如手机横屏),所有元素的位置都要重新计算,非常消耗性能。
|
||||
|
||||
- "这个标题框宽 100px,高 50px,放在屏幕左上角 (0,0) 位置。"
|
||||
- 这个过程叫**重排 (Reflow)**。如果窗口大小变了(比如手机横屏),所有元素的位置都要重新计算,非常消耗性能。
|
||||
|
||||
#### 步骤5:绘制 (Paint) —— 上色
|
||||
|
||||
@@ -300,17 +312,18 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
|
||||
让我们回顾整个旅程:
|
||||
|
||||
| 阶段 | 技术术语 | 网购类比 | 核心任务 | 关键技术 |
|
||||
|------|----------|----------|----------|----------|
|
||||
| **1. 解析** | URL 解析 | 填写订单 | 理解买家想买什么 | 协议、域名、端口、路径、参数 |
|
||||
| **2. 查询** | DNS 查询 | 查仓库址 | 找到店铺的发货仓库 | 递归/迭代查询、缓存机制 |
|
||||
| **3. 连接** | TCP 握手 | 建立通道 | 确保物流通畅 | 三次握手、序列号、流量控制 |
|
||||
| **4. 对话** | HTTP 交换 | 仓库发货 | 提交订单并收货 | 请求方法、状态码、头部字段 |
|
||||
| **5. 展示** | 浏览器渲染 | 拆箱组装 | 把商品展示出来 | DOM、CSSOM、渲染树、布局、绘制 |
|
||||
| 阶段 | 技术术语 | 网购类比 | 核心任务 | 关键技术 |
|
||||
| ----------- | ---------- | -------- | ------------------ | ------------------------------ |
|
||||
| **1. 解析** | URL 解析 | 填写订单 | 理解买家想买什么 | 协议、域名、端口、路径、参数 |
|
||||
| **2. 查询** | DNS 查询 | 查仓库址 | 找到店铺的发货仓库 | 递归/迭代查询、缓存机制 |
|
||||
| **3. 连接** | TCP 握手 | 建立通道 | 确保物流通畅 | 三次握手、序列号、流量控制 |
|
||||
| **4. 对话** | HTTP 交换 | 仓库发货 | 提交订单并收货 | 请求方法、状态码、头部字段 |
|
||||
| **5. 展示** | 浏览器渲染 | 拆箱组装 | 把商品展示出来 | DOM、CSSOM、渲染树、布局、绘制 |
|
||||
|
||||
**整个过程通常在几百毫秒内完成** —— 想想这有多么不可思议!
|
||||
|
||||
你的浏览器在不到1秒的时间里:
|
||||
|
||||
- 解析了一个复杂的地址
|
||||
- 查询了分布在全球的 DNS 服务器
|
||||
- 和千里之外的服务器建立了可靠连接
|
||||
@@ -323,20 +336,20 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
|
||||
## 7. 名词速查表 (Glossary)
|
||||
|
||||
| 名词 | 全称 | 简单解释 |
|
||||
|------|------|----------|
|
||||
| **URL** | Uniform Resource Locator | **统一资源定位符**。网页的"地址",告诉浏览器去哪里找资源。 |
|
||||
| **DNS** | Domain Name System | **域名系统**。互联网的"电话簿",把人类可读的域名转换成机器可读的 IP 地址。 |
|
||||
| **IP 地址** | Internet Protocol Address | **互联网协议地址**。每台联网设备的唯一"门牌号",如 `192.168.1.1`。 |
|
||||
| **TCP** | Transmission Control Protocol | **传输控制协议**。确保数据可靠传输的"规则",通过三次握手建立连接。 |
|
||||
| **HTTP** | HyperText Transfer Protocol | **超文本传输协议**。浏览器和服务器"对话"的规则。 |
|
||||
| **HTTPS** | HTTP Secure | **安全的 HTTP**。在 HTTP 基础上加了加密(TLS/SSL),保护数据安全。 |
|
||||
| **HTML** | HyperText Markup Language | **超文本标记语言**。网页的"骨架",定义内容的结构。 |
|
||||
| **CSS** | Cascading Style Sheets | **层叠样式表**。网页的"皮肤",定义内容的外观。 |
|
||||
| **DOM** | Document Object Model | **文档对象模型**。浏览器把 HTML 转换成的树形结构,方便操作。 |
|
||||
| **CSSOM** | CSS Object Model | **CSS 对象模型**。浏览器把 CSS 转换成的树形结构。 |
|
||||
| **渲染** | Rendering | 浏览器把代码转换成屏幕像素的过程。 |
|
||||
| **RTT** | Round Trip Time | **往返时间**。数据包从发送到接收确认的时间,影响网页加载速度。 |
|
||||
| 名词 | 全称 | 简单解释 |
|
||||
| ----------- | ----------------------------- | -------------------------------------------------------------------------- |
|
||||
| **URL** | Uniform Resource Locator | **统一资源定位符**。网页的"地址",告诉浏览器去哪里找资源。 |
|
||||
| **DNS** | Domain Name System | **域名系统**。互联网的"电话簿",把人类可读的域名转换成机器可读的 IP 地址。 |
|
||||
| **IP 地址** | Internet Protocol Address | **互联网协议地址**。每台联网设备的唯一"门牌号",如 `192.168.1.1`。 |
|
||||
| **TCP** | Transmission Control Protocol | **传输控制协议**。确保数据可靠传输的"规则",通过三次握手建立连接。 |
|
||||
| **HTTP** | HyperText Transfer Protocol | **超文本传输协议**。浏览器和服务器"对话"的规则。 |
|
||||
| **HTTPS** | HTTP Secure | **安全的 HTTP**。在 HTTP 基础上加了加密(TLS/SSL),保护数据安全。 |
|
||||
| **HTML** | HyperText Markup Language | **超文本标记语言**。网页的"骨架",定义内容的结构。 |
|
||||
| **CSS** | Cascading Style Sheets | **层叠样式表**。网页的"皮肤",定义内容的外观。 |
|
||||
| **DOM** | Document Object Model | **文档对象模型**。浏览器把 HTML 转换成的树形结构,方便操作。 |
|
||||
| **CSSOM** | CSS Object Model | **CSS 对象模型**。浏览器把 CSS 转换成的树形结构。 |
|
||||
| **渲染** | Rendering | 浏览器把代码转换成屏幕像素的过程。 |
|
||||
| **RTT** | Round Trip Time | **往返时间**。数据包从发送到接收确认的时间,影响网页加载速度。 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+707
-350
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user