|
| 1 | +## 第一步 :弄清用例与约束 |
| 2 | + |
| 3 | +> 收集需求和问题的范围 |
| 4 | +> 通过问问题来弄清用例与约束 |
| 5 | +> 讨论假设 |
| 6 | +
|
| 7 | +我们假定以下用例 |
| 8 | + |
| 9 | +### 用例 |
| 10 | +解决这个问题需要采用迭代的方法: |
| 11 | + |
| 12 | +1. 基准/负载测试 |
| 13 | +2. 瓶颈检测 |
| 14 | +3. 评估替代方案来解决瓶颈 |
| 15 | +4. 重复以上 |
| 16 | + |
| 17 | +这是将基本设计升级为可扩展设计的良好模式 |
| 18 | + |
| 19 | +除非你有AWS的背景或者正在申请AWS的相关职位,否则在AWS上的实现细节不需要了解。然而**大部分在这里讨论的原理可以应用到除了AWS以外更通用的地方** |
| 20 | + |
| 21 | +#### 我们将问题约束到如下范围 |
| 22 | +* **用户**发送读或写请求 |
| 23 | + * **服务**处理,存储用户数据然后返回结果 |
| 24 | +* **服务**需要从少量用户发展到数百万用户 |
| 25 | + * 在我们升级架构来处理大量用户请求时,讨论通用的扩展模式 |
| 26 | +* **服务**需要高可用 |
| 27 | + |
| 28 | +### 约束和假设 |
| 29 | + |
| 30 | +#### 状态假设 |
| 31 | + |
| 32 | +* 流量分布不均 |
| 33 | +* 需要关系型数据 |
| 34 | +* 从单个用户扩展到千万级用户 |
| 35 | + * 用户增加的标识: |
| 36 | + * 用户数+ |
| 37 | + * 用户数++ |
| 38 | + * 用户数+++ |
| 39 | + * ... |
| 40 | + * 一千万用户 |
| 41 | + * 每月10亿次写入 |
| 42 | + * 每月1000亿次读取 |
| 43 | + * 100:1读写比 |
| 44 | + * 每次写入1KB内容 |
| 45 | + |
| 46 | +#### 计算方式 |
| 47 | + |
| 48 | +**如果你想做一个大致估算,请向你的面试官表明以下数据:** |
| 49 | + |
| 50 | +* 每月1TB数据写入 |
| 51 | + * 每次写入1KB数据 * 每月10亿次写入 |
| 52 | + * 3年有3TB数据写入 |
| 53 | + * 假设大多数写入是新的内容而不是已有内容的更新 |
| 54 | +* 平均每秒400次写入 |
| 55 | +* 平均每秒40000次读取 |
| 56 | + |
| 57 | +方便的转换公式: |
| 58 | + |
| 59 | +* 每月有250万秒 |
| 60 | +* 每秒一个请求 = 每月250万个请求 |
| 61 | +* 每秒40个请求 = 每月1亿个请求 |
| 62 | +* 每秒400个请求 = 每月10亿个请求 |
| 63 | + |
| 64 | +## 第二步:创建高层设计 |
| 65 | + |
| 66 | +> 大致写出包含所有重要组件的高层设计 |
| 67 | +
|
| 68 | + |
| 69 | + |
| 70 | +## 第三步:设计核心组件 |
| 71 | + |
| 72 | +> 深入每个核心组件的细节 |
| 73 | +
|
| 74 | +### 用例:用户发送读或写的请求 |
| 75 | + |
| 76 | +#### 目标 |
| 77 | + |
| 78 | +* 对于仅仅的1-2个用户,你只需要一个基本的配置 |
| 79 | + * 简单的单体应用 |
| 80 | + * 当需要的时候垂直缩放 |
| 81 | + * 监控来确定瓶颈 |
| 82 | + |
| 83 | +#### 从单体应用开始 |
| 84 | + |
| 85 | +* EC2上的**服务器** |
| 86 | + * 存储用户数据 |
| 87 | + * [**MySQL数据库**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9Frdbms) |
| 88 | + |
| 89 | +使用**垂直扩展**: |
| 90 | + |
| 91 | +* 选择更好性能的机器 |
| 92 | +* 密切关注监控指标以确定如何扩大规模 |
| 93 | + * 使用基本监控来确定瓶颈:CPU,内存,IO,网络等 |
| 94 | + * CloudWatch, top, nagios, statsd, graphite等 |
| 95 | +* 垂直缩放可能会很昂贵 |
| 96 | +* 没有故障转移措施 |
| 97 | + |
| 98 | +*替代方案和其他细节:* |
| 99 | + |
| 100 | +* **垂直扩展**的替代是[**水平扩展**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E6%B0%B4%E5%B9%B3%E6%89%A9%E5%B1%95) |
| 101 | + |
| 102 | +#### 从SQL开始,考虑NoSQL |
| 103 | + |
| 104 | +约束里我们需要关系型数据。我们在开始的时候可以在单机上用**MySQL数据库**. |
| 105 | + |
| 106 | +*替代方案和其他细节:* |
| 107 | + |
| 108 | +* [关系型数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9Frdbms) |
| 109 | +* 使用[SQL还是NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-%E8%BF%98%E6%98%AF-nosql)的原因 |
| 110 | + |
| 111 | +#### 分配公网静态IP |
| 112 | + |
| 113 | +* 弹性IP提供一个重启之后不会更改的公网端口 |
| 114 | +* 有效的帮助故障转移,只需要将域名指向新IP |
| 115 | + |
| 116 | +#### 使用DNS |
| 117 | + |
| 118 | +使用Route 53添加**DNS**将域名映射到实例的公共IP |
| 119 | + |
| 120 | +*替代方案和其他细节:* |
| 121 | + |
| 122 | +* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%9F%9F%E5%90%8D%E7%B3%BB%E7%BB%9F) |
| 123 | + |
| 124 | +#### 保护web服务器 |
| 125 | + |
| 126 | +* 开启必要的端口 |
| 127 | + * 允许web服务器对于以下端口回复: |
| 128 | + * 80 - HTTP |
| 129 | + * 443 - HTTPS |
| 130 | + * 22 - SSH(白名单) |
| 131 | + * 阻止web服务器进行出站连接 |
| 132 | + |
| 133 | +*替代方案和其他细节:* |
| 134 | + |
| 135 | +* [安全](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%AE%89%E5%85%A8) |
| 136 | + |
| 137 | +## 第四步:扩展设计 |
| 138 | + |
| 139 | +> 鉴于约束条件,确定并解决瓶颈 |
| 140 | +
|
| 141 | +### 用户数+ |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | +#### 假设 |
| 146 | + |
| 147 | +我们的用户数正在增加并且在我们单体应用上的负载也在增加。我们的**基准/负载测试**和**瓶颈**指向了**MySQL数据库**占用更多内存和CPU资源,同时用户内容正在填满磁盘空间 |
| 148 | + |
| 149 | +到目前为止我们可以通过**水平扩展**解决问题。但不幸的是已经变得非常昂贵并且**MySQL数据库**和**web服务器**无法独立扩展 |
| 150 | + |
| 151 | +#### 目标 |
| 152 | + |
| 153 | +* 减轻单体应用的负载并且允许独立扩展 |
| 154 | + * 将静态内容分开存储到**AWS对象存储** |
| 155 | + * 移动**MySQL数据库**到独立的服务上 |
| 156 | +* 缺点 |
| 157 | + * 这些改变将增加复杂度并且需要**Web服务器**指向**对象存储**和**MySQL数据库** |
| 158 | + * 新组件额外的安全措施 |
| 159 | + * AWS的费用将会增加但应该与自己管理类似系统成本进行权衡 |
| 160 | + |
| 161 | +#### 分离存储静态内容 |
| 162 | + |
| 163 | +* 考虑使用S3作为**对象存储** |
| 164 | + * 高扩展和可靠性 |
| 165 | + * 服务端加密 |
| 166 | +* 移动静态内容到S3 |
| 167 | + * 用户文件 |
| 168 | + * JS |
| 169 | + * CSS |
| 170 | + * 图片 |
| 171 | + * 视频 |
| 172 | + |
| 173 | +#### 移动MySQL数据库到独立的服务 |
| 174 | + |
| 175 | +* 考虑使用RDS服务管理**MySQL数据库** |
| 176 | + * 扩展和管理简单 |
| 177 | + * 多个可用区 |
| 178 | + * 静态加密 |
| 179 | + |
| 180 | +#### 保护系统 |
| 181 | + |
| 182 | +* 在传输和静止时加密数据 |
| 183 | +* 使用虚拟私有网络 |
| 184 | + * 为单个**Web服务器**创建公共子网以便可以发送和接收网上的流量 |
| 185 | + * 为其他组件创建私有网络,组织外部访问 |
| 186 | + * 每个组件仅仅对白名单IP开放端口 |
| 187 | + |
| 188 | +### 用户数++ |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +#### 假设 |
| 193 | + |
| 194 | +我们的**基准/负载测试**和**瓶颈检测**表明我们的单体**Web服务器**在高峰期出现瓶颈,导致回应慢,在某些情况下宕机。随着服务的成熟,我们希望提高可用性和冗余度 |
| 195 | + |
| 196 | +#### 目的 |
| 197 | + |
| 198 | +* 以下目标尝试解决**Web服务器**的扩展问题 |
| 199 | + * 基于**基准/负载测试**和**瓶颈检测**,你可能只需要实现这些技术中的一个或者两个 |
| 200 | +* 使用[**水平扩展**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E6%B0%B4%E5%B9%B3%E6%89%A9%E5%B1%95)处理不断增加的负载并解决单体故障 |
| 201 | + * 添加[**负载均衡器**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%99%A8) |
| 202 | + * ELB是高可用的 |
| 203 | + * 如果你想配置自己的**负载均衡器**, 在多个可用区配置[主-主](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%8F%8C%E5%B7%A5%E4%BD%9C%E5%88%87%E6%8D%A2active-active)或[主-备](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%B7%A5%E4%BD%9C%E5%88%B0%E5%A4%87%E7%94%A8%E5%88%87%E6%8D%A2active-passive)可以提高可用性 |
| 204 | + * 在**负载均衡器**上关闭SSL去减少在后端服务器上的计算负载并简化证书管理 |
| 205 | + * 使用多个**Web服务器**分布到多个区域 |
| 206 | + * 使用多个[**主从故障切换**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6)模式的**MySQL**实例来增进冗余度 |
| 207 | +* 将**Web服务器**和[**应用服务器**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%BA%94%E7%94%A8%E5%B1%82)分开 |
| 208 | + * 独立扩展和配置这两层 |
| 209 | + * **Web服务器**可以作为[**反向代理服务器**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86web-%E6%9C%8D%E5%8A%A1%E5%99%A8) |
| 210 | + * 比如你可以添加**应用服务器**处理**读API**而其他**应用服务器**处理**写API** |
| 211 | +* 移动静态(和一些动态)内容到[**CDN**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%86%85%E5%AE%B9%E5%88%86%E5%8F%91%E7%BD%91%E7%BB%9Ccdn)比如CloudFount去减少负载和延迟 |
| 212 | + |
| 213 | +### Users+++ |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +**注意:** 为了避免过于混乱,没有显示**内部负载均衡器** |
| 218 | + |
| 219 | +#### 假设 |
| 220 | + |
| 221 | +我们的**基准/负载测试**和**瓶颈检测**表明我们的读请求很多(100:1读写比),我们的数据库因为大量读取请求导致性能不佳 |
| 222 | + |
| 223 | +#### 目标 |
| 224 | + |
| 225 | +* 以下目标尝试去解决在**MySQL数据库**上的问题 |
| 226 | + * 基于**基准/负载测试**和**瓶颈检测**,你可能只需要实现这些技术中的一个或者两个 |
| 227 | +* 移动以下数据到[**内存缓存**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E7%BC%93%E5%AD%98),比如Elasticache去减少负载和延迟: |
| 228 | + * 在**MySQL**中经常读取的内容 |
| 229 | + * 首先,在实现**内存缓存**之前试图配置**MySQL数据库**的缓存看是否足以解决瓶颈 |
| 230 | + * 来自**Web服务器**的session数据 |
| 231 | + * **Web服务器**变成无状态服务,允许**自动缩放** |
| 232 | + * 从内存读取1MB需要250微秒,而SSD需要4倍的时间,从硬盘读取需要80倍时间 |
| 233 | +* 添加[**MySQL只读副本**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6)来减少主服务器的负载 |
| 234 | +* 添加更多**Web服务器**和**应用服务器**来提升响应 |
| 235 | + |
| 236 | +#### 添加MySQL只读副本 |
| 237 | + |
| 238 | +* 除了增加和扩展**内存缓存**外, **MySQL只读副本**也能帮助减轻**MySQL主节点**的负载 |
| 239 | +* 添加**Web服务器**的逻辑来分开读写数据 |
| 240 | +* 在**MySQL只读副本**前添加**负载均衡器**(图里没画) |
| 241 | + |
| 242 | +### 用户数++++ |
| 243 | + |
| 244 | + |
| 245 | + |
| 246 | +#### 假设 |
| 247 | + |
| 248 | +我们的**基准/负载测试**和**瓶颈检测**表明在正常工作时间内流量激增,在用户离开办公室时显著下降。我们认为我们可以根据实际负载自动调整服务器来降低成本。我们是个小公司,因此我们希望尽可能多地**自动缩放** |
| 249 | + |
| 250 | +#### 目标 |
| 251 | + |
| 252 | +* 添加**自动缩放**来根据需求提供实例数量 |
| 253 | + * 跟上流量的高峰 |
| 254 | + * 通过关闭未使用的实例来减少费用 |
| 255 | +* DevOps自动化 |
| 256 | + * Chef, Puppet, Ansible等 |
| 257 | +* 继续监控指标以解决瓶颈 |
| 258 | + * **主机级别** - 查看单个EC2实例 |
| 259 | + * **汇总级别** - 查看负载均衡器统计信息 |
| 260 | + * **日志分析** - CloudWatch, CloudTrail, Loggly, Splunk, Sumo |
| 261 | + * **外部网站性能** - Pingdom或New Relic |
| 262 | + * **处理通知和时间** - PagerDuty |
| 263 | + * **错误报告** - Sentry |
| 264 | + |
| 265 | +#### 添加自动缩放 |
| 266 | + |
| 267 | +* 考虑AWS的托管服务**自动缩放** |
| 268 | + * 为每个**Web服务器**和**应用服务器**创建一个组, 每个组放到多个可用区中 |
| 269 | + * 设置最小和最大实例数 |
| 270 | + * 通过CloudWatch触发向上和向下扩展 |
| 271 | + * 一段时间内的指标: |
| 272 | + * CPU负载 |
| 273 | + * 延迟 |
| 274 | + * 网络流量 |
| 275 | + * 自定义指标 |
| 276 | + * 缺点 |
| 277 | + * 自动缩放可能会带来复杂性 |
| 278 | + * 系统可能需要一段时间才能适当扩展以满足不断增长的需求,或者在需求下降时缩小规模 |
| 279 | + |
| 280 | +### Users+++++ |
| 281 | + |
| 282 | + |
| 283 | + |
| 284 | +**注意:** **自动缩放**组未在图中显示 |
| 285 | + |
| 286 | +#### 假设 |
| 287 | + |
| 288 | +随着服务继续朝着约束中的数字增长, **基准/负载测试**和**瓶颈检测**继续迭代来发现和解决新的瓶颈 |
| 289 | + |
| 290 | +#### 目标 |
| 291 | + |
| 292 | +由于问题的限制,我们将继续解决扩展问题: |
| 293 | + |
| 294 | +* 如果我们的**MySQL数据库**开始变得非常大,我们可能会考虑只将有限时间段的数据存储在数据库中,同时将其余数据存储在Redshift等数据仓库中 |
| 295 | + * 像Redshift这样的数据仓库可以轻松处理每月1TB的新内容 |
| 296 | +* 每秒平均读取请求4万次,读取常用数据的流量可以通过扩展**内存缓存**来解决,这对于处理不均匀分布的流量和流量峰值也很有用 |
| 297 | + * **SQL只读副本**可能在处理缓存未命中时遇到问题,我们可能需要采用其他SQL扩展模式 |
| 298 | +* 对于单个**SQL写服务**来说,每秒400次平均写入次数(可能更高的峰值)可能很难,同时也表明需要额外的缩放技术 |
| 299 | + |
| 300 | +SQL扩展模式包括: |
| 301 | + |
| 302 | +* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E8%81%94%E5%90%88) |
| 303 | +* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%88%86%E7%89%87) |
| 304 | +* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E9%9D%9E%E8%A7%84%E8%8C%83%E5%8C%96) |
| 305 | +* [SQL调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-%E8%B0%83%E4%BC%98) |
| 306 | + |
| 307 | +为了进一步解决高读取和写入请求,我们还应考虑将适当的数据移动到[**NoSQL数据库**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#nosql),例如DynamoDB |
| 308 | + |
| 309 | +我们可以进一步分离[**应用服务器**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%BA%94%E7%94%A8%E5%B1%82)来允许独立的缩放。不需要实时完成的批处理和计算可以使用**队列**和**工作程序**[**异步**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%BC%82%E6%AD%A5)完成: |
| 310 | + |
| 311 | +* 例如,在照片服务中,照片上传和缩略图创建可以分开: |
| 312 | + * **客户端**上传图片 |
| 313 | + * **应用程序服务器**放一个任务到**队列** |
| 314 | + * **工作服务**从**队列**中拉取到任务: |
| 315 | + * 创建缩略图 |
| 316 | + * 上传到**数据库** |
| 317 | + * 存储缩略图到**对象存储** |
0 commit comments