分类 从0开始学架构 下的文章

基本思想和模式

  • 可扩展的基本思想:拆分
  • 拆分思路

    • 面向流程拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分。

      • 举例:TCP/IP协议的层次,即应用层 → 传输层 → 网络层 → 物理 + 数据链路层
      • 架构实现方式:分层架构
    • 面向服务拆分:将系统提供的服务拆分,每个服务作为一部分。

      • 举例:TCP/IP协议每层提供的服务,例如应用层的HTTP、FTP、SMTP 等
      • 架构实现方式:SOA、微服务
    • 面向功能拆分:将系统提供的功能拆分,每个功能作为一部分。

      • 举例:TCP/IP每个服务能提供的功能,例如HTTP 服务提供 GET、POST 功能等
      • 架构实现方式:微内核

511.png

CAP理论

  • 定义:在一个分布式系统(指互相连接并共享数据的节点集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
  • 一致性:对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
  • 可用性:非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)
  • 分区容错性:当出现网络分区后,系统能够继续“履行职责”
  • 实际应用中,必须选择P(分区容错性),因此只有AP、CP两种选择

    • CP(Consistency/Partition Tolerance)

      • 为了保证一致性,当发生分区现象后,客户端请求到未收到最新数据的节点时,节点返回Error,即不满足可用性
    • AP(Availability/Partition Tolerance)

      • 为了保证可用性,当发生分区现象后,客户端请求到未收到最新数据的节点时,节点返回当前持有的旧数据,因为该数据不是最新的,所以又不满足一致性

CAP理论细节

  • CAP细节阐述

    • CAP 关注的粒度是数据,而不是整个系统

      • 一个系统会涉及到多种数据,不同的数据可以各自选择CP还是AP
    • CAP 是忽略网络延迟的
    • 正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA

      • 架构设计的时候既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA
    • 放弃并不等于什么都不做,需要为分区恢复后做准备

      • 我们可以在分区期间进行一些操作,从而让分区故障解决后,系统能够重新达到 CA 的状态
  • ACID理论

    • Atomicity(原子性)

      • 要么做,要么全不做
    • Consistency(一致性)

      • 在事务开始之前和事务结束以后,数据库的完整性没有被破坏
    • Isolation(隔离性)

      • 允许多个并发事务同时对数据进行读写和修改的能力
      • 隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
    • Durability(持久性)

      • 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
  • BASE理论

    • 核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但可以采用适合的方式达到最终一致性
    • 基本可用(Basically Available)

      • 分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
    • 软状态(Soft State)

      • 允许系统存在中间状态,而该中间状态不会影响系统整体可用性
      • 这里的中间状态就是 CAP 理论中的数据不一致
    • 最终一致性(Eventual Consistency)

      • 系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
  • ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸

