百万用户的系统设计的循序演进

海龙2025-6-11编程架构

为学习系统级别的设计,此次由浅到深的,一步步整理出来,防止又双叒叕给忘记。
只有重点逻辑和思路,没有各种细节,细节需要自己查询和实现。而且时代再进步,思路比具体框架更重要

从单服务器开始

服务最简单的开始,就是单服务器,一个服务器上运行web服务、数据库、前端页面。

单服务器

对于这种单服务器,我们可见的是,压力来源主要是两个

  • 浏览器端
  • 手机端

一个应用的初始,这通常足够了,单服务器其实能抗住很多流量。以下是一个大概的估算,可用作参考
RPS:每秒请求数,QPS:每秒查询数,RPS偏向整体流量,QPS偏向数据库瓶颈。
低配服务器

  • 配备 2 核 CPU(如 Intel i3)、4GB 内存、HDD 或入门级 SSD、100Mbps 带宽,适合小型应用。
  • 静态内容可支持 1,000–5,000 RPS(约 6 万–30 万并发用户,每用户每分钟 1 请求),动态 API 100–500 RPS(约 6,000–3 万用户),数据库驱动应用 50–200 RPS(约 3,000–1.2 万用户)。
  • 主要瓶颈为带宽和磁盘 I/O,适合个人博客或低流量网站。

中配服务器

  • 配备 8 核 CPU(如 Intel Xeon)、32GB 内存、NVMe SSD、1Gbps 带宽,适合中小型应用。
  • 静态内容可支持 10,000–50,000 RPS(约 60 万–300 万用户),动态 API 1,000–5,000 RPS(约 6 万–30 万用户),数据库驱动应用 200–2,000 RPS(约 1.2 万–12 万用户)。
  • 瓶颈通常为 CPU 和数据库性能,适合电商或论坛等场景。

高配服务器

  • 配备 32 核 CPU(如 AMD EPYC)、128GB 内存、高性能 NVMe SSD、10Gbps 带宽,适合大型应用。
  • 静态内容可支持 50,000–200,000 RPS(约 300 万–1200 万用户),动态 API 5,000–20,000 RPS(约 30 万–120 万用户),数据库驱动应用 1,000–10,000 RPS(约 6 万–60 万用户)。
  • 瓶颈在带宽和数据库优化,适合流媒体或高并发 API。

数据库的分离

随着用户量增大,数据库瓶颈会变得明显,单台服务器难以承受压力,此时需引入多台服务器进行处理 (算得上分布式形式了)。首先是拆除一台专用于数据库的服务器,将web/移动流量和数据库服务器分离,即为拆出了web层和数据库层。这种分离便于未来针对两个层面进行独立扩展和优化。

分离据库

两种数据库的区别

通常用到的数据库,有关系型数据库和非关系型数据库。

  1. 关系型数据库如mysql,postgresql等,关系型数据库以表格的形式存储数据,每个表格由行和列组成,每个表格之间通过外键关联。
  2. 非关系型数据库如mongodb,redis,elasticsearch等。这些数据库分为四类:键值存储、图存储、列存储和文档存储,通常不支持链接操作。

对于大多数情况来说,关系型数据库是最佳选择,运行稳定且良好。但是一些特殊情况,非关系型数据库会更好:

  • 数据量很大,但是查询很少,比如日志系统。
  • 数据格式不固定,是非结构化数据,比如社交网络。
  • 数据需要低延迟,比如游戏数据。

垂直扩展与水平扩展

垂直扩展:增加单个服务器的硬件资源,如CPU、内存、磁盘等。上面举例的单服务器的不同规格就是垂直扩展。虽然简单,但是有很大局限性。

  • 扩展成本高,需要购买新的服务器。而且有上限,不能无限扩展。
  • 没有容错率,单点故障,一旦服务器故障,整个系统都会瘫痪。

水平扩展:增加服务器数量,将负载分散到多台服务器上。每台服务器都是相同的配置,可以无限扩展。

负载均衡

负载均衡是分布式系统中常用的技术,用于将请求分散到多台服务器上,均匀分配流量。

单服务器

这不仅减少了服务器的压力,还能保证一台服务器故障时,不会影响整个系统。还能有另外一个服务器来维持。而当两台服务器不足,你可以直接加入第三台服务器,而不用重新部署。负载均衡器会自动将流量分配到新的服务器上。
关于负载均衡的算法,有以下几种:

  • 轮询算法:将请求均匀分配到每台服务器上。
  • 加权轮询算法:根据服务器的性能,分配不同的权重,性能好的服务器分配更多的请求。
  • 最少连接算法:将请求分配给连接数最少的服务器。
  • 加权最少连接算法:根据服务器的性能,分配不同的权重,性能好的服务器分配更多的连接。
  • 源地址哈希算法:根据请求的源地址,将请求分配到固定的服务器上。
  • 随机算法:将请求随机分配到每台服务器上。
  • 一致性哈希算法:将请求分配到固定的服务器上。

