《深入浅出大型网站架构设计》第1章
第一章 网站架构概述
1.1 网站的基本组件
- 现在的网站在公司或组织中扮演者极其重要的角色,是公司或组织业务的线上版本,甚至是公司与用户的主要接口。这些网站的使用频率高,逻辑也极其复杂,所以有必要进行进一步的细分,以达到提升性能、灵活伸缩、更容易变更业务逻辑等的目的。
- 网站基本架构
- 应用层:提供视图和轻量前端服务
- 服务层:提供后端复杂逻辑和计算服务
- 数据层
- 文件服务器
- 数据库服务器
1.2 网站业务规模增长带来的问题
- 所谓量变引起质变,对于一个系统来说,同一件事情,做一次造成的影响和做一百万次造成的影响是完全不同的。
- 从技术的角度看,当一个网站随着业务规模、业务种类的增长,承担的流量越来越大时,会出现哪些问题呢?
- 当网站的业务变得复杂时,网站应用会有更复杂的计算需求。
- 一个事务的每个阶段,它的资源占用未必是一致的。
- 网站和服务器就像平时使用的软件和个人计算机一样,它在代码本身没有漏洞的情况下,依然有出错的概率。
1.3 大型网站架构设计的目标和原则
- “高性能、高可用、高神所、高安全、高扩展”
- “高可用性、高并发性、高性能”
- 在实际应用种,很多时间不能简单地归类到某一类原则种,而是它们的综合体。
- 不同的原则,它们遵循方式和实现手段也未必在一个维度上。
1.3.1 高性能(Performance)
- 衡量一个网站性能的主要指标可以分为以下两个角度。
- 从单个用户或客户都安的角度来看,就是单个请求(在保证正确的情况下)的响应时间(即延时),一般来说响应时间越短,性能越高。
- 从网站建设和维护者的角度来看,除了每个请求的响应时间,还有每秒事务次数(Transaction Per Second, TPS),以及服务器的性能指标,包括CPU使用率、内存使用率、IOPS(Input Output Per Second)等。TPS越高,性能越高。
- 而服务器性能的衡量指标则是总体来说资源占用越低,网站性能越高。
- 高性能:提高每个请求的响应速度,降低其资源消耗。
1.3.2 高可用(Availability)
- 当一个网站的可用性很高时,则意味着它在绝大多数的时候可以被用户访问,并且一些小规模的故障和以外不会影响它的可用性。
- 可用性:网站关键功能或API的调用成功率。
- 网站的可用性应该由部分服务器或者逻辑组件出现故障时,网站业务是否多半可用来衡量。在实际生产中,依然要落实到API实时错误率之类的数值上。
1.3.3 伸缩性(Scalability)
- 伸缩,是指网站的服务器集群规模即一些其他的必需资源的增加与减少。
- 网站拥有者的资本是有限的,服务器资源也是有限的。正常生产环境中的网站需要根据实际的需求,动态地变动自己拥有的服务器,当预期流量增长时,添加服务器,而当预期流量减少时,减少服务器,这样才能保证在所花费的成本没有浪费的情况下,满足所有网站的使用需求。
- 衡量网站伸缩性的指标如下:
- 理想状况下,在网站业务规模增长时,能够通过直接添加足够的服务器来满足未来的需求?
- 理想状况下,在网站业务规模缩小时,能够通过直接减少服务器削减所有有必要削减的成本?
- 伸缩性好的网站,在业务规模发生变化时,可以通过直接添加或减少服务器来适应变化,并且能够最小化成本的浪费。
1.3.4 扩展性
- 扩展性:业务和功能的扩展。
- 网站的扩展性是指一个网站需要支持新的功能和业务时,是否能够很容易地添加支持。
- 添加这个新功能,是否需要对已有代码或者架构进行大量的修改?
- 添加这个新功能,假如在已有组件中已经有类似功能,是否需要从头搭建类似的功能?
- 添加这个新功能,有没有可能对没有被修改的网站组件造成影响?
- 添加这个新功能,有没有可能降低没有被修改的网站组件的性能。
- 回答“否”越多,网站的扩展性越好,反之扩展性越差。
《深入浅出大型网站架构设计》
第2章 大型网站架构设计的流程
2.1 需求分析
- 需要列举出所有需求,并结合当前团队的成本预算和技术实力设计出一个有效且符合实际的架构。
2.1.1 需求驱动的重要性
- 需求驱动是指将需求作为设计的原动力来决定设计的特点,以及应该侧重哪些方面、舍弃哪些方面,当需求变动时,优先根据需求的变动量决定进行什么样的更新。
- 容易出现的问题:
- 过于侧重需求方面,而忽视了同样重要的其他方面。
- 设计能满足当前需求,但是过于追求设计上的完美,从而需要花费远多于满足需求截止日期的时间,使项目处于错过窗口期而可能失败的境地。
- 设计能满足当前需求,但是对某些未来潜在且近在眼前的需求变动无法满足,并且需要彻底重构。
- 没有与需求发起者,如产品经理沟通,而对某些需求产生了错误的理解。
2.1.2 如何根据需求制定系统目标
- 步骤
- 列举出这个架构是为了谁而设计的,即用户是谁。
- 列举出每个类型的用户的所有用例。
- 用例(Use Case)是一个描述用户如何使用这个系统的例子。规范写法如:作为一个某类的用户,我希望在做事情家时,能够看见效果乙。
- 根据这些用例,对用户进行优先级的分类和排序。
- 优先级分类
- 必须有的功能,一般称为P0。P代表英文单词Priority(优先级)。
- 初始发布可以没有,但是未来版本马上需要有的功能,一般成为P1.
- 有固然好,没有也没关系的功能,一般称为P2。
- 优先级分类,促使设计者和产品经理或者任何需求的发起者进行深入沟通,从而发现一些之前没有意识到的问题。决定了设计者设计的侧重点。指导设计者如何迅速修改系统来满足新的状况。
- 优先级分类
- 根据P0制定当前架构必须具有的功能,根据P1制定当前架构必须没有的瓶颈。
- 设计者要指导系统必须满足哪些功能。
- 提醒设计者,由于成本限制、旧有系统的限制或者团队自身能力的限制,当前设计出的架构未必是最完美的。
2.2 方案设计
2.2.1 与架构设计原则相结合
- “奥卡姆剃刀定律”:如无必要,勿增实体。
- 根据识别出的优先级,结合网站的主要用例和流量特征,决定需要有限考虑什么原则。参考标准:
- 对访问模式比较频繁的网站,我们需要尽量追求高性能、高并发。
- 对业务实时性和可靠性要求高、访问人群对服务质量敏感挑剔的网站,我们需要尽量追求高可用。
- 对业务根据不同时间流量变化比较大的网站,我们需要尽量追求伸缩性。
- 对业务变动频繁、需要经常支持新用例或者淘汰老用例的系统,我们需要尽量追求扩展性。
2.2.2 设计多套备选方案
- 尽量设计多套方案以供选择,越复杂的架构,使用的依赖越多。
- 复杂且潜在种种变动的需求:
- 列举出所有不确定的部分。
- 做出假设。
- 目的是不要让不确定性阻止你的工作。
- 根据做出的假设,列出备选方案。
- 备选方案需要有一个预备思想,以防意外出现时错手不及。
- 有时候你的主设计依赖于对某些隐患的暂时忽视,而当这些隐变成了真正的问题时,你能展示出你的“B计划”。
2.3 方案评估
- 针对方案的评估和调整。
- 需要开一个设计的研讨会议或者评估会议。
- 整个团队了解你的设计思路。
- 有团队中水平与你相当或高于你的人来参与评估。
- 有需求发起者参与评估。
- 好处是:第一,你能够发现一些在设计过程中没有意识到或遗漏的问题;第二,也可以通过对需求的再次回顾,在设计中精简掉一些不必要的部分。
- 团队在技术角度上认可了你的设计之后,需要对成本进行评估。
- 这样的架构需要使用其他服务吗?例如,数据库系统或者云服务。
- 这样的架构需要使用多少台服务器?
- 开发的周期需要多久?测试的周期需要多久?
- 在进行技术评估和成本评估,了解了资源的使用率之后,再次回顾设计,查看有没有可以取舍的部分。
- 在技术评估的过程中,发现某一部分设计对团队的技术能力要求过高,或者某一部分使用了极为昂贵的某类系统,则需要重新思考是否需要舍弃一部分设计中带来的优势,从而达到节约成本、降低实现难度等目的。
- 需要开一个设计的研讨会议或者评估会议。
《深入浅出大型网站架构设计》
第3章 数据库的选择
3.1 关系数据库
- 关系数据库(Relational Database)
3.1.1 什么是关系数据库
- 关系数据库由“表”构成。
- 每张表都有一个独立且唯一的名字,而表中的每一行代表用户保存的值的联系。
- “关系”(Relation)就是指表。
- 所有的关系数据库都有一个模式(Schema),模式是指数据库的逻辑设计,就是数据库表的定义。
- 规范的关系数据库语言表示就是:成绩(学号、姓名、成绩)
- 在关系数据库中,相同名字的属性可以建立不同表之间的联系,以及相应的约束(Constraint)
- 需要对数据库进行读取、更新、写入等操作,所谓的SQL(Structured Query Language,结构化查询语言)
- SQL在对数据库进行操作时,整个过程被成为一个事务(Transaction)。
- 成熟的关系数据库产品,都一定会保证事务在执行过程中,所有出现的错误都能够被正确地处理,不会出现由于SQL语句复杂,而出现数据不一致的情况。
3.1.2 关系数据库的优势和应用场景
- 关系数据库特征
- 使用者在对SQL熟练掌握的前提下,可在数据层直接对数据进行极为复杂的操作。
- 关系数据库在完成数据操作时始终保持一致,而不会因为一些操作的错误或者先后顺序问题让某些请求读到一些过时或者不正确的数据。这一般也被简称为关系数据库的数据一致性。
- 对于数据记录和写入记录的过程,关系数据库可以进行数据的值验证。
- 关系数据库应用场景
- 经常要对数据进行非常复杂或者繁所的操作的业务
- 对数据操作的安全性和可靠性要求极高的业务
3.2 非关系数据库
- NoSQL = Not Only SQL
- 非关系数据库和关系数据库并不是完全对立的关系,你中有我,我中有你的关系,各自的侧重点不同。
3.2.1 什么是非关系数据库
- 非关系数据库的核心是键值对(Key-Value Pair)。
- 非关系数据库中新纪录的键值可以自由输入,即如果该非关系数据库没有特殊定义的话,新记录中可以缺失旧记录中的键值对,也可以有旧记录中没有的键值对。
- 关系数据库中,记录受到约束,在进行写入、读取等操作时,都经过了数据库的层层处理保证它的强一致性,而非关系数据库在没有特别定义的前提下,这些操作都没有相应的安全保证。
- 非关系数据库的优势
- 非关系数据库容易扩展和更新,在变动时所需要进行的配置和代码变化是最少的。
- 非关系数据库在所作事务相同、所使用的产品技术水平相当的情况下,一般比关系数据库速度快。
- 非关系数据库机会不需要开发者学习额外的内容,易上手。
- 非关系数据库的使用场景
- 需要快速开始开发并迭代的产品
- 产品所用到的数据定义不确定、未来变动很大
- 产品规模变动频繁,数据层需要经常扩展
- 数据规模较大,处理速度要求较高
- 如果要建设的网站没有特殊的安全性或可靠性需求,一般简易从非关系数据库产品入手。
3.3 常见的关系数据库产品
3.3.1 MySQL
- LAMP = Linux + Apache + MySQL +PHP
- 在现在新兴的网站中,使用MySQL的网站已经逐渐变少了,因为同样的使用场景,更多的公司愿意城市使用非关系书库,因为同样简便、轻量的需求下,非关系数据库比MySQL所需的配置、学习成本都要低,而且性能更好。
3.3.2 MS SQL Server
- MS SQL Server是典型的商用、企业级关系数据库,主要用户也是中小型企业和组织,但是也有大型公司和商业用例使用它。
- 优势:
- 拥有强大的可视化界面。
- 与微软技术栈的结合非常自然且紧密。从Windows服务器、C#编程语言、.NET框架、IIS中间件到MS SQL Server。
- 缺点:
- 使用MS SQL Server一般就要使用微软的技术栈。
- 由于使用全套的微软商用产品价格交规,所以对于中小型公司和组织来说成本也是无法回避的负担。
3.3.2 Oracle
- Oracle的优势,可靠性和安全性。如果网站的安全性要求极高,数据层要求非常健壮,那么Oracle是最佳选择。并且其错误恢复和日志机制十分健全。
- Oracle的劣势,需要一定的学习成本,领域知识特征极强。
3.4 常见的非关系数据库产品
3.4.1 MongoDB
3.4.2 DynamoDB
3.5 云数据库
- 第一,云数据库可以省去专门针对数据库的部署和配置。
- 第二,云数据库的维护和伸缩都不需要用户手动操作。
- 第三,大多数云服务商都对云数据库提供了良好的配套监视系统。
- 无论是关系数据库还是非关系数据库,在网站开发者成本和条件允许的情况下,应当尽量使用云服务,以减少编码之外的工作量。
《深入浅出大型网站架构设计》
第4章 数据库优化:分库分表
- 数据库在业务规模增长时,就需要对原先简单的数据库系统进行进一步的分割和优化。
4.1 什么是分库分表
- 数据库从广义上说就是一台服务器或者服务器集群。因此,当数据存量或者数据的吞吐量增大时,服务器的负担就会逐渐增加以至于影响读/写的性能和成功率。这时,就需要将数据库进行切分。
- 如果将数据分散到多台数据库服务器,则称为分库;
- 如果将一台数据库服务器上的数据用多张表表示,则成为分表。
4.1.1 分库
- 根据数据种类拆分:用户、商品、订单数据库->用户数据库+商品数据库+订单数据库
4.1.2 分表
- 分表之后,就得到了一张从业务逻辑上来说信息更重要、读取更频繁的表和一张信息不如前一张重要、读取较少的表。
4.2 为什么要进行分库分表
4.2.1 吞吐量
- 数据量太大时,数据库的性能会受到影响。数据量大有两个方面:意识数据的吞吐量很大,即每次存储或读取的数据都很复杂或者很大;二是数据库本身保存的数据很多。
4.2.2 索引
- 当数据库本身的数据量很大时,读写操作的性能也会下降,具体下降程度取决于数据库的实现。同时,无论是关系数据库还是非关系数据库,都会有“索引”的概念。
- 索引在本质上也是一个数据库表,只是经过了特殊数据结构或者算法的调制,例如,很多数据库中的索引是使用B树实现的,比简单的散列表在范围查询上速度要快得多,但再快的数据结构也不是魔法,当数据量达到一定程度时,运行速度依然会出现肉眼可见的下降。索引的本意就是为了加快查询,如果索引的速度受到了影响。那么数据库的性能就更加不堪了。
4.2.3 备份
- 数据备份时指每隔一段时间对生产环境中的数据进行复制。它们不必时刻保持与实际数据同步,只需要保证一定的时效性。
- 如果生产数据的量极多,备份时所消耗的时间也会相应增大。
- 如果经过了分库或者分表,就可以对不同的表采取不同的备份策略。
4.2.4 其他风险
- 数据量越大、数据越集中,发生其他问题导致数据损坏或丢失的风险越大,而发生问题时造成的问题也越大。
4.3 实现分库分表
- 分库分表的实现手段大致分为两类:垂直和水平。
4.3.1 垂直分库分表
- 垂直分库分表适用于如下场景:
- 表的列数过多。
- 表中的信息明显属于多个业务逻辑模块。
- 表中的信息重要程度差异明显。
- 表中的字段大小差异明显。
- 表中的字段访问频率差异明显。
- 可根据业务逻辑,将库分成几个独立的小库。
- 垂直分表
- 回归本源,即网站的业务逻辑。根据业务逻辑分析,从逻辑和语义上看,有哪几类信息。
- 根据以下特征进行进一步的分类。
- 哪些是重要的,哪些是次要的。
- 哪些是占用空间大的,哪些是占用空间小的。
- 哪些是访问频率高的,哪些是访问频率低的。
- 根据分析后的结果,尽量将重要、占用空间小和访问频率高的信息分为一组,将相反的信息分为另一组。
- 如果不能同时满足多条特征(即又重要、又占用空间小、或者又重要、访问频率又高等),则优先从数据信息本身的意义考虑分组。
- 分表之后,所有分出来的表都依然和原来的表共享一个唯一的ID。
4.3.2 水平分库分表
- 水平分库分表就是横向切分一个库或表,切分之后,分出来的库或表依然保持原表的结构,但是存储的是另一部分的数据。
- 水平分库分表适用于以下场景:
- 当前单库或单表的数据行数过大,已经导致单库或者单表的读写出现了性能下降。
- 尚未出现性能瓶颈,但业务特征导致数据库规模(行数)会持续增长或大幅增长。
- 水平分表的解决方案
- 固定范围
- 优点:
- 易于理解,易于实现。
- 随着业务和数据规模的增长,数据表均匀增长,理论上可以无限扩展。
- 缺点:
- 范围的大小不易掌握,太大则重现了分库分表前的问题,太小则造成了太多的子库或子表,从而造成过重的维护负担。
- 在某些情况下,不同子库或子表所承担的数据量和吞吐量可能会极不均衡。
- 优点:
- 使用配置表
- 优点:
- 易于理解,易于实现。
- 变动灵活,随着业务和数据规模的增长,数据表的增长可以自由控制。
- 当业务规模和数据规模需要变动时可以通过修改配置表的数据来进行快速的修改。
- 缺点:
- 配置表本身也是表。
- 为了操作到真正的数据,每次操作都要额外对配置表进行一次操作。
- 优点:
- 基于算法的映射
- 优点:可以通过调制映射算法使数据在子表中的分布达到相对均匀,并且无论数据如何增长,每个子表承担的新数据都是相对均匀的。
- 缺点
- 虽然不是高新科技,但其实现手段毕竟比前两种要复杂一些,而且需要设计者挑选一个适合当前用例的算法,不至于在数据增长时出现不均匀的子表增长。
- 当业务变动,扩充新表时需要重新设计映射算法,而映射算法的重新设计有时会造成所有数据都需要重新分布,维护代价将大大增高。
- 固定范围
4.4 分库分表带来的问题
- 在数据量很大的情况下,能够提高读写性能、降低风险,并且可以适应持续的业务增长。但分库分表也会带来一些潜在问题。
4.4.1 全局唯一ID
- 数据的ID需要额外的机制保护其唯一性。
- 一个常见的手段时使用各个编程语言框架提供的ID生成逻辑。
- UUID的主要缺陷就是占用过多的空间。作为数据的ID,其唯一作用就是识别这条数据,如果连它都占用了过多的空间,那么数据库的读写性能必然受到影响。另外,索引的创建也会因为它占用过多的空间而降低性能。
4.4.2 关系数据库的部分操作
- 分库分表之后会影响关系数据库的部分操作的有效性和效率。(例如JOIN)
- SQL还有一部分操作时处理查询数据集合的,如Order By、Group By等,这些操作也无法在多个库之间进行,而是依然要使用应用层的逻辑,让网站开发者使用编程语言手动实现这些操作。
4.4.3 事务支持
- 数据库,尤其是关系数据库,有原生的事务支持,即所谓的一致性、原子性等(最流行的说法是ACID,A=Atomicity原子性,C=Consistency一致性,I=Isolation独立性,D=Durability持久性)。
- 分库之后,事务就不能再被原生支持。
- 有很多现代的应用层框架已经支持了这样的操作,但是与之整合依然需要额外的工作。
《深入浅出大型网站架构设计》
第5章 数据库优化:读写分离
- 用户信息的读操作和写操作是不平衡的,读操作高而写操作低。
5.1 什么是读写分离
- 读写分离是指将数据库的读操作和写操作分开。这里的分开是指再服务器的维度上分开,即读操作和写操作在不同的服务器上完成。
- 经过读写分离之后,在应用层对数据库进行操作时,写操作和读操作的请求将会被发送到不同的数据库服务器。
- 写操作被发送至一台称为主数据库的服务器中,我们也可以成为主服务器(Master),而读操作被发送至一台成为从数据库的服务器中,我们也可以称其为从服务器(Slave)。
- 读写分离之后,一般会有一主一从两台服务器(库),或者一主多从多台服务器(库),而控制读写分离的实际操作,在应用层或者数据访问的封装层中完成,在需要进行读/写操作时,实时决定要将请求发送给哪台服务器。
- 分库分表:
- 原动力是数据量过大。
- 分开的方式一般是将每一条数据根据字段分成两部分或将多条数据分散到多个数据库中。
- 读写分离:
- 原动力是因为数据读写的频率不均衡;
- 分开的方式一般是将同样的数据复制到多台数据库服务器中。
5.2 为什么要使用读写分离
- 第一,在什么情况下我们需要考虑读写分离?
- 第二,使用读写分离后,会带来哪些好处。
5.2.1 何时需要使用读写分离
- 数据库的读写频率极高,已经造成数据库的性能明显下降。
- 数据库的信息根据业务逻辑和生产数据可知,读写频率有明显差距,一般来说读操作次数远远大于写操作的次数。
- 数据库的性能下降原因不在于读操作本身。
5.2.2 读写分离的好处
-
首先,使用读写分离之后读操作和写操作对相应服务器的压力都将大大减轻,从而提升性能。
-
其次,在分离读写操作之后,我们可以进一步地为主从数据库进行有区别的、针对性的优化。
-
再次,本书后文会重点阐述“高可用性”的重要性和实现方式。
-
最后,针对某些书库,使用读写分离数还可以带来更多额外的好处。
-
有的数据库,如MySQL中有X锁和S锁的概念,X锁是指排它锁(X是Exclusive,即排斥),S锁是指共享锁(S是Shared,即共享),X锁的作用是当某个操作可以对某条数据加上锁使得只有当前这个操作可以读取或者修改这条数据,而S锁的作用是某个操作可以对某条数据加上所,使得其他数据不可以再给当前数据加上X锁,直到这个S锁被解除(释放)为止。读操作往往需要加S锁,而写操作往往需要加X锁。
-
读写操作频繁的时候,X锁和S锁会频繁争用,在数据库原本的延时基础上增加了更多的时间来等待锁的释放。在使用读写分离之后,这两个锁的争用情况将会被大大缓解。
5.3 实现读写分离
- 通过中间件实现和通过应用层实现。
5.3.1 中间件实现
- 中间件(Middleware)是一个舶来词,它是处于硬件(Hardware)和软件(Software)之间的组件。
- 数据库服务器看作硬件,可以将业务逻辑,即应用层看作软件,那中间件就在数据库服务器和应用层之间。从软件设计的原则角度上,中间件应该是一个和业务解耦的独立组件,连接数据库服务器和应用层,并包装访问数据库的任何逻辑。
- 中间件需要实现以下功能:
- 与不同类型的数据库的操作语言和标准(如SQL、JSON数据)进行整合,这样才能够实现数据库和应用层的解耦。
- 与不同类型的数据库的连接协议兼容。
- 提供对主从数据库操作的整合和错误处理。这方面类似与原生数据库事务的原子性、一致性。
- 提供支持多门语言的接口给应用层调用。中间件存在的目的就是让应用层不需要处理读写分离的操作,因此其接口必须和数据库保持一致,而原生数据库的接口,如关系数据库就是标准SQL。
5.4.2 应用层实现
- 直接在应用层实现。这种实现方法当然也需要将访问数据库的代码抽象到一个层中,称其为数据库访问层(Data Access Layer),但它不是一个独立的组件或者服务器。
- 中间件在应用层就相当于一个数据库,而数据访问层则是一个与应用层耦合更紧密的组件。
- 数据访问层可以自己实现读写分离。
5.4 读写分离带来的问题
- 读写分离可以使读频率远远高于写频率的网站或者服务得到极大的性能提升,但它也有自己的问题。
5.4.1 副本的实时性
- 主从数据库不一致是读写分离面临的主要问题。
5.4.2 副本实时性的解决方案
- 根据业务的实时敏感度决定是否采用读写分离。
- 一旦对某些信息发生了写操作,下一个读操作在主数据库上进行。
- 对主数据库进行二次读取。
5.4.3 成本问题
- 读写分离有成本问题,或者说复杂性上的顾虑。在数据库的读操作频率极高时,是否选择使用读写分离是一个值得思考的问题。读写分离最大的问题在于,从数据库也是数据库(服务器),它也需要成本,当从数据库及其中间件、数据访问层消耗的建设和维护成本以及导致的故障频率增加时,还有很多其他选项值得考虑,如缓存。