搞定系统设计 01:从 0 到百万用户的系统

2021-11-12 18:38:15 浏览数 (1)

如何从零开始设计一套可以服务百万用户的系统。

这是本书第一章的内容,浅显易懂,把常见的套路组合了一下,没有具体的技术细节,过一遍也没什么负担。

从单服务开始

俗话说:千里之行,始于足下。构建一个复杂系统也不例外,我们从单服务开始。

single server

画出整体的大框图,流程很简单。客户端先请求 DNS 拿到服务端的 IP 地址;客户端拿着 IP 地址请求服务端的接口;服务端返回 HTML;客户端渲染出页面。

这种是最简单的系统。但有个问题是:数据无法持久化。只能把状态存在内存中,一旦进程重启,数据就都丢失了。

数据库

因此,一般的服务都会加一个数据库:

add a database

具体是选择关系型数据库还是非关系型数据库,这里有一些需要考虑的点:

关系型数据库最出名的当属 MySQL,Oracle,PgSQL,互联网公司用得最多的还是 MySQL。它将数据存储在一张表的一行,可以在不同的表之间执行 join 操作。

非关系型数据库也叫 NoSQL,它有四种类型:k-v图数据库列存文档型

更多的时候当然还是选择老牌的关系型数据库,但有些场景还是更适合 NoSQL,尤其是在互联网蓬勃发展的今天。

下面这几个场景更适合使用 NoSQL:

  • 服务需要超低延迟。
  • 服务的数据是非结构化的。
  • 只需要序列化反序列化数据(Json/XML/YAML)
  • 需要存储大量数据。

负载均衡

现在很少有网站直接用 IP 地址 端口的地址访问,前面至少得挂个 Nginx。

后端服务也不能是单个 server,至少得挂个 2 台。当单个服务意外挂了,另一个服务得马上顶上,防止单点故障。

服务之间则是通过私有 IP 通信。

客户端通过 DNS 拿到的 IP 不再是服务器的 IP,而是前面的负载均衡器的 IP。请求到来之后,由负载均衡器将请求打到不同的 server 上。这一切对用户是透明的。

add lb

加上了负载均衡器后,服务层的可用性解决了。但是数据库层呢,我们还只有一个数据库服务器,一旦它挂了,服务同样是不可用的。

解决办法就是让数据库有更多的副本。

数据库副本

互联网绝大多数的场景是多读写少,可以采用经典的主从式架构模式,一般都会设置成一主多从。

所有的写操作,包括插入、删除、更新操作由主执行;而所有的读操作都由从执行。

database replication

采用主从架构的好处很多:

  • 性能更强。读操作被分散到多个从,每个从的压力会更小,性能也更高。
  • 可靠性更高。数据被分散保存,一个从挂了,数据不会丢,可以通过其他从恢复。
  • 可用性更强。一个从挂了,有其他从顶上。

缓存

增加一个缓存层也是常规的做法。

缓存在内存中存储计算好的结果,等下次请求到来时,直接使用内存中的数据,系统响应时间会更短。

关于缓存,有这么几点需要考虑:

  • 何时使用缓存。读多写少的场景更适合用缓存,例如城市 id 和名称的对应关系,基本是不会变的,放在缓存中再合适不过。
  • 过期策略。过期后,数据将被移出缓存。过期时间既不能太长也不能太短:太长数据会不“新鲜”,太短则需要频繁更新数据。
  • 一致性。数据发生变更时,数据库里的更改和缓存的更改并不在一个事务中,存在不一致的风险。
  • 处理单点失效。由多个 server 组成集群,在多个数据中心部署缓存都是可选的方法。
  • 逐出策略。一旦 cache 满了,再添加新的缓存项时就需要逐出缓存中已有的缓存项。逐出策略如 LRU、LFU、FIFO 分别在不同的场景下使用。

CDN

CDN 全称是 Content Delivery Network,内容分发网络。它是基于地理位置的服务,用来缓存静态内容,如图片、视频、CSS、JavaScript 文件等等。

下面这张图展示了 CDN 的工作流程,非常清晰:

cdn-workflow

使用 CDN 需要考虑的点有:

  • 费用。CDN 一般由第三方提供,根据数据流入、流出量收取费用。因此对于不常访问的数据应及时从 CDN 中移出。
  • 设置过期时间。太长导致数据不“新鲜”,太短又需要频繁回源读取数据。
  • CDN 失效时如何处理。当 CDN 出问题时,客户端应该有能力直接从源服务器读取数据。
  • 踢出文件。通过 CDN 厂商提供的 API 手动踢出或者通过在 URL 中增加版本号的方式实现。

当我们增加了 CDN 之后,系统架构图如下:

add cdn

无状态层

Web 层的水平扩展依赖无状态的设计:将状态(例如用户的 session)保存在外存,一般用 NoSQL。

当 Web 层无状态化后,流量上升就加机器,流量下降就减机器,扩展更容易。

auto scale

数据中心

业务做起来后,用户会越来越多。为了更好的用户体验,需要建立多个数据中心。根据用户的地理位置决定由哪个数据中心提供服务。

当某个数据中心宕机了,流量自动或手动切到其他数据中心。

multi datacenter

消息队列

消息队列让生产者和消费者解耦,对于构建可伸缩的服务架构是非常有用的。

生产者向消息队列发送消息,它不需要关心消费者是否在可用。消费者消费消息,它也不需要关心生产者是否可用。

日志、打点、自动化

对错误日志量的监控常常能快速发现和定位问题。

对业务信息、机器状态、进程状态进行打点和监控也是必不可少的。

当整个系统越来越大,我们还需要通过自动化工具来提升开发效率,CICD 就得搬到台前。

add tools

数据库伸缩

服务在线上正常运转,数据会越来越多。单机总有一天不能装下所有的数据,对数据库的扩展需求提上日程。

同样有两种扩展方式。

最简单的就是垂直扩展。换更大容量、更多核心,更大内存的机器,只要足够有钱,这都不是事。开始阶段,加机器的效率甚至更高。

更难的是水平扩展。说白了就是分 shard,将全量数据拆分到不同的机器上去。

分 shard 最关键的就是 key 的选取,它要能保证数据的平均分布。

数据分片并不容易,需要考虑下面这几点:

  • resharding。当某个 shard 也不能完全存放数据时,就需要重新再分 shard。reshard 最常用的方法是一致性哈希。
  • 名人问题。也称为热点 key 问题,需要特殊解决。
  • join。一旦分片,数据库被分散到不同的机器上,不再能执行 join 操作,通常的解法是将字段冗余到同一个表上。

最后是一张考虑了以上所有的点之后的架构图:

overview

点评

第一章感觉说了很多,又感觉什么都没说。整体看内容还是比较浅显的,不少都是点到即止。

不管怎么说,我们都已经上路了。

0 人点赞