现在我们有了负载均衡器,有了多台web服务器,web端情况已经到了一个新的阶段了,通常到了这个阶段,就是数据库开始成为瓶颈了。

数据库的复制

数据库的复制是分布式系统中常用的技术,用于将数据复制到多台服务器上,提高系统的可用性和性能。但是通常数据库之间存在主从关系,主库负责写入数据,从库负责读取数据。因为大多数应用都是读多写少,所以通常会使用主从复制,从库数量会多于主库。

数据库复制

这样做,可以让性能更佳,可靠性更高,可用性更强。性能更佳是因为服务被分开了,这是必然的。那么可靠性和可用性体现在哪儿呢?
如果一个数据库故障了

  • 主库故障,从库可以继续提供服务,从库可以被提升为新的主库,可以依旧维持原来的架构来进行服务支持。但是因为原本是从库,所以会存在数据不是最新的数据的情况。丢失的数据可能需要一些办法来恢复数据,亦或者一开始就不止一个主库,作为主库的备份。
  • 从库故障,问题比较轻一些,因为会有其他从库继续提供读取服务,或者还有主库提供服务,不会影响太多。
uml diagram

关于这部分,先总结一下这部分的设计:

  1. 用户端请求DNS服务,DNS服务返回负载均衡器IP地址。
  2. 用户端请求负载均衡器,负载均衡器路由分配到web服务器
  3. web服务器写入数据到主数据库,主数据库复制数据到从数据库
  4. web服务器读取数据,从数据库读取数据

缓存

缓存是临时存储一些数据的技术,用于将一些重复且频繁的访问数据,存储在内存中,以便更快地处理后续请求,还能减轻数据库的压力,增强用户的访问体验。

为了减轻数据库的压力,我们也会需要建立单独的缓存层,有效的提高性能,降低数据库压力之外,还能独立扩展。
服务器接收到请求后,会先向缓存层检查是否有可用的数据,如果有数据,则直接返回,如果没有数据,则向数据库查询数据,并且会复制数据到缓存层。这种策略,叫“读通缓存”,只是缓存策略的一种。

uml diagram

使用缓存的注意事项

  • 什么时候才应该使用缓存?
    • 数据频繁读取,但是不经常更新
    • 缓存服务器重启后,数据库应可以正常接手数据服务
    • 缓存数据存在于易丢失的内存中,因此不应用于持久化储存
  • 过期策略
    • 基于时间失效:为缓存数据设置固定存活时间(如秒、分钟),到期后自动失效
    • 最近最少使用:当缓存空间不足时,移除最近最少访问的数据。
    • 最不经常使用:移除访问频率最低的数据,基于历史访问次数。
    • 先进先出:按数据进入缓存的顺序,优先移除最早加入的数据。
    • 基于业务规则的失效:根据业务逻辑主动失效缓存,如数据更新时清除或标记过期。
    • 随机失效:缓存满时随机移除数据。
  • 一致性,确保数据储存和缓存要同步,这涉及有多种策略进行优化。
  • 缓解故障,单个缓存服务器故障,需要让其不影响系统的正常运行,建立多个不同数据中心的多个缓存服务器,或者按一定比例超额配置所需内存

CDN

CDN 是由地理位置分散的服务器组成的网络,用于分发静态内容。CDN 服务器缓存静态内容,例如图像、视频、CSS、JavaScript 文件等。
CDN的工作原理就是用户访问网站时,静态内容将从用户最近的CDN服务器返回,而不是从服务器,由于静态内容通常内容不会经常更新,
且数据量较大,所以从更近的CDN服务器返回将比从服务器返回更快。减少服务器压力,又能提高用户访问体验。

uml diagram

使用CDN的注意事项

  • 成本,CDN服务器一般用第三方服务,需要支付费用。缓存数据应选择常用的数据
  • 设置缓存过期时间,过期时间太短,会导致频繁从服务器请求数据,过期时间太长,会导致数据更新不及时。
  • CDN回退,当CDN服务器故障时,需要有回退机制,将请求转发到源服务器。
  • 具有可以手动将文件无效化的机制,当文件更新时,可以手动将文件无效化,以便用户可以获取到最新的文件。

下一个阶段

uml diagram

无状态web层

关于水平扩展还有一部分是将状态从web层转移出去,比如用户登录状态,用户信息等。
这些数据可以放在持久化储存中,比如数据库或者NoSQL数据库。所有web服务器都可以从那里获取到状态数据

有状态架构