FMEA方法

  • FMEA(Failure mode and effects analysis,故障模式与影响分析),是一种通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响的方法。
  • 具体分析方法

    • 给出初始的架构设计图
    • 假设架构中某个部件发生故障
    • 分析此故障对系统功能造成的影响
    • 根据分析结果,判断架构是否需要进行优化
  • 分析维度

    • 功能点(指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的)
    • 故障模式(指的是系统会出现什么样的故障,包括故障点和故障形式,这里不需要给出故障原因
    • 故障影响(功能点具体会受到什么影响,需要尽量准确描述)
    • 严重程度(严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度)
    • 故障原因(不同原因的概率、检测手段、处理措施会不一样)
    • 故障概率(高/中/低)
    • 风险程度(即故障等级,风险程度 = 严重程度 × 故障概率)
    • 已有措施(针对具体的故障原因,系统现在是否提供了某些措施来应对)
    • 规避措施(为了降低故障发生概率而做的一些事情,技术或管理手段)
    • 解决措施(为了能够解决问题而做的一些事情,一般都是技术手段)
    • 后续规划(技术/管理手段,规避/解决措施)
  • Xmind图
    411.png
  • 举例
    412.png

存储高可用架构:双机架构

  • 存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用
  • 常见架构:主备、主从、主主
  • 主备复制

    • 架构关系

      • 正常时:客户端-(读&写)->主机-(数据复制)->备机
      • 主机不可用时:客户端-(读&写)->备机
    • 优点:简单(客户端不感知备机、主备机仅需要数据复制)
    • 缺点:备机硬件成本浪费、不可用时需要人工切换
  • 主从复制

    • 架构关系

      • 客户端-(读&写)->主机-(数据复制)->从机
      • 客户端-(读)->从机
    • 优点

      • 主机故障时,从机能继续提供读服务
      • 从机提供读服务,发挥了硬件性能
    • 缺点

      • 客户端需要感知主从关系,增加复杂度
      • 如果主从复制延迟比较大,会出现数据不一致问题
      • 故障时仍需要人工干预
    • 双机切换

      • 互连式

        • 指主备机直接建立状态传递的渠道(主->从/备)
        • 通道的具体实现方式多样:网络连接、串口线
        • 客户端只记录虚拟IP;或者同时记录主备IP,能连哪个就哪个
        • 缺点:如果传递通道有问题,则从/备机会变成主机,造成有两个主机
      • 中介式

        • 在主备两者之外引入第三方中介,主备机都去连接中介,通过中介来传递状态信息
        • 优点:连接管理更简单、状态决策更简单
        • 缺点:中介自己如果不可用,则整个系统陷入双备状态
      • 模拟式

        • 备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态
        • 优点:实现更加简单,因为省去了状态传递通道的建立和管理工作
        • 缺点:模拟式读写操作获取的状态信息只有响应信息,没有互连式那样多样,基于有限的状态来做状态决策,可能出现偏差
  • 主主复制

    • 架构关系

      • 客户端-(读&写)->主机A
      • 客户端-(读&写)->主机B
      • 主机A<-(数据复制)->主机B
    • 存在问题

      • 必须保证数据能够双向复制,而很多数据是不能双向复制的(例如自增ID,库存数)

存储高可用架构:集群和分区架构

  • 数据集群

    • 数据集中集群

      • 架构关系

        • 客户端-(读&写)->主机-(数据复制)->从/备机1、2……n
      • 复杂度高

        • 主机如何将数据复制给备机
        • 备机如何检测主机状态
        • 主机故障后,如何决定新的主机
    • 数据分散集群

      • 多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据(类似RAID)
      • 均衡性
      • 容错性
      • 可扩展性
  • 数据分区

    • 数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据
    • 数据量

      • 数据量越大,分区规则会越复杂,考虑的情况也越多
    • 分区规则

      • 例如:洲际分区、国家分区、城市分区
    • 复制规则

      • 集中式

        • 指存在一个总的备份中心,所有的分区都将数据备份到备份中心
        • 优点

          • 设计简单,各分区之间并无直接联系,可以做到互不影响
          • 扩展容易
        • 缺点

          • 成本较高,需要建设一个独立的备份中心
      • 互备式

        • 指每个分区备份另外一个分区的数据
        • 优点:成本低,直接利用已有的设备
        • 缺点

          • 设计比较复杂,各个分区除了要承担业务数据存储,还需要承担备份功能
          • 扩展麻烦
      • 独立式

        • 指每个分区自己有独立的备份中心
        • 优点

          • 设计简单,各分区互不影响
          • 扩展容易,新增加的分区只需要搭建自己的备份中心即可
        • 缺点

          • 成本高,每个分区需要独立的备份中心

计算高可用架构

  • 计算高可用的本质是通过冗余更多计算机器来规避部分故障的风险
  • 常见架构:主备、主从、集群
  • 主备架构

    • 架构关系

      • 任务分配器-(计算任务)->主机
      • 任务分配器-(计算任务,故障时人工切换)->备机
    • 任务分配器将所有任务分配给主机,当主机不可用时,人工切换至备机
    • 备机类型:冷备、温备

      • 冷备:备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动
      • 温备:备机上的业务系统已经启动,只是不对外提供服务
    • 优点:简单;缺点:人工切换
  • 主从架构

    • 架构关系

      • 任务分配器-(计算任务A)->主机
      • 任务分配器-(计算任务B)->从机
    • 正常情况下,计算任务A分配给主机,计算任务B分配给从机;当主机故障时,人工将从机升级为主机,此时任务A&B均分配给新主机
    • 优点:发挥从机硬件性能;缺点:需要任务分类,且任务分配会复杂一点
  • 集群架构

    • 对称集群

      • 集群中每个服务器的角色都是一样的,都可以执行所有任务
      • 具体设计

        • 正常情况下,任务分配器按照一定分配算法将任务分配给不同的机器
        • 当检测到某机器故障时,不再给它分配任务
        • 当故障机器恢复正常后,重新分配任务
      • 设计关键点

        • 任务分配算法:随机、轮询
        • 检测服务器状态:通过心跳来传递信息,包括服务器信息和任务信息
    • 非对称集群

      • 一台master,多台slaver。部分任务是 Master 服务器才能执行,部分任务是 Slave 服务器才能执行。
      • 具体设计

        • 集群会通过某种方式来区分不同服务器的角色(ZAB算法选举)
        • 任务分配器将不同任务发送给不同服务器
        • 若master故障,则从多台slaver中重新选举新的master;如果slaver故障,则只要剔除掉它即可
      • 复杂点

        • 任务分配策略更加复杂
        • 角色分配策略实现比较复杂

业务高可用:异地多活

  • 应用场景

    • 异地就是指地理位置上不同的地方;多活就是指不同地理位置上的系统都能够提供业务服务。
    • 代价:系统复杂度会发生质的提高;成本会上升。
  • 架构模式

    • 同城异区

      • 定义:同一个城市的不同机房
      • 优点:由于同城,逻辑上我们可以将它们看作同一个机房,这样的设计大大降低了复杂度,减少了异地多活的设计和实现复杂度及成本
      • 不足:城市级别的灾难无法解决(例如新奥尔良大水灾)
    • 跨城异地

      • 存在问题:网络传输速度会显著降低;中间传输过程中不可控因素增加;造成的数据短时间内不一致
    • 跨国异地

      • 适用场景:为不同地区用户提供服务;只读类业务做多活

异地多活设计4大技巧

  • 保证核心业务的异地多活

    • 举例:登录功能比注册核心,而登录所需数据可同步至其它中心
  • 保证核心数据的最终一致性

    • 尽量减小异地多活机房的距离,搭建高速网络
    • 尽量减少数据同步,只同步核心业务相关的数据
    • 保证最终一致性,不保证实时一致性
  • 采用多种手段同步数据

    • 消息队列
    • 二次读取

      • 第一次读取本地,本地失败后第二次读取对端
    • 存储系统同步

      • MySQL 的主备复制、Redis 的 Cluster 功能等
    • 回源读取

      • 判断数据属于的中心,直接读它
    • 重新生成数据

      • 例如:session读取失败,则重新生成即可
  • 只保证绝大部分用户的异地多活

异地多活设计4步走

  • 业务分级

    • 按照一定的标准将业务进行分级,挑选出核心的业务设计异地多活,降低方案整体复杂度和实现成本
    • 评价维度:访问量大、核心业务、产生大量收入业务
  • 数据分类

    • 挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计
    • 分析维度:数据量、唯一性、实时性、可丢失性、可恢复性
  • 设计数据同步方案

    • 存储系统同步

      • 优点:使用简单
      • 不足:无法针对业务数据特点做定制化的控制
    • 消息队列同步

      • 适合无事务性或者无时序性要求的数据
    • 重复生成

      • 数据不同步到异地机房,每个机房都可以生成数据。例如session,cookie
  • 异常处理

    • 多通道同步

      • 采取多种方式来进行数据同步,例如存储系统+消息队列
      • 一般情况下,两种通道即可,不然维护成本也很高
      • 不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障
      • 需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的
    • 同步和访问结合

      • 不能让数据库同步和接口访问都走同一条网络通道
      • 数据有路由规则,可以根据数据来推断应该访问哪个机房的接口来读取数据
      • 由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问
    • 日志记录

      • 每个关键操作前后都记录相关一条日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复
      • 常用日志保存方式

        • 服务器上保存日志
        • 本地独立系统保存日志
        • 日志异地保存
    • 用户补偿

如何应对接口级的故障

  • 典型表现

    • 系统并没有宕机,网络也没有中断,但业务却出现问题(业务响应缓慢、访问超时或出现异常)
  • 故障原因

    • 内部原因:程序bug、慢SQL,程序逻辑不完善导致耗尽内存等
    • 外部原因:黑客攻击(DDoS)、大量正常请求(双十一秒杀)
  • 应对思想

    • 优先保证核心业务优先保证绝大部分用户
  • 应对措施

    • 降级

      • 指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能
      • 降级方式

        • 系统后门降级(优点:成本低。缺点:存在安全隐患、要一台台搞效率低)
        • 独立降级系统
    • 熔断

      • 指的是当依赖的系统或服务出现异常故障时,停止访问该服务,而是直接报错返回
      • 设计关键:阈值的设定。一般先根据分析确定阈值,然后上线观察效果,再进行调优。
      • 与降级的区别:降级是服务提供方的操作(我不行了,只保证部分功能,或者直接给你返回个结果);而熔断则是服务消费方的操作(我依赖你,你不行了,我就不再调你,省的我自己被拖累)。
    • 限流

      • 限流是从系统访问压力的角度来应对故障的方式。只承受能够承受的访问请求,超过部分丢弃。
      • 限流方式

        • 基于请求限流,例如QPS。设计关键仍是阈值,一般采用压测分析,上线观察,调优。
        • 基于资源限流,例如CPU使用率、线程数。设计关键也是如何选择资源以及对应的阈值。
    • 排队

      • 是限流的变种,它不丢弃请求,而是全部扔在队列里面,调度处理(若队列满了还是要丢弃的)。

数据库集群:读写分离

  • 原理:将数据库读写操作分散到不同的节点上。

    • 数据库服务器搭建主从集群,一主一从、一主多从都可以
    • 数据库主机负责读写操作,从机只负责读操作
    • 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据
    • 业务服务器将写操作发给数据库主机,将读操作发给数据库从机
  • 存在问题:复制延迟

    • 解决方式1:写操作后的读操作指定发给数据库主服务器

      • 存在问题:和业务强绑定,对业务的侵入和影响较大
    • 解决方式2:读从机失败后再读一次主机

      • 优点:与业务无绑定,只需要在底层访问数据API接口做封装
      • 存在问题:要读两次,增加主机的访问压力
    • 解决方式3:关键业务读写操作全部指向主机,非关键业务采用读写分离

      • 存在问题:还是和业务强绑定,即要对业务做分级,关键业务不走读写分离
  • 具体实现:分配机制

    • 实现1:程序代码封装

      • 在代码里面抽出一个数据访问层,实现读写操作分离和数据库服务器连接的管理。
      • 优点:实现简单,且可以做定制。
      • 缺点:每个编程语言都要实现一次;如果要做主从切换,则需要改代码或者配置,然后重启
      • 已有产品:TDDL
    • 实现2:中间件封装

      • 独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。
      • 优点:支持多编程语言;主从切换等操作,业务服务器无感知
      • 缺点:实现复杂,容易出BUG;所有请求都走中间件系统,性能要求高;
      • 已有产品:MySQL Router(官方)、Atlas(360)

数据库集群:分库分表

  • 业务分库

    • 定义:指的是按照业务模块将数据分散到不同的数据库服务器
    • 存在问题

      • join操作问题——无法使用join,只能业务层面分别查询
      • 事务问题——无法支持本地事务,而分布式事务性能又太差
      • 成本问题——需增加数据库机器
  • 分表

    • 垂直分表

      • 定义:表记录数不变,但按照列切成多张表(例如原有A、B、C三列,切成A、B和A、C两张表)
      • 适用于将不常用的列单独切出去存储
      • 缺点:可以会涉及多次读写操作
    • 水平分表

      • 定义:列不变,将数据切成多份放在不同表里面
      • 路由算法

        • 范围路由:选取有序的数据列(例如整形)作为路由的条件,不同分段分散到不同的数据库表中

          • 优点:可以随着数据的增加平滑地扩充新的表
          • 不足:可能数据分布不均匀
        • Hash路由:将某一列或多列,按照hash分布在不同分表内

          • 优点:数据分布均匀
          • 不足:扩充新表麻烦,需要数据重新分布
        • 配置路由:新增一张路由表,记录原表每行数据对应的分表id

          • 优点:设计简单,需要扩充的时候只要迁移数据再修改路由表数据即可
          • 不足:需要多查询一次,切路由表本身会成为性能瓶颈
      • 分表后问题

        • join操作、count、order by等,都需要业务层面代码支持
    • 实现方式

      • 程序代码封装、中间件封装

高性能NoSQL

  • 关系数据库存在的不足

    • 存储的是行记录,无法存储数据结构
    • schema扩展不方便
    • 在大数据场景下I/O高
    • 全文搜索能力弱
  • K-V存储

    • 解决关系数据库无法存储数据结构的问题,以 Redis 为代表
  • 文档数据库

    • 解决关系数据库强 schema 约束的问题,以 MongoDB 为代表
    • 目前绝大部分文档数据库存储的数据格式是 JSON
    • 优点:新增字段简单、历史数据不会出错、很容易存储复杂数据
    • 不足:不支持事务、无法实现join操作
  • 列式数据库

    • 解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表
    • 按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库”
    • 优点:节省I/O、存储压缩比更大
    • 不足:频率更新多个列性能较差
  • 全文搜索引擎

    • 解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表
    • 基本原理:倒排索引
    • 使用方式:关系型数据库的数据,转换成JSON,再输入全文搜索引擎做索引

高性能缓存

  • 适用场景

    • 需要经过复杂运算后得出的数据
    • 读多写少的数据
  • 缓存存在的问题

    • 缓存穿透

      • 定义:是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据
      • 出现该问题的两种情况

        • 存储数据确实不存在。解决方式是若不存在,也提供一个默认值存在缓存当中
        • 缓存数据生成耗费大量时间或者资源。当缓存失效时,由于生成缓存数据的时间较长,因此这段时间内的请求都会打到存储系统上。
    • 缓存雪崩

      • 定义:是指当缓存失效(过期)后引起系统性能急剧下降的情况。在生成新缓存的这段时间内,所有请求仍然会打到存储系统上。
      • 解决方式

        • 更新锁机制。对缓存更新操作进行加分布式锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
        • 后台更新机制。缓存本身的有效期设置为永久,后台线程定时更新缓存。

          • 注意存在内存不够,缓存数据被踢出的问题。此时两种解决方式:要么更新缓存的线程还要频繁读缓存,若发现被踢了则再写进去;或者业务线程发现缓存数据不存在,发消息通知,然后更新缓存线程收到消息来重新写缓存。
    • 缓存热点

      • 定义:对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大
      • 解决方式:复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。

单服务器高性能模式:PPC与TPC

  • PPC(Process Per Connection)

    • 定义:每次有新的连接就新建一个进程去专门处理这个连接的请求
    • 父进程流程:socket->bind->listen->accept->fork->close
    • 子进程流程:read->业务处理->write->close
    • 适用场景:服务器的访问量和并发量并没有那么大
    • 存在问题

      • fork代价高(要创建进程)、父子进程通信复杂(适用IPC等方案通信)、支持的并发连接数量有限(一般最大几百个)
  • prefork

    • 定义:prefork 就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去 fork 进程的操作
    • 父进程流程:socket->bind->listen->fork
    • 子进程流程:accept->read->业务处理->write->close
    • 实现关键就是多个子进程都accept同一个socket,当有新连接进入时,保证只有一个进程能最后accept成功
  • TPC(Thread Per Connection)

    • 定义:每次有新的连接就新建一个线程去专门处理这个连接的请求
    • 优势:创建线程的消耗比进程要少得多;线程通信相比进程通信更简单
    • 主进程流程:socket->bind->listen->accept->pthread
    • 子线程流程:read->业务处理->write->close
    • 存在问题

      • 创建线程仍然有代价、可能出现死锁、多线程互相影响可能导致整个进程退出
  • prethread

    • 定义:预先创建线程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去创建线程的操作
    • 主进程流程:socket->bind->listen->pthread
    • 子线程流程:accept->read->业务处理->write->close

单服务器高性能模式:Reactor与Proactor

  • Reactor

    • 使用I/O多路复用技术

      • 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接
      • 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理
    • 核心组成:Reactor(负责监听和分配事件)、处理资源池(进程池/线程池)(负责处理事件)
    • 具体实现方式

      • 单Reactor单进程/线程

        • Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
        • 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
        • 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。
        • Handler 会完成 read-> 业务处理 ->send 的完整业务流程。
      • 单Reactor多线程

        • 主线程中,Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发
        • 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件
        • 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应
        • Handler 只负责响应事件,不进行业务处理;Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理
        • Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理;Handler 收到响应后通过 send 将响应结果返回给 client
      • 多Reactor多进程/线程

        • 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程
        • 子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件
        • 当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应
        • Handler 完成 read→业务处理→send 的完整业务流程
  • Proactor

    • Proactor Initiator 负责创建 Proactor 和 Handler,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核
    • Asynchronous Operation Processor 负责处理注册请求,并完成 I/O 操作
    • Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor
    • Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理
    • Handler 完成业务处理,Handler 也可以注册新的 Handler 到内核进程

高性能负载均衡:分类及架构

  • 高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法
  • 负载均衡不只是为了计算单元的负载达到均衡状态,可以基于性能、负载、业务来考虑
  • 负载均衡分类

    • DNS负载均衡

      • 优点:简单、成本低;就近访问,提升访问速度。
      • 不足:更新不及时,有DNS缓存;扩展性差,控制权在域名商;分配策略比较简单
    • 硬件负载均衡

      • 通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似
      • 典型的硬件产品:F5、A10
      • 优点:功能强大;性能强大;稳定性高;支持安全防护
      • 不足:贵;扩展性差
    • 软件负载均衡

      • 通过在普通服务器上运行负载均衡软件来实现负载均衡功能
      • 典型产品:Nginx(7层负载均衡)、LVS(4层负载均衡)
      • 优点:简单;便宜(用普通Linux服务器即可);灵活可扩展
      • 不足:性能一般;功能没有硬件负载均衡强大;一般不具备防火墙和防 DDoS 攻击等安全功能
  • 三种负载均衡可以同时使用

    • 基本原则:DNS 负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡

高性能负载均衡:算法

  • 算法分类

    • 任务平分类。将收到的任务平均分配给服务器进行处理(绝对平均或加权平均)
    • 负载均衡类。根据服务器的负载来进行分配(CPU load、连接数、I/O使用率等)
    • 性能最优类。根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器
    • Hash类。根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上
  • 具体算法

    • 轮询

      • 优点:最简单
      • 不足:不关注服务器本身状态(只要没宕机或断线);不关心服务器之间性能差异(加权轮询解决了该问题)
    • 负载最低优先

      • 将任务分配给当前负载最低的服务器。具体负载指标可以有多个(CPU load、连接数等)
      • 优点:感知服务器本身状态
      • 不足:复杂
    • 性能最优

      • 性能最优优先类算法是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器
      • 优点:同样感知服务器本身状态(通过响应时间)
      • 不足:复杂(收集和统计响应时间本身性能有损耗,采样率和统计周期的设置都有讲究)
    • Hash类

      • 常见两种场景:按源地址Hash,按请求ID Hash

架构是什么

  • 系统

    • 定义:泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。
    • 关键词:关联、规则、能力
  • 模块和组件

    • 模块定义:一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。现代软件开发往往利用模块作为合成的单位。模块的接口表达了由该模块提供的功能和调用它时所需的元素。模块是可能分开被编写的单位。
    • 组件定义:自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。
    • 两者区别:从逻辑的角度来拆分系统后,得到的单元就是“模块”;从物理的角度来拆分系统后,得到的单元就是“组件”。划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。
  • 框架和架构

    • 框架定义:通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品
    • 架构定义:指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。
    • 两者区别:框架关注的是“规范”,架构关注的是“结构”。
  • 重新定义架构

    • 定义:软件架构指软件系统的顶层结构。

架构设计的目的

  • 架构设计的主要目的是为了解决软件系统复杂度带来的问题

    • 通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。
    • 架构设计并不是要面面俱到,不需要每个架构都具备高性能、高可用、高扩展等特点,而是要识别出复杂点然后有针对性地解决问题。
    • 理解每个架构方案背后所需要解决的复杂点,然后才能对比自己的业务复杂点,参考复杂点相似的方案。

软件系统复杂度来源

  • 高性能

    • 高性能带来的复杂度主要有两方面

      • 单台计算机内部为了高性能带来的复杂度
      • 多台计算机集群为了高性能带来的复杂度
    • 单机复杂度

      • 批处理->进程->线程->CPU多核并行
    • 集群复杂度

      • 任务分配、任务分解
  • 高可用

    • 定义:系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。
    • 计算高可用

      • 增加任务分配器、任务分配器与业务服务器之间的连接与交互、分配算法
    • 存储高可用

      • 存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响
    • 高可用状态决策

      • 通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确
      • 决策形式:独裁式、协商式、民主式
  • 可扩展性

    • 具备良好可扩展性的系统,有两个基本条件:正确预测变化完美封装变化
    • 预测变化

      • 不能每个设计点都考虑可扩展性
      • 不能完全不考虑可扩展性
      • 所有的预测都存在出错的可能性
    • 应对变化

      • 方案1:将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”

        • 复杂点:系统需要拆分出变化层和稳定层、需要设计变化层和稳定层之间的接口
      • 方案2:提炼出一个“抽象层”和一个“实现层”
  • 低成本

    • 往往只有“创新”才能达到低成本目标
  • 安全

    • 功能安全:代码层面防范XSS 攻击、CSRF 攻击、SQL 注入等
    • 架构安全:访问控制策略
  • 规模

    • 规模带来复杂度的主要原因就是“量变引起质变”

      • 功能越来越多、数据越来越多

架构设计三原则

  • 合适原则

    • 合适原则宣言:“合适优于业界领先
    • 不合适导致失败的几种原因:开发资源不足;技术积累不够;业务场景不存在
  • 简单原则

    • 简单原则宣言:“简单优于复杂
    • 软件领域复杂性体现在:结构的复杂性、逻辑的复杂性
  • 演化原则

    • 演化原则宣言:“演化优于一步到位
    • 软件架构设计其实更加类似于大自然“设计”一个生物,通过演化让生物适应环境,逐步变得更加强大

      • 首先,设计出来的架构要满足当时的业务需要。
      • 其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。
      • 第三,当业务发生变化时,架构要扩展、重构,甚至重写。

架构设计流程

  • 识别复杂度

    • 将主要复杂度问题列出来(几个方面:高性能、高可用、可扩展、低沉本、安全、规模)
    • 根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题
  • 设计备选方案

    • 常见错误1:设计最优秀的方案

      • 根据架构设计原则中“合适原则”和“简单原则“的要求去设计
    • 常见错误2:只设计一个备选方案

      • 备选方案的数量以 3 ~ 5 个为最佳
      • 备选方案的差异要比较明显
      • 备选方案的技术不要只局限于已经熟悉的技术
    • 常见错误3:备选方案过于详细

      • 耗费了大量的时间和精力
      • 将注意力集中到细节中,忽略了整体的技术设计
      • 评审的时候其他人会被很多细节给绕进去,评审效果很差
  • 评估和选择备选方案

    • 360度环评:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。
    • 常见的质量属性点:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性等
    • 按优先级选择
  • 详细方案设计

    • 详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性。

      • 架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解
      • 通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度
      • 如果方案本身就很复杂,那就采取设计团队的方式来进行设计