从0开始学架构
架构设计的目的
主要目的是为了解决软件系统复杂度带来的问题。
架构设计的三大原则
合适、简单、演化
1、合适优于业界领先
2、简单优于复杂
3、演化优于一步到位
高性能、高可用、可扩展性
高性能增加机器目的在于”扩展“处理性能;
高可用增加机器目的在于”冗余“处理单元;
高可用
”状态决策“,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。
高可用状态决策:
1、独裁式(决策者决定)
2、协商式(主从决策)
3、民主式(投票)
可扩展性
两个基本条件:正确预测变化、完美封装变化
应对变化:将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。
一、高性能
- 数据库集群:读写分离、分库分表
- NoSQL、缓存架构
- 单服务器:PPC与TPC、Reactor与Proactor
- 负载均衡:分类及架构、算法
(1)读写分离
读写分离的基本原理是将数据库读写操作分散到不同的节点上。
两个设计复杂度:主从复制延迟和分配机制
复制延迟
解决方法
- 写操作后的读操作指定发给数据库主服务器
- 读从机失败后再读一次主机
- 关键业务读写操作全部指向主机,非关键业务采用读写分离
分配机制
将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件封装。
(2)分库分表
业务分库
按照业务模块将数据分散到不同的数据库服务器。比如把用户数据、商品数据、订单数据放在三台不同的数据库服务器上。
带来问题
1、join操作问题
2、事务问题
3、成本问题
分表
单表数据拆分有两种方式:垂直分表和水平分表
垂直分表:将表中某些不常用且占了大量空间的列拆分出去。
水平分表:适合表行数特别大的表。
水平分表的路由算法
1、范围路由
2、Hash路由
3、配置路由
(3)NoSQL
关系数据库存在如下缺点:
- 存储的是行记录,无法存储数据结构
- schema 扩展很不方便
- 在大数据场景下 I/O 较高
- 全文搜索功能比较弱
常见的 NoSQL 方案分为 4 类
- K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
- 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
- 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
- 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。
(4)缓存架构
1. 缓存穿透
指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。通常情况下有两种情况:
- 存储数据确实不存在
- 存储系统中存在数据,但缓存数据生成耗费大量时间或者资源,或刚好在业务访问的时候缓存失效了
2. 缓存雪崩
指当缓存失效(过期)后引起系统性能急剧下降的情况
缓存雪崩的常见解决方法有两种:更新锁机制和后台更新机制。
1. 更新锁
对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
2. 后台更新
由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存。
3. 缓存热点
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。
不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存雪崩效应。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值。
(5)单服务器高性能模式:Reactor和Proactor
PPC 模式最主要的问题就是每个连接都要创建进程,解决这个问题是将 read 操作改为非阻塞,然后进程不断地轮询多个连接。但轮询是要消耗 CPU 的;
为了解决上述问题,只有当连接上有数据的时候进程才去处理,这就是** I/O 多路复用技术的来源**。
I/O 多路复用结合线程池,取了一个很牛的名字:Reactor。
Reactor 模式的核心组成部分包括 Reactor 和处理资源池(进程池或线程池),其中Reactor 负责监听和分配事件,处理资源池负责处理事件。
Reactor 模式有这三种实现方案:
- 单 Reactor 单进程 / 线程。
- 单 Reactor 多线程。
- 多 Reactor 多进程 / 线程。
1. 单 Reactor 单进程 / 线程
优点:没有进程间通信,没有进程竞争,全部都在同一个进程内完成。
缺点:无法发挥多核 CPU 的性能;只能采取部署多个系统来利用多核 CPU。
只适用于业务处理非常快速的场景,目前比较著名的开源软件中使用单 Reactor 单进程的是 Redis。
2. 单 Reactor 多线程
优点:充分利用多核多 CPU 的处理能力
缺点:多线程数据共享和访问比较复杂。例如,子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的互斥和保护机制。
3. 多 Reactor 多进程 / 线程
多 Reactor 多进程 / 线程的方案看起来比单 Reactor 多线程要复杂,但实际实现时反而更加简单,主要原因是:
- 父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理。
- 父进程和子进程的交互很简单,父进程只需要把新连接传给子进程,子进程无须返回数据。
- 子进程之间是互相独立的,无须同步共享之类的处理(这里仅限于网络模型相关的select、read、send 等无须同步共享,“业务处理”还是有可能需要同步共享的)。
目前著名的开源系统 Nginx 采用的是多 Reactor 多进程,采用多 Reactor 多线程的实现有Memcache 和 Netty。
(6)负载均衡
高性能集群的复杂性主要体现在需要增加一个任务分配器,现在更流行的叫法是“负载均衡器”。
常见的负载均衡系统包括 3 种:DNS 负载均衡、硬件负载均衡和软件负载均衡。
DNS 负载均衡
一般用来实现地理级别的均衡。
DNS 负载均衡的本质是 DNS 解析同一个域名可以返回不同的 IP 地址。例如,同样是 www.baidu.com,北方用户解析后获取的地址是 61.135.165.224(这是北京机房的 IP),南方用户解析后获取的地址是14.215.177.38(这是深圳机房的 IP)。
优点:就近访问,提升访问速度
缺点:更新不及时、扩展性差、分配策略比较简单
硬件负载均衡
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5 和 A10。
软件负载均衡
软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有 Nginx 和 LVS,其中Nginx 是软件的 7 层负载均衡,LVS 是 Linux 内核的 4 层负载均衡。4 层和 7 层的区别就在于协议和灵活性,Nginx 支持HTTP、E-mail 协议;而 LVS 是 4 层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等。
负载均衡典型架构(三种组合使用)
二、高可用
(1)FMEA 方法
在架构设计领域,FMEA 的具体分析方法是:
- 给出初始的架构设计图。
- 假设架构中某个部件发生故障。
- 分析此故障对系统功能造成的影响。
- 根据分析结果,判断架构是否需要进行优化。
(2)高可用储存:双机架构
析常见的双机高可用架构:主备、主从、主备 / 主从切换和主主。
(3)高可用储存:数据集群和数据分区
数据分区
设计一个良好的数据分区架构,需要从多方面去考虑
1. 数据量:数据量的大小直接决定了分区的规则复杂度。
2. 分区规则:
3. 复制规则:
(4)异地多活架构
异地多活架构可以分为同城异区、跨城异地、跨国异地。
跨国异地:
- 为不同地区用户提供服务
- 只读类业务做多活
异地多活 - 四大技巧
- 优先实现核心业务的异地多活
- 保证核心数据最终一致性
- 采用多种手段同步数据(MySQL 的主备复制、Redis 的 Cluster 功能、Elasticsearch 的集群功能)
- 只保证绝大部分用户的异地多活
异地多活 - 四步走
第 1 步:业务分级
访问量大的业务、核心业务、产生大量收入的业务
第 2 步:数据分类
数据量、唯一性、实时性、可丢失性、可恢复性
第 3 步:数据同步
存储系统同步、消息队列同步、重复生成
第 4 步:异常处理
多通道同步、同步和访问结合、日志记录、用户补偿
(5)应对接口级的故障
导致接口级故障的原因一般有下面几种:
- 内部原因:程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等。
- 外部原因:黑客攻击、促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。
解决接口级故障的核心思想和异地多活基本类似:优先保证核心业务和优先保证绝大部分用户。
应对接口级的故障方法:
1. 降级:系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。
2. 熔断:降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。
3. 限流:只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。
4. 排队:限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间
三、可扩展性
可扩展的基本思想:拆
常见的拆分思路有如下三种:
- 面向流程拆分:分层架构,将整个业务流程拆分为几个阶段,每个阶段作为一部分。
- 面向服务拆分:SOA、微服务,将系统提供的服务拆分,每个服务作为一部分。
- 面向功能拆分:微内核架构,将系统提供的功能拆分,每个功能作为一部分。
微服务
- 服务发现、服务路由、服务容错:这是最基本的微服务基础设施。
- 接口框架、API 网关:主要是为了提升开发效率,接口框架是提升内部服务的开发效率,API 网关是为了提升与外部服务对接的效率。
- 自动化部署、自动化测试、配置中心:主要是为了提升测试和运维效率。
- 服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率。
微内核架构
微内核架构,也被称为插件化架构,是一种面向功能进行拆分的可扩展性架构
微内核架构包含两类组件:核心系统和插件模块。
核心系统负责和具体业务功能无关的通用功能,例如模块加载、模块间通信等;插件模块负责实现具体的业务逻辑,例如专栏前面经常提到的“学生信息管理”系统中的“手机号注册”功能。