从 0 到 100 万用户的扩展

单服务器配置

构建一个复杂系统,从简单的部分着手,先让所有的功能在一个服务器上运行,包括 web应用、数据库、缓存等。

用户(网页浏览器、移动应用)通过 DNS 查询 api.mysite.com 获得 IP 地址,客户端根据 IP 地址进行HTTP请求到 Web服务器,web服务器返回HTML页面以及API响应。

数据库

随着用户基数增长,一台服务器已经无法满足需求。需要多台服务器,一台用于 Web 应用的流量,另一台用作数据库,把处理Web应用流量的服务器与数据库(数据层)服务器分开。

用户<---IP地址------api.mysite.com--->DNS
用户访问 www.mysite.com api.mysite.com 到Web服务器
Web服务器 与 数据库服务器交互,读写更新 返回数据

使用何种数据库

可以选择传统的关系型数据库,也可以选择非关系型数据库。

关系型数据库历史悠久,方案成熟,但它们无法满足特殊使用场景时,就需要考虑关系型数据库之外的选项。当满足下面条件,非关系型数据库可能是一个正确的选择:

纵向扩展VS横向扩展

纵向扩展也叫向上扩展,提升服务器的能力 CPU、RAM等,横向扩展也叫向外扩展 指的是为你的资源池添加更多服务器。

纵向扩展有硬性限制,不可能给一台服务器无限添加CPU和内存,且没有故障转移和冗余,一旦一台服务器宕机,应用完全不可用。

大型应用来说,采用横向扩展更合适一些。

负载均衡器

负载均衡器把输入流量均匀分配到负载均衡集里的各个Web服务器上。

DNS(mywebsite.com IP:88.88.88.1) <---> 用户
用户<--公网IP地址88.88.88.1--> 负载均衡器
负载均衡器<--私有IP地址:10.0.0.1--> 服务器1
负载均衡器<--私有IP地址:10.0.0.2--> 服务器2

也就是加了一层网关。

如果服务器1离线,所有流量直接路由到服务器2,避免应用完全不可用,后面再加一台新的健康的服务器到服务器池中,平衡负载。

网站流量增常非常快,就往服务器池里加更多的服务器,负载均衡器就会自动将请求发给新加入的服务器。

数据库复制

目前设计方案中只有一个数据库,无法支持数据库的故障转移和冗余,数据库复制是解决这些问题的常用技巧。

在很多数据库管理系统中 , 通常都可以利用原始数据库 (Master, 主库)和拷贝数据库 (Slave, 从库)之 间 的主从关系进行数据库复制。

Web服务器写数据时写到主库,读数据时从 从库中读,多个从库一个主库,从库从主库进行数据库复制到自己本地。

数据库复制优点:

如果只有一个从库,宕机了,系统暂时将读操作路由到主库,主库宕机,会有一个从库代替原来的主库 生产环境中,从库的数据不一定是最新,推选一个新的主库会更麻烦,缺失的数据库需要通过运行数据恢复脚本来不全。

图1-6

缓存

缓存是临时的存储空间,用于存储一些很耗时的响应结果或者内存中经常被访问的数据,这样后续再访问这些数据时能更快。

缓存层

缓存层是一个 临时数据存储层,比数据库快很多 。设置独立缓存层的好处有:提高系 统归老,减轻数据库的工作负载以及能够单独扩展缓存层。

图1-7

这种缓存策略叫作通过缓存读 (Read-through Cache) 。缓存服务器一般大家都用 Redis、Memcached。

使用缓存时的注意事项

内容分发网络

内容分发网路 Content Delivery Network,CDN。

是由在地理上分散的服务器组成的网络,被用来传输静态内容。 CDN 中的服务器缓存了像图片、视频、 CSS 和 JavaScript 文件这一类的静态内容。当用户访问一个网站时,离用户最近的 CDN 服务器会返回静态资源 。

图1-10
  1. 用户A尝试通过请求图片的URL获取image.png,URL 的域名由 CDN 服 务商提供 。
  2. 如果 CDN 服务器的缓存中没有 image.png, CDN 服务器就会向数据源服务器请求 这个文件。数据源服务器可以是 Web 服务器,或者线上存储,比如 Amazon S3 。
  3. 数据源服务器将 image.png 文件返回给 CDN 服务器 ,其 中包括可选的 HTTP 头 Time-to-Live TTL, 生存时间。TTL 描述了该图片文件应该被缓存多长时间 。
  4. CDN 服务器缓存这个图片并将其返回给用 户 A 。这个图片 一直缓存在 CDN 服务器 中,直到 TTL 到期。
  5. 用户 B 发送请求,要求获取这张图片。只要 TTL 还没到期,CDN 服务器的缓存就会返回该图片。

