本文翻译自https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/scaling_aws/README.md
因为原文有很多重复内容并且组织比较零散,所以译文调整了部分原文顺序,并整理了很多过于琐碎的内容。

第一步:用例和约束
在面试过程中我们应该向面试官询问和讨论用例和约束。但是本文由于没有面试官可以讨论,我们会定义一些用例和约束。

1. 用例
本题中我们将处理下面的用例:
1)用户发送读或者写请求
2)服务要处理并存储用户数据,然后返回结果
3)服务需要从一个少量用户的系统升级至百万用户
4)讨论当一个系统从少量用户升级至百万用户的过程中的一般扩展模式
5)服务有高可用性
解决用例3,4和5需要不断运行Benchmark/负载测试并且分析瓶颈。

2. 约束和假设
这部分内容大部分省略,太琐碎。

第二步:总体设计
总体设计包含了所有必要的组件。


第三步:设计核心组件

假设我们只需要服务1-2个用户,那么我们就只需要一台服务器,这台服务器上安装了数据库和web服务器。当我们需要升级的时候只需要做垂直升级就行了。另外,如果我们的数据是关系型数据,那么可以选用MySQL数据库。防火墙只打开必要的端口:80,443和22。

第四步 扩展我们的设计
1.Users+
1)假设
我们的用户数量在上升并且服务器的负载也在增加。我们的测试和分析结果显示,MySQL数据库占用了大量的内存和CPU资源,而用户内容正在不断占用磁盘空间。
到目前为止,我们一直采用垂直升级。不幸的是,这种升级方式比较昂贵并且不允许MySQL数据库和Web服务器单独扩展。
2)目标

我们的目标是减轻服务器的负载并且允许独立扩展。为此我们可以把静态内容单独存储到一个面向对象存储服务器上,并且我们可以把MySQL数据库移动到一台单独的服务器上。但这么做的缺点是,系统的复杂度上升了,并且我们需要额外的安全措施来确保新增服务器的安全。
3)把静态内容分开存放
我们可以考虑使用面向对象存储例如S3来保存静态内容。S3的扩展性和可靠性都很高,并且服务器端是加密的。User
files、JS、CSS、Images和Videos都可以移动到S3。
4)把MySQL数据库移动到一台单独的服务器上
5)系统的网络安全

我们需要加密传输的数据,并且我们可以考虑使用虚拟私有云。我们可以为Web服务器分配公有网址,这样它可以从internet发送和接收数据;我们要为其他的服务器分配私有网址,他们不访问外网;对于每台服务器我们只开放列在白名单的IP的端口。



2.Users++
1)假设
我们的测试结果显示网站的性能瓶颈在我们的唯一的一台web服务器上。在峰值期间这台服务器的响应会很慢,并且有时候会无响应。因此我们需要更高的可用性和冗余。
2)目标
我们可能只需要以下的一种或者几种方法来解决假设中的问题。
a.我们可以使用水平扩展来处理不断增加的系统负载和解决单点故障问题。

我们可以增加负载均衡器(例如ELB)。ELB的可用性很高;如果你配置自己的负载均衡器的话,你可以把它们配置为active-active或者active-passive类型;我们可以让负载均衡器来处理SSL认证,这样可以降低后端服务器的负载并简化认证管理。
我们可以让web服务器群分布于多个地区以提高系统冗余性。
我们可以让MySQL服务器群分布于多个地区以提高系统冗余性。
b.我们可以把应用服务器从web服务器中分离出来;
我们可以单独地配置web服务器群和应用服务器群;web服务器群和应用服务器群可以有各自的负载均衡器;我们可以把应用服务器分为Read
API服务器和Write API服务器。
c.我们可以把静态内容(和部分动态内容)移动到CDN上以减少系统负载和延迟。



3.Users+++
1)假设
我们的测试数据显示,我们的数据库由于大量的读请求而性能糟糕。
2)目标
我们可能只需要以下的一种或者几种方法来解决假设中的问题。
a.把以下数据移动到缓存服务器(例如Elasticache)中以降低负载和延迟:
一是访问频繁的MySQL数据。不过,请首先尝试打开MySQL的缓存;如果不能解决性能瓶颈的话,再考虑缓存服务器。
二是Web服务器的会话数据。这样web服务器就变成无状态的,从而可以自动扩展。
b.添加“MySQL读服务器(MySQL Read Replicas)”以降低“写服务器(write master)”的负载;
c.添加更多的web服务器和应用服务器以加快响应。
3)添加“MySQL读服务器(MySQL Read Replicas)”
a.“MySQL读服务器(MySQL Read Replicas)”可以帮助降低“写服务器(write master)”的负载;
b.我们需要在Web服务器上区分“读”和“写”的逻辑;
c.为“MySQL读服务器(MySQL Read Replicas)”服务器集群添加负载均衡器;
d.大多数的服务是“读”负载大于“写”负载的。



4.Users++++
1)假设
我们的测试数据显示,我们的网站流量在工作时间会急剧上升,但是下班后会急剧下降。所以,我们可以根据实际负载来自动启动或者关闭服务器。
2)添加自动扩展
a.为web服务器和应用服务器分别创建服务器集群,集群中的服务器应该分布于多个不同的区域,这样可以提高可用性。
b.设定集群服务器的上限和下限;
c.监控各台服务器的cpu负载、延时以及网络流量等日志以出发服务器的自动开启和关闭;
d.自动扩展的缺点是增加了系统的复杂性;另外,开启或者关闭服务器会有一定的延迟。


5.Users+++++
当用户进一步增加的时候,我们需要通过测试和性能分析找到瓶颈,而非盲目的优化。

a.如果我们的MySQL数据库持续增长,那么我们可以考虑只在数据库中保存最近一段时期的数据,而把其他时间的数据保存在数据仓库中(例如:Redshift)。诸如Redshift之类的数据仓库可以轻松处理每个月新增1TB的数据的情况。

b.我们可以通过扩展内存缓存服务器来处理平均每秒40000个读请求的负载。另外,该方法也能很好地处理非均匀分布的流量和流量峰值问题。注意:SQL“读服务器(Read
Replicas)”可能会有缓存没有命中的情况,我们可能需要额外的SQL扩展方法来解决该问题。
c.当平均每秒有大于等于400写操作的时候,只有单台写服务器的SQL Master-Slave可能会无法应付,这时候我们需要额外的扩展方法。
d.我们也可以考虑把数据移动到NoSql数据库上以进一步解决大量的读写请求。

额外的数据库扩展方法包括:
a.Federation
b.分片
c.反范式
d.SQL调优


我们可以进一步划分应用服务器以更好地独立扩展。不需要实时完成的请求或者操作可以用Queues和Workers完成。例如,在照片服务中,照片上传和缩略图生成的功能可以被分开:
a.用户上传照片
b.应用服务器将一个job放入Queue
c.某台服务器上的Worker会把job从Queue中取出,然后创建缩略图,更新数据库,将缩略图保存入面向对象存储。