构建一个复杂系统,从简单的部分着手,先让所有的功能在一个服务器上运行,包括 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服务器 与 数据库服务器交互,读写更新 返回数据可以选择传统的关系型数据库,也可以选择非关系型数据库。
关系型数据库历史悠久,方案成熟,但它们无法满足特殊使用场景时,就需要考虑关系型数据库之外的选项。当满足下面条件,非关系型数据库可能是一个正确的选择:
纵向扩展也叫向上扩展,提升服务器的能力 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服务器写数据时写到主库,读数据时从 从库中读,多个从库一个主库,从库从主库进行数据库复制到自己本地。
数据库复制优点:
如果只有一个从库,宕机了,系统暂时将读操作路由到主库,主库宕机,会有一个从库代替原来的主库 生产环境中,从库的数据不一定是最新,推选一个新的主库会更麻烦,缺失的数据库需要通过运行数据恢复脚本来不全。
缓存是临时的存储空间,用于存储一些很耗时的响应结果或者内存中经常被访问的数据,这样后续再访问这些数据时能更快。
缓存层是一个 临时数据存储层,比数据库快很多 。设置独立缓存层的好处有:提高系 统归老,减轻数据库的工作负载以及能够单独扩展缓存层。
这种缓存策略叫作通过缓存读 (Read-through Cache) 。缓存服务器一般大家都用 Redis、Memcached。
内容分发网路 Content Delivery Network,CDN。
是由在地理上分散的服务器组成的网络,被用来传输静态内容。 CDN 中的服务器缓存了像图片、视频、 CSS 和 JavaScript 文件这一类的静态内容。当用户访问一个网站时,离用户最近的 CDN 服务器会返回静态资源 。
image.png?v=2
用户 A 的会话数据和个人资料图片会被存储到服务器 1 上 。为了对用户 A 进行身份验证,必须将 HTTP 请求发给服务器1 。 如果将请求发给其他服务器,比如服务器 2, 由 千服务器 2 上没有用户 A 的会话数据,因此身份验证就会失败 。
常见的就是服务器中存了用户的session。
状态架构中,用户的 HTTP 请求可以发给任意 Web 服务器,然后 Web 服务器从共享 的数据存储中拉取数据。状态数据存储在共享数据存储而非 Web 服务器中 。 无状态的系统更加简单,更健壮,也更容易扩展
常见的还有用JWT来解决的,省去服务器端的状态存储,用编解码时间换空间。
session 共享存储一般都用 Redis、Memcached。
两个数据中心例子,用户被基于地理位置的域名服务导流到最近的数据中心。
基于地理位置的域名服务(geoDNS)是一种基于用户的地理位置将域名解析为不同的IP地址的DNS服务。
如果数据中心2挂掉了,则流量会全部导到 数据中心1。不同地区用户可以使用不同本地数据库或缓存,在故障转移的场景流量可能被转到一个数据不可用的数据中心,常用的策略是在多个数据中心复制数据。
消息队列是一个持久化的 组件,存储在内存中,支持异步通信。它被用作缓冲区,分 配异步的请求。消息队列的基本架构很简单:输入服务(也称为生产者或发布者)创建消 息,并把它们发布到消息队列中;其他服务或者服务器(也称为消费者或订阅者)与消息 队列连接,并执行消息所定义的操作。
记录日志,监控错误日志非常重要,你可以监控每个服务器的错误日志,也可以用工具把各个服务器的日志汇到一个中心化的服务中,方便搜索和查看。
收集指标,手机不同类型的指标数据,有助于获得商业洞察力和了解系统的健康状态。如 主机级别指标 CPU、内存、磁盘IO,聚合级指标 比如整个数据库层的性能,整个缓存层的性能等。关键业务指标,每日活跃用户数、留存率、收益等。
自动化工具,持续集成,每次代码检入 check in 都需要通过自动化工具审核,使团队能及时发现问题,将 构建、测试和部署等流程自动化。
下面系统包含一个消息队列,使系统更松散地耦合且更容易从故障中恢复。包含记录日志、监控和收集指标的功能,以及自动化工具。
随着数据与日俱增,数据库过载变得越来越严重,需要扩展数据层。
数据库的扩展有两种方式,纵向扩展和横向扩展。
向上扩展,为已有机器增加算力 CPU、内存、硬盘等。AWS的RDS(关系型数据库服务)可以提供拥有 的24TB内存的数据库服务器,可以存储和处理非常多的数据。
纵向扩展,硬件的能力总是有上限的,更大的单点故障风险,总成本高 强劲的服务器比一般的服务器贵很多。
横向扩展,也叫切片,就是添加更多服务器。
数据库分片指把大数据库拆分成更小、更容易管理的部分(这些部分叫做Shard,分片),每个 Shard 共享同样的数据库 Schema,但里面的数据都是这个 Shard 独有的。
可以进行分表分库。
分片远不是一个完美的解决方案,它为系统引入了复杂性和新的挑战。
重分片数据:可能会遇到,数据快速增长单个Shard无法存储更多数据,因为数据的分布不均有些Shard空间比其他更快耗尽,需要更新用于分片的哈希函数,然后把数据移到别的地方。
名人问题:也叫热点键问题,过多访问一个特定的Shard可能过载,如把特别出名人的数据放在同一个Shard,肯定就是不均匀了。
连接和去规范化,数据库通过分片到多个服务器上,很难跨数据库分片执行连接 join 操作,常用方法是对数据库去规范化,把数据冗余存储到多张表中,以便查询可以在一张表中执行。
对数据库做分片,以支持数据流量的快速增长,将有些非关系型功能迁移到 NoSQL 数据库中,以降低数据库的负载。