最近在阅读《从 0 开始学架构》,干货满满,特在此记录。
软件架构指软件系统的顶层结构;框架是面向编程或配置的半成品;组件是从技术维度上的复用;模块是从业务维度上职责的划分;系统是相互协同可运行的实体。
软件开发最本质的挑战有两个:复杂和变更,而软件的价值是保证业务的响应力,与之相对的是开发资源的有限,各种的软件开发方法论,也都是在研究有限的资源下,如何应对着两个挑战,寻找平衡点,实现业务目标。因为是在寻找平衡点,就说明是有取舍的,所以就没有所谓的银弹的存在。
一、复杂度来源
1)高性能
软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
2)高可用
高可用是指系统无中断地执行其功能的能力。其“冗余”解决方案,单纯从形式上来看,和高性能是一样的,都是通过增加更多机器来达到目的,但其实本质上是有根本区别的:高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。
- 计算高可用,这里的“计算”指的是业务的逻辑处理。计算有一个特点就是无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的。
- 存储高可用,存储与计算相比,有一个本质上的区别:将数据从一台机器搬到到另一台机器,需要经过线路进行传输。难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。
3)可扩展性
可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。
预测变化的复杂性在于:
- 不能每个设计点都考虑可扩展性。
- 不能完全不考虑可扩展性。
- 所有的预测都存在出错的可能性。
对于架构师来说,如何把握预测的程度和提升预测结果的准确性,是一件很复杂的事情,而且没有通用的标准可以简单套上去,更多是靠自己的经验、直觉。
第一种应对变化的常见方案是将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。无论是变化层依赖稳定层,还是稳定层依赖变化层都是可以的,需要根据具体业务情况来设计。
第二种常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”。抽象层是稳定的,实现层可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无须修改抽象层。这种方案典型的实践就是设计模式和规则引擎。
4)低成本、安全、规模
低成本给架构设计带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。这里的“创新”既包括开创一个全新的技术领域(这个要求对绝大部分公司太高),也包括引入新技术,如果没有找到能够解决自己问题的新技术,那么就真的需要自己创造新技术了。
类似的新技术例子很多,我来举几个。
- NoSQL(Memcache、Redis 等)的出现是为了解决关系型数据库无法应对高并发访问带来的访问压力。
- 全文搜索引擎(Sphinx、Elasticsearch、Solr)的出现是为了解决关系型数据库 like 搜索的低效的问题。
- Hadoop 的出现是为了解决传统文件系统无法应对海量数据存储和计算的问题。
从技术的角度来讲,安全可以分为两类:一类是功能上的安全,一类是架构上的安全。
功能安全其实就是“防小偷”,而架构安全就是“防强盗”。
规模带来复杂度的主要原因就是“量变引起质变”,当数量超过一定的阈值后,复杂度会发生质的变化。常见的规模带来的复杂度有:
- 功能越来越多,导致系统复杂度指数级上升。
- 数据越来越多,系统复杂度发生质变。
二、高性能架构模式
1)读写分离
读写分离的基本原理是将数据库读写操作分散到不同的节点上。
读写分离的基本实现是:
- 数据库服务器搭建主从集群,一主一从、一主多从都可以。
- 数据库主机负责读写操作,从机只负责读操作。
- 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
- 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
读写分离的实现逻辑并不复杂,但有两个细节点将引入设计复杂度:主从复制延迟和分配机制。
2)分库分表
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题:
- 原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
- 表分散到不同的数据库中,无法通过事务统一修改。
- 业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台。
单表数据拆分有两种方式:垂直分表和水平分表。
垂直分表引入的复杂性主要体现在表操作的数量要增加。水平分表相比垂直分表,会引入更多的复杂性,主要表现在下面几个方面:
- 某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算。
- 数据分散在多个表中,如果需要与其他表进行 join 查询,需要在业务代码或者数据库中间件中进行多次 join 查询,然后将结果合并。
- 水平分表前用一个 count() 就能完成的操作,在分表后就没那么简单了。
- 排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询每个子表中的数据,然后汇总进行排序。
3)NoSQL
关系数据库存在如下缺点。
- 关系数据库存储的是行记录,无法存储数据结构。
- 关系数据库的 schema 扩展很不方便。
- 关系数据库在大数据场景下 I/O 较高。
- 关系数据库的全文搜索功能比较弱。
NoSQL != No SQL,而是 NoSQL = Not Only SQL。常见的 NoSQL 方案分为 4 类。
- K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
- 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
- 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
- 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。
4)缓存架构
缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可能重复使用的数据放到内存中,一次生成、多次使用,避免每次使用都去访问存储系统。
缓存能够带来性能的大幅提升,以 Memcache 为例,单台 Memcache 服务器简单的 key-value 查询能够达到 TPS 50000 以上。
缓存穿透是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。
缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况。对于一个高并发的业务系统来说,几百毫秒内可能会接到几百上千个请求。
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。
5)负载均衡:分类及架构
常见的负载均衡系统包括 3 种:DNS 负载均衡、硬件负载均衡和软件负载均衡。
DNS 负载均衡的本质是 DNS 解析同一个域名可以返回不同的 IP 地址。
DNS 负载均衡实现简单、成本低,就近访问,提升访问速度,但也存在更新不及时、扩展性差、分配策略比较简单等缺点。
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。
硬件负载均衡的优点是:功能强大,性能强大,稳定性高,支持安全防护。缺点是价格昂贵,扩展能力差。
软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有 Nginx 和 LVS,其中 Nginx 是软件的 7 层负载均衡,LVS 是 Linux 内核的 4 层负载均衡。
软件负载均衡的优点:简单,便宜,灵活。缺点是性能一般,一个 Nginx 大约能支撑 5 万并发,功能没有硬件负载均衡那么强大,一般不具备防火墙和防 DDoS 攻击等安全功能。
6)负载均衡:算法
根据算法期望达到的目的,大体上可以分为下面几类。
- 任务平分类:负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或者权重上的平均。
- 负载均衡类:负载均衡系统根据服务器的负载来进行分配,这里的负载并不一定是通常意义上我们说的“CPU 负载”,而是系统当前的压力,可以用 CPU 负载来衡量,也可以用连接数、I/O 使用率、网卡吞吐量等来衡量系统的压力。
- 性能最优类:负载均衡系统根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器。
- Hash 类:负载均衡系统根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上。常见的有源地址 Hash、目标地址 Hash、session id hash、用户 ID Hash 等。
三、高可用架构模式
1)存储架构:双机架构
常见的高可用存储架构有主备、主从、主主、集群、分区,每一种又可以根据业务的需求进行一些特殊的定制化功能,由此衍生出更多的变种。
主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如 MySQL、Redis、MongoDB 等。
主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。我们可以理解为仆从是要帮主人干活的,这里的干活就是承担“读”的操作。也就是说,主机负责读写操作,从机只负责读操作,不负责写操作。
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作。
2)存储架构:集群和分区
集群就是多台机器组合在一起形成一个统一的系统,这里的“多台”,数量上至少是 3 台;相比而言,主备、主从都是 2 台机器。根据集群中机器承担的不同角色来划分,集群可以分为两类:数据集中集群、数据分散集群。
数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为 1 主多备或者 1 主多从。
数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。
数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。
3)异地多活架构
异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。
实现异地多活架构代价很高,具体表现为:
- 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
- 成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。
根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。
4)接口级的故障
接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了。例如,业务响应缓慢、大量访问超时、大量访问出现异常(给用户弹出提示“无法连接数据库”),这类问题的主要原因在于系统压力太大、负载太高,导致无法快速处理业务请求,由此引发更多的后续问题。
导致接口级故障的原因一般有下面几种:
- 内部原因,程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等。
- 外部原因,黑客攻击、促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。
解决接口级故障的核心思想和异地多活基本类似:优先保证核心业务和优先保证绝大部分用户。
降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。例如,论坛可以降级为只能看帖子,不能发帖子。
熔断是禁止访问的意思,例如A 服务内部只要发现是请求 B 服务的这个接口就立即返回错误,从而避免 A 服务整个被拖慢甚至拖死。
限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。
排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间,全世界最有名的排队当属 12306 网站排队了。
四、可扩展架构模式
可扩展性架构的设计方法很多,但万变不离其宗,所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
按照不同的思路来拆分软件系统,就会得到不同的架构。常见的拆分思路有如下三种。
- 面向流程拆分:将整个业务流程拆分为几个阶段,分层架构,例如展示层 → 业务层 → 数据层 → 存储层。
- 面向服务拆分:将系统提供的服务拆分,SOA、微服务,例如将系统拆分为注册、登录、信息管理、安全设置等服务。
- 面向功能拆分:将系统提供的功能拆分,微内核架构,每个服务都可以拆分为更多细粒度的功能,例如手机注册、邮箱注册等。
1)微内核架构
微内核架构包含两类组件:核心系统(core system)和插件模块(plug-in modules)。
核心系统负责和具体业务功能无关的通用功能,例如模块加载、模块间通信等;插件模块负责实现具体的业务逻辑,例如专栏前面经常提到的“学生信息管理”系统中的“手机号注册”功能。
微内核的核心系统设计的关键技术有:插件管理、插件连接和插件通信。
- 插件管理,核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。
- 插件连接,插件连接指插件如何连接到核心系统。通常来说,核心系统必须制定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可。
- 插件通信,插件通信指插件间的通信。虽然设计的时候插件间是完全解耦的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信。
规则引擎从结构上来看也属于微内核架构的一种具体实现,其中执行引擎可以看作是微内核,执行引擎解析配置好的业务流,执行其中的条件和规则,通过这种方式来支持业务的灵活多变。
对照微内核架构的设计关键点,我们来看看规则引擎是具体是如何实现的。
- 规则引擎中的规则就是微内核架构的插件,引擎就是微内核架构的内核。
- 规则引擎的插件连接实现机制其实就是规则语言。
- 规则引擎的规则之间进行通信的方式就是数据流和事件流,规则只需要输出数据或者事件,由引擎将数据或者事件传递到下一个规则。
五、架构实战
随着业务规模越来越大,系统复杂度越来越高,子系统数量越来越多,如果继续采取各自为政的方式来实现这些支撑功能,会发现重复工作非常多。因此我们自然而然就会想到将这些支撑功能做成平台,避免重复造轮子,减少不规范带来的沟通和协作成本。
1)运维平台
运维平台的核心设计要素是“四化”:标准化、平台化、自动化、可视化。
需要制定运维标准,规范配置管理、部署流程、监控指标、应急能力等,各系统按照运维标准来实现,避免不同的系统不同的处理方式。
运维平台的好处有:
- 可以将运维标准固化到平台中,无须运维人员死记硬背运维标准。
- 运维平台提供简单方便的操作,相比之下人工操作低效且容易出错。
- 运维平台是可复用的,一套运维平台可以支撑几百上千个业务系统。
传统手工运维方式效率低下的一个主要原因就是要执行大量重复的操作,运维平台可以将这些重复操作固化下来,由系统自动完成。
可视化的主要目的就是为了提升数据查看效率。
2)数据平台
数据平台的核心职责主要包括三部分:数据管理、数据分析和数据应用。
数据管理包含数据采集、数据存储、数据访问和数据安全四个核心职责,是数据平台的基础功能。
数据分析包括数据统计、数据挖掘、机器学习、深度学习等几个细分领域。
数据应用很广泛,既包括在线业务,也包括离线业务。例如,推荐、广告等属于在线应用,报表、欺诈检测、异常检测等属于离线应用。