使用CDN时的注意事项

  1. 花销,CDN 由第三方供应商来运营,对数据在CDN中的进出都会收费。尽量不要缓存不经常使用的内容。
  2. 设置合理的缓存过期时间,过长内容不够新、过短可能导致频繁将内容从数据源服务器重新加载至CDN。
  3. CDN 回退,网站或应用应有应对CDN故障的能力,CDN故障就向数据源服务器请求资源。
  4. 作废文件,通过CDN服务商提供的API来作废CDN对象,通过对象版本化提供一个不同版本的对象如可以在URL中添加一个参数 image.png?v=2
图1-11

有状态架构

用户 A 的会话数据和个人资料图片会被存储到服务器 1 上 。为了对用户 A 进行身份验证,必须将 HTTP 请求发给服务器1 。 如果将请求发给其他服务器,比如服务器 2, 由 千服务器 2 上没有用户 A 的会话数据,因此身份验证就会失败 。

常见的就是服务器中存了用户的session。

图1-12

无状态架构

状态架构中,用户的 HTTP 请求可以发给任意 Web 服务器,然后 Web 服务器从共享 的数据存储中拉取数据。状态数据存储在共享数据存储而非 Web 服务器中 。 无状态的系统更加简单,更健壮,也更容易扩展

常见的还有用JWT来解决的,省去服务器端的状态存储,用编解码时间换空间。

session 共享存储一般都用 Redis、Memcached。

图1-13

数据中心

两个数据中心例子,用户被基于地理位置的域名服务导流到最近的数据中心。

基于地理位置的域名服务(geoDNS)是一种基于用户的地理位置将域名解析为不同的IP地址的DNS服务。

图1-15

如果数据中心2挂掉了,则流量会全部导到 数据中心1。不同地区用户可以使用不同本地数据库或缓存,在故障转移的场景流量可能被转到一个数据不可用的数据中心,常用的策略是在多个数据中心复制数据。

消息队列

消息队列是一个持久化的 组件,存储在内存中,支持异步通信。它被用作缓冲区,分 配异步的请求。消息队列的基本架构很简单:输入服务(也称为生产者或发布者)创建消 息,并把它们发布到消息队列中;其他服务或者服务器(也称为消费者或订阅者)与消息 队列连接,并执行消息所定义的操作。

图1-18

记录日志、收集指标与自动化

记录日志,监控错误日志非常重要,你可以监控每个服务器的错误日志,也可以用工具把各个服务器的日志汇到一个中心化的服务中,方便搜索和查看。

收集指标,手机不同类型的指标数据,有助于获得商业洞察力和了解系统的健康状态。如 主机级别指标 CPU、内存、磁盘IO,聚合级指标 比如整个数据库层的性能,整个缓存层的性能等。关键业务指标,每日活跃用户数、留存率、收益等。

自动化工具,持续集成,每次代码检入 check in 都需要通过自动化工具审核,使团队能及时发现问题,将 构建、测试和部署等流程自动化。

添加消息队列和各种工具

下面系统包含一个消息队列,使系统更松散地耦合且更容易从故障中恢复。包含记录日志、监控和收集指标的功能,以及自动化工具。

图1-19

数据库扩展

随着数据与日俱增,数据库过载变得越来越严重,需要扩展数据层。

数据库的扩展有两种方式,纵向扩展和横向扩展。

数据库纵向扩展

向上扩展,为已有机器增加算力 CPU、内存、硬盘等。AWS的RDS(关系型数据库服务)可以提供拥有 的24TB内存的数据库服务器,可以存储和处理非常多的数据。

纵向扩展,硬件的能力总是有上限的,更大的单点故障风险,总成本高 强劲的服务器比一般的服务器贵很多。

数据库横向扩展

横向扩展,也叫切片,就是添加更多服务器。

数据库分片指把大数据库拆分成更小、更容易管理的部分(这些部分叫做Shard,分片),每个 Shard 共享同样的数据库 Schema,但里面的数据都是这个 Shard 独有的。

可以进行分表分库。

图1-21

分片远不是一个完美的解决方案,它为系统引入了复杂性和新的挑战。

重分片数据:可能会遇到,数据快速增长单个Shard无法存储更多数据,因为数据的分布不均有些Shard空间比其他更快耗尽,需要更新用于分片的哈希函数,然后把数据移到别的地方。

名人问题:也叫热点键问题,过多访问一个特定的Shard可能过载,如把特别出名人的数据放在同一个Shard,肯定就是不均匀了。

连接和去规范化,数据库通过分片到多个服务器上,很难跨数据库分片执行连接 join 操作,常用方法是对数据库去规范化,把数据冗余存储到多张表中,以便查询可以在一张表中执行。

对数据库做分片,以支持数据流量的快速增长,将有些非关系型功能迁移到 NoSQL 数据库中,以降低数据库的负载。

图1-23