有状态架构会记住从一个请求到下一个请求的客户端数据(状态)。比如一个登录后,其验证信息会储存在服务器中,假设用户1登录时使用的服务器1.这之后的请求
我们需要保证用户1的请求都走服务器1,否则就会判断为用户1并没有进行登录,导致验证失败。
通常来说,负载均衡器的粘性会话可以做到这一点,让同一个客户端访问同一个服务器,但是会导致服务器之间不再相同,那么故障时替换使用或者删除增加服务器都会变得更困难。

无状态架构

无状态架构中,http请求无论发给哪个服务器,状态都会被存储到一个位置,然后服务器也都会在这里去获取状态信息,这种方式更简单,更健壮,也更有扩展性

uml diagram

我们将状态数据拆分出来,储存在一个持久性的数据存储中,通常来说,这个可以用NoSQL来储存,因为NoSQL更易于扩展。

现在,系统演化到了下面的情况:

uml diagram

数据中心

如果产品成功了,拥有了大量的用户,甚至到了国际级别。那么地址距离数据中心较远的地区,访问将变得十分缓慢,影响用户体验,所以就要扩展出多个不同位置的数据中心,来提高不同地区的用户体验。

假如你开了两个数据中心,也就是你全套服务拥有了两组,那么用户访问时,就要通过geoDNS路由,也就是地理路由,来寻找到最近的数据中心来访问服务。

geoDNS

GeoDNS路由是一种基于地理位置的域名系统(DNS)解析技术,通过根据用户的地理位置返回最优的服务器IP地址来优化网络性能和用户体验

  1. 接收DNS请求:用户发起DNS查询,请求某个域名(如example.com)的IP地址。
  2. 地理位置识别:GeoDNS服务器根据客户端的IP地址,结合GeoIP数据库(如MaxMind),确定用户的地理位置(如国家、城市)。
  3. 选择最优服务器:根据预设的路由策略(如最低延迟、负载均衡或区域限制),返回距离最近或最合适的服务器IP。
  4. 响应DNS查询:将选定的IP地址返回给客户端,客户端随后连接到该服务器。

两个数据中心图示

uml diagram

如果某个数据中心发生彻底的宕机,还能有另外的整套的服务中心继续提供服务,这也提高了系统的容错空间。
实现多个数据中心,有一些必须要解决的技术问题,涉及数据同步、网络延迟、负载均衡、应用复杂性、安全合规、监控运维和GeoDNS局限性等技术难点。
这些问题 通常在分布式系统的那些组件中,已经有前人栽树了:

  • 分布式数据库
  • 高精度GeoIP数据库
  • 全局负载均衡(GSLB)
  • Kafka、flink
  • Prometheus、Jaeger、IaC

监控与自动化

日志记录:监控错误日志非常重要,因为它有助于识别系统中的错误和问题。您可以按服务器级别监控错误日志,也可以使用工具将它们聚合到集中服务中,以便于搜索和查看。
指标:收集不同类型的指标有助于我们获得业务洞察并了解系统的健康状况。以下一些指标很有用:

  • 主机级别指标:CPU、内存、磁盘 I/O 等。
  • 聚合级别指标:例如整个数据库层、缓存层等的性能。
  • 关键业务指标:日活跃用户数、留存率、收入等。
    自动化:当系统变得庞大而复杂时,我们需要构建或利用自动化工具来提高生产力。持续集成是一种很好的实践,它通过自动化来验证每次代码提交,使团队能够及早发现问题。此外,构建、测试、部署等流程的自动化可以显著提高开发人员的生产力。
uml diagram

数据库扩展

垂直扩展

垂直扩展,就是在现有的数据库中,增加更多cpu,RAM,磁盘。这种方法有厉害的企业在用,但是这种方式依然是有各种问题存在,而且会十分昂贵

水平扩展

水平扩展(也称为分片),就是增加更多的服务器,将大型的数据库服务器分割成多个更小的部分,这些部分叫做“分片”,每个分片使用相同的架构,但是里面的数据是不同的。
水平扩展是非常棒的扩展数据的方案,但是水平扩展也会有一些问题:

  • 数据分布不均匀,导致某些分片数据量过大或者访问热点集中。
  • 复杂的事务管理,跨分片的事务(分布式事务)实现复杂且性能开销大。数据库可能需要两阶段提交(2PC)或补偿机制,增加了开发和维护成本。
  • 查询复杂性增加,非分片键的查询可能要扫描很多分片,导致查询效率低下
  • 数据迁移和扩展变得困难,因为要涉及大量数据的迁移
  • 一致性和同步难以保证,特别是分布式情况下,需要用额外的同步机制
  • 分片键的选择变困难,需要兼顾性能,分布均匀,扩展性等各种方面
  • 跨分片的表关联查询效率低下

结语

应对系统的逐渐增加,核心思想就是"拆",服务器、数据库、资源以及各种内容拆开,然后再通过一些方式去一层层解决因为"拆"带来的一致性、延迟、幂等、性能等各种问题。

最后更新日期 6/24/2025, 10:29:18 AM