原文地址
要设计一个支持数亿用户的系统并不容易。对于软件架构师来说,这是一个很大的挑战(不过今天读完这篇文章之后,就会变得容易了🤣)
下面是我在本文中讨论的一些主题。
- 从简单开始:一体机(all in one)
- 扩展的艺术:横向扩展和纵向扩展
- 扩展关系数据库:主-从复制、主-主复制、联合、分片、去范式化和SQL调优。
- 选择哪种数据库:NoSQL还是SQL?
- 高级概念:缓存、CDN、geoDNS等。
今天,我不想讨论高性能计算中的一般术语,比如容错、可靠性、高可用性等。
保持淡定,我们现在开始!
从零开始
在下面的图中,我从设计一个只有少量用户的基础应用程序开始。最简单的方法是将整个应用程序部署在一台服务器上。这可能是我们大多数人开始的方式。
- 一个网站(包括api)运行在像Apache¹(或Tomcat²)这样的web服务器上。
-
一个类似Oracle或MySQL的数据库
但是当前的架构有以下缺点。
- 如果数据库故障,则整个系统就故障。
- 如果webserver故障,则整个系统也故障。
在这种情况下,我们没有故障转移和冗余。如果一个服务器坏了,所有的服务都坏了。
使用DNS服务器解析主机名和IP地址
在上图中,用户(或客户端)连接到DNS系统就可以获得我们系统所在服务器的IP地址。一旦获得了IP地址,请求就会直接发送到我们的系统。
每次你访问一个网站,你的计算机都会执行DNS查找。
通常,域名系统(DNS)服务器是由托管公司提供的付费服务,而不是运行在你自己的服务器上。
扩展系统的艺术
由于数据量增加、工作量的增加(例如事务数量)和用户增长等原因,我们的系统可能必须进行扩展。
可伸缩性通常意味着通过添加更多的资源能够处理更多的用户、客户端、数据、事务或请求,而不影响用户体验。
我们必须决定如何扩展系统。在这种情况下,有以下两种类型的扩展:纵向扩展(scale up)和横向扩展(scale out)。
纵向扩展:向现有服务器添加更多内存和CPU
这也称为“垂直扩展”,它指的是系统的资源最大化,以扩展其处理不断增加的负载的能力——例如,我们通过添加RAM和CPU来增加服务器的处理能力。
如果我们使用的服务器是8GB内存,只需更换或添加硬件就能容易地升级到32GB甚至128GB。
有许多垂直扩展的方法,如下所示:
- 通过在RAID中添加更多的硬盘来增加I/O容量。
- 通过切换到固态硬盘(ssd)来提高I/O访问时间。
- 切换到具有更多处理器的服务器。
- 通过升级网络接口或安装额外的网络接口来提高网络吞吐量。
- 通过增加RAM来减少I/O操作。
垂直扩展对于小型系统来说是一个很好的选择,并且能够负担得起硬件升级,但是它也有以下一些严重的缺陷: - “不可能给一台服务器无限的扩展处理能力”。它主要取决于操作系统和服务器的内存总线宽度。
- 当我们给系统升级内存时,我们必须关闭服务器,因此,如果系统只有一个服务器,停机是不可避免的。
- 高性能服务器通常比普通的硬件贵很多。
扩展不仅适用于硬件,也适用于软件,例如,可以优化查询和应用程序代码。
你需要更多的服务器吗?
随着用户数量的增长,一台服务器是远远不够的。我们需要考虑将一台服务器分离到多台服务器。
使用这个体系结构,有以下一些优点:
- web服务器可以与数据库服务器进行不同的调优。
- web服务器需要更好的CPU,而数据库服务器需要更多的内存。
- web层和数据层使用独立的服务器可以实现各自单独的扩展。
横向扩展:添加任意数量的硬件和软件实体
它也被称为“水平扩展”,我们向资源池中添加更多实体(包括机器、服务)。水平扩展比垂直扩展更难实现,因为我们需要在系统构建之前考虑它。
水平扩展通常一开始成本更高,因为我们需要更多的服务器来实现最基本的功能,但是在后期会有所回报。我们需要做一些权衡。
- 增加服务器的数量意味着需要维护更多的资源。
- 系统的代码也需要修改,以允许并行和在多个服务器之间分发任务。
使用负载均衡器来平衡所有节点的流量
负载均衡器是一种专门的硬件或软件组件,它有助于将流量分散到服务器集群,以提高系统(包括但不限于应用程序、网站或数据库)的响应和可用性。
通常,负载均衡器位于客户端和服务器之间,接受传入的网络和应用程序流量,并使用各种算法将流量分布到多个后端服务器。所以,它也可以用在很多地方,例如;Web服务器和数据库服务器之间,以及客户端和Web服务器之间。
HAProxy和Nginx是两款流行的开源负载均衡软件。
负载均衡技术是一种容错保证方法,可以提高服务可用性,如下所示:
- 如果服务器1宕机,所有流量将被路由到服务器2和服务器3。网站不会下线。您还需要向集群种添加一个新的正常运行的服务器,以平衡负载。
- 当流量快速增长时,你只需要向web服务器中添加更多的服务器,负载均衡器就会为你路由流量。
负载均衡器采用各种策略和工作分配算法来优化负载分配,如下所示:
- 轮询:在这种情况下,每个服务器按照顺序接收请求,类似于先进先出(FIFO)。
- 最少连接数:流量将被路由到连接数最少的服务器。
- 响应最快:具有最快响应时间的服务器将被选择发送请求。
- 加权:处理能力强的服务器将接收更多的请求,而较弱的服务器将接受更少的请求。
-
IP哈希:在这种情况下,计算客户端IP地址的哈希值,将请求重定向到对应的服务器。
在多个服务器之间平衡请求的最简单方法是使用硬件设备负载均衡。 - 瞬间从共享IP中添加和删除实服务器。
- 可以根据需要进行负载均衡。
软件负载平衡是硬件负载平衡器的廉价替代品。它可工作在4层(网络层)和7层(应用层)。 - 4层负载均衡器:负载均衡器使用网络层TCP提供的信息。在这一层,它通常不查看请求的内容就直接选择一个服务器。
- 7层负载均衡器:可以根据请求中查询字符串、cookie或我们选择的任何请求头中的信息以及包括源地址和目的地址在内的常规层信息来进行负载均衡。
扩展关系数据库
在一个简单的系统中,我们可以使用RDBMS(如Oracle或MySQL)来保存数据项。但是关系数据库系统也会面临着挑战,特别是当我们需要扩展它们的时候。
扩展关系数据库有许多技术:主从复制、主主复制、联合、分片、反范式化和SQL调优。
- 复制通常是指一种技术,它允许我们在不同的机器上存储相同数据的多个副本。
- 联邦(或功能分区)按功能分割数据库。
- 分片是一种与分区相关的数据库架构模式,将数据的不同部分放到不同的服务器上,不同的用户将访问数据集的不同部分。
- 反范式试图以牺牲一些写性能为代价来提高读性能,通过复制写入多个表中的数据来避免昂贵的连接。
- SQL调优。
主从复制
主-从复制技术允许将来自一个数据库服务器(主)的数据复制到一个或多个其他从数据库服务器,如下图所示。
- 客户端将连接到主服务器来更新数据。
- 然后,数据将通过从服务器继续扩散,直到所有数据在服务器之间保持一致。
在实践中,这种方式仍然存在一些瓶颈: - 如果主数据库由于某种原因宕机,数据仍然可以通过从服务器访问,但是不能进行新的写入操作。
- 我们需要一个额外的算法来实现从服务器升到主服务器。
下面是一些只有一个主服务器情况下实现处理更新请求的解决方案: - 同步更新:数据修改事务在被所有服务器(分布式事务)接受之前不会提交,因此在故障转移时不会丢失数据。
-
异步更新:提交->延迟->传播到集群中的其他服务器,因此在故障转移时可能丢失一些数据更新。
请记住,如果同步解决方案太慢,请更改为异步解决方案。
主主复制
当其他服务器也被视为主服务器时,每个数据库服务器都可以充当主服务器。在某个时间点,所有的主节点同步,以确保它们都有正确和最新的数据。
下面是主-主复制的一些优点:
- 如果一个主服务器发生故障,其他数据库服务器可以正常运行,并填补空缺。当数据库服务器重新联机时,它将通过拷贝恢复正常。
- 主服务器可以位于多个物理位置,并且可以分布在整个网络中。
- 受限于主服务器处理更新的能力。
联邦
联邦(或功能分区)按功能分割数据库。例如,您可以拥有三个数据库:论坛、用户和产品,而不是一个总数据库,从而减少对每个数据库的读写流量,从而减少复制延迟。
更小的数据库可以容纳更多的数据到内存,由于改进了缓存局部性,这反过来会实现更高的缓存命中。由于没有单独的串行写,您可以并行地写,从而提高吞吐量。
分片
分片(也称为数据分区)是一种将一个大数据库分解成许多小部分的技术,这样每个数据库只能管理数据的一个子集。
在理想的情况下,我们有不同的用户与不同的数据库节点通信。它有助于提高系统的可管理性、性能、可用性和负载平衡。
- 每个用户只需要与一个服务器进行通信,可以获得该服务器的快速响应。
- 负载在服务器之间很好地平衡——例如,如果我们有5个服务器,每个服务器只需要处理20%的负载。
在实践中,有许多不同的技术可以将数据库分割成多个更小的部分。
水平分割
在这种技术中,我们将不同的行放入不同的表中。例如,如果我们在一个表中存储用户配置文件,那么我们可以决定id小于1000的用户存储在一个表中,id大于1001和小于2000的用户存储在另一个表中。
垂直分割
在本例中,我们将数据划分为与特定特性相关的表存储在自己的服务器中。例如,如果我们正在构建一个类似instagram的系统——我们需要存储用户、他们上传的照片和他们关注的人相关的数据——我们可以将用户的个人资料信息放在一个数据库服务器上,朋友列表放在另一个服务器上,而照片放在第三个服务器上。
基于目录的切分
解决这个问题的一种松散耦合方法是创建一个查找服务,该服务知道您当前的分区模式,并保存每个实体的映射以及它存储在哪个数据库上。
当一个数据库无法存储所有数据时,我们可以使用这种方法来扩展,通过减少一个数据库中的竞争来提供性能。但是请记住,切分技术存在以下一些常见问题。
- 数据库join变得更加麻烦,并且在某些情况下还不能。
- 分片会损害数据库引用完整性。
- 数据库schema更改可能会变得非常昂贵。
- 数据分布不均匀,某个分片上有很多存大量数据。
反范式
反范式试图以牺牲一些写性能为代价来提高读性能。将数据的副本写入多个表中,以避免昂贵的连接。
一旦数据通过联合和分片等技术进行分布,跨数据中心的连接管理将进一步增加复杂性。去范式可能会避免这种复杂的join。
在大多数系统中,读的频率可能大大超过写:100:1甚至1000:1。导致复杂数据库连接的读取可能非常昂贵,会在磁盘操作上花费大量时间。
一些RDBMS如PostgreSQL和Oracle支持物化视图,物化视图处理存储冗余信息和保持冗余副本一致的工作。
数据库类型
在数据库领域,有两种主要的解决方案:SQL和NoSQL。它们有不同的构建方式,存储不同的信息类型,以及它们使用的存储方法也不同。
SQL
关系数据库以行和列的形式存储数据。每一行包含关于一个实体的所有信息,每一列包含所有单独的数据点。一些最流行的关系数据库有MySQL、Oracle、MS SQL Server、SQLite、Postgres和MariaDB。
NoSQL
它也被称为非关系数据库。这些数据库通常分为五个主要类别:键值存储、图存储、列存储、文档存储和Blob存储。
键值对存储
数据存储在键-值对数组中。' key '是一个链接到' value '的属性名。著名的键值存储包括Redis、Voldemort和Dynamo。
文档存储
在这些数据库中,数据存储在文档中(而不是表中的行和列)。每个文档可以有完全不同的结构。文档数据库包括CouchDB和MongoDB。
列存储
在列式数据库中,我们有列族,而不是表,用于存储放列数据。与关系数据库不同,我们不需要预先知道所有的列,每一行也不需要有相同数量的列。
列式数据库最适合分析大型数据集,包括Cassandra和HBase。
图数据库
这些数据库用于存储那些能将关系表示成图对数据。数据保存在带有节点(实体)、属性(关于实体的信息)和线(实体之间的连接)的图结构中。图形数据库的例子包括Neo4J和InfiniteGraph。
如何选择使用哪个数据库?
说到数据库技术,没有万能的解决方案。这就是为什么许多企业同时依赖SQL和NosQL数据库来满足不同的需求。
横向扩展web层
我们已经扩展了数据层,现在我们需要扩展web层。要做到这一点,我们需要将用户会话(状态)的数据从web层存储到一个数据库中,如关系数据库或NoSQL。它也被称为无状态架构。
不要使用有状态架构。 我们必须尽可能地选择无状态架构,因为状态的实现限制了可伸缩性,降低了可用性,并增加了成本。
在上述场景中,负载均衡器可以实现最大效率,因为它可以选择任何服务器进行最佳请求处理。
高级概念
缓存
负载均衡帮助您在不断增加的服务器数量上进行水平扩展,但是缓存将使您能够更好地利用已经拥有的资源,以便在随后的请求中更快地服务请求。
通过向服务器添加缓存,我们可以避免直接从服务器读取网页或数据,从而减少服务器上的响应时间和负载。这有助于使我们的应用程序更具可伸缩性。缓存可以应用于许多层,如数据库层、web服务器层和网络层。
内容分发网络(CDN)
CDN服务器保存内容(如图像、网页等)的缓存副本,并从最近的位置提供服务。CDN的使用改进了用户的页面加载时间,因为数据是在最接近它的位置检索的。这也有助于增加内容的可用性,因为它存储在多个位置。
CDN服务器向我们的Web服务器发出请求,以验证正在缓存的内容,并在需要时更新它们。缓存的内容通常是静态的,如HTML页面、图像、JavaScript文件、CSS文件等。
全球化
当你的应用走向全球时,你将拥有并运营世界各地的数据中心,以确保你的产品每周7天、每天24小时运行。传入的请求将基于GeoDNS路由到“最佳”数据中心。
GeoDNS是一种DNS服务,可以根据客户所在的位置将域名解析为IP地址。从亚洲连接的客户端可能会获得与从欧洲连接的客户端不同的IP地址。
完整架构
迭代前面讲的所有这些技术,我们可以轻松地将系统扩展到超过1亿用户,如无状态架构,应用负载均衡器,尽可能多地使用缓存数据,支持多个数据中心,在CDN上托管静态资源,通过分片扩展您的数据层等。