从零开始学架构#
架构基础#
概念#
-
系统与子系统
系统:指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。(关联、规则、能力)
子系统也是由一群关联的个体所组成的系统,多半是更大系统中的一部分。
例如微信本身是一个系统,同时又包含聊天、支持、朋友圈等子系统。
朋友圈这个系统又包含动态、评论、点赞等子系统。
-
模块与组件
软件模块:是一套一致且互相有紧密关联的软件组织,它包含程序和数据结构两部分。
软件组件:定义为自包含的、可编程、可重用的、与语言无关的软件单元,软件组件可以很容易地被用于组装应用程序。
例如学生信息管理系统,从逻辑角度拆分,可以分为登陆注册模块、个人信息模块、个人成绩模块;从物理角度可以拆分成 nginx、web 服务器、MySQL.
- 从逻辑角度,拆分系统得到的就是模块
- 从物理角度,拆分系统得到的就是组件
- 划分模块是为了职责分离,划分组件为了单元复用。
-
框架与架构
软件框架:为了实现某个业界标准或完成特定基本任务的软件组织规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
软件架构:是指软件系统的基础结构,创造这些基础结构的准则,以及对这些准则的描述。
(软件架构指软件系统的顶层结构)
架构设计的目的:为了解决复杂度带来的问题。#
复杂度来源#
-
高性能
-
单机的复杂度
- 考虑多进程、多线程、进程间通信、多线程并发等。
-
集群的复杂度
-
任务分配
- 一个任务分配器多个业务服务器,逐渐演变到多个任务分配器和多个业务服务器。
-
任务分解
-
将复杂的业务系统拆分成小而简单的多个系统配合的业务系统
-
任务分解提升性能的原因
- 简单的系统更容易做到高性能
- 可以针对单个任务进行扩展,也并且系统拆分越细越好,因为过多的子系统,用户一次访问需要多个迭代地调用,才能得到响应结果。
-
-
-
-
高可用
-
通过 “冗余” 来实现高可用。
-
计算高可用
-
存储高可用
-
状态决策高可用
- 独裁式:仅存在一个决策者,其他都是上报者。
- 协商式:两个独立的个体通过交流信息,然后根据规则进行决策。
- 民主式:多个独立的个体通过投票的方式进行状态决策。
-
-
-
可扩展性
-
为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或仅需要少量修改就可以支持,无需整个系统重构或重建。
- 正确预测变化
- 完美封装变化
-
-
低成本
-
只有创新才能达到低成本目标
-
例子
- NoSQL (Redis,Memcache 等) 的出现是为了解决关系型数据库无法应对高并发访问带来的访问压力。
- 全文搜索引擎 (Sphinx、Elasticsearch、Solr) 的出现是为了解决关系型数据库 like 搜索的低效问题。
- Hadoop 的出现时为了解决传统文件系统无法应对海量数据存储和计算的问题。
-
-
-
安全
-
功能安全
- 常见的 XSS 攻击、CSRF 攻击、SQL 注入、Windows 漏洞、密码破解等,本质上是因为系统存在漏洞,才让黑客有机可乘。
-
架构安全
-
主要依靠防火墙,为隔离网络
-
DDos
分布式拒绝服务攻击 (英文意思是 Distributed Denial of Service,简称 DDoS) 是指处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。由于攻击的发出点是分布在不同地方的,这类攻击称为分布式拒绝服务攻击,其中的攻击者可以有多个。
-
-
-
-
规模
- 功能越来越多,导致系统复杂度指数级上升
- 数据越来越多,系统复杂度发生质变
架构设计原则#
合适原则:合适优于业务领先#
简单原则:简单优于复杂#
演化原则:演化优于一步到位#
高性能架构#
存储高性能#
-
关系数据库
-
读写分离:将访问压力分散到集群中的多个节点,但是并没有分散存储压力。
-
分库分表:既可以分散访问压力,又可以分散存储压力。
-
业务分库:分散存储和访问压力
-
引入问题
-
join 操作问题
- 原本在同一数据库的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
-
事务问题
- 原本在同一数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。
-
成本问题
- 原来只需要 1 台服务器就能处理的事情,可能需要扩展为 3 台或者更多。
-
-
-
分表
-
垂直分表:适合将表中某些不常用且占了大量空间的列拆分出去。
-
引入问题
- 因为表信息被分散到多个表中,导致原来一次查询,现在可能需要 2 次或者更多。
-
-
水平分表:适合表行数据特别大的表,例如单表行记录超过 5000 万条。
-
引入问题
-
路由:水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算
- 范围路由
- Hash 路由
- 配置路由
-
count () 操作
- 原始对表进行 count () 操作,切分后需要 表个数 * count (*)
-
记录数表
- 新建一张表,记录每次插入或删除子表数据后,表的记录数。
-
-
-
-
-
实现方法
-
程序代码封装:在代码中抽象一个数据访问层来实现读写分离、分库分表
-
中间件封装:独立一套系统出来,实现读写分离和分库分表操作
-
实现复杂度:分库分表比读写分离要复杂的多。
- 读写分离实现时,只需识别 SQL 操作是读操作还是写操作接口,即通过关键字 SELECT、UPDATE、INSERT、DELETE 就可以判断。
- 分库分表除了要判断操作类型,还需要判断 SQL 中具体要操作的表、操作函数(count、order by、group by),然后根据不同的操作进行不同的处理。
-
-
存在缺点
- 关系数据库存储的是行记录,无法存储数据结构
- 关系数据库的表结构 schema 扩展很不方便
- 关系数据库在大数据场景下 I/O 较高
- 关系数据库的全文搜索功能比较弱
-
-
NoSQL
-
关系数据库
-
NoSQL 的本质是牺牲 ACID 特性中的某个或某些特性,作为关系数据库的补偿。
-
常用的 NoSQL 方案有如下 4 类
-
K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
-
文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
文档数据库最大特点是 no-schema, 可以存储和读取任意的数据,数据格式一般为 JSON.
优势:
新增字段简单;
历史数据不会出错;
可以很容器存储复杂数据。
-
列式数据库:解决关系数据库大数据场景下 I/O 问题,以 HBase 为代表。
例如,统一某城市超重人员的数量,只需读取体重这一列的数据即可。
-
全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。
-
-
-
缓存
-
基本原理:将可能重复使用的数据放到内存中,一次生成,多次使用,避免每次使用都去访问存储系统。
-
面临的问题
- 缓存穿透:访问了缓存中不存在的数据,导致业务系统需要再次访问数据库,导致对数据库服务器造成压力。
- 缓存击穿:单个高热数据过期的瞬间,数据访问量较大,未命中缓存后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力
- 缓存预热:系统启动前,提前将相关的缓存数据直接加载到缓存系统。
- 缓存雪崩:由于大量的热数据设置了相同或接近的过期时间,导致缓存在某一时刻密集失效,大量请求全部转发到 DB,导致存储系统受到巨大压力,最终导致系统崩溃
-
计算高性能#
-
单服务器高性能
-
PPC (Process per Connection),每次有新的连接就新建一个进程专门处理这个连接的请求。
-
prefork:提前创建进程,便于后续直接使用
-
TPC (Thread per Connection),每次有新的连接就新建一个线程专门处理这个连接的请求。
-
prethread:提前创建线程,便于后续直接使用
-
Reactor (非阻塞同步网络模型):核心组件包括 Reactor 和处理资源池,其中 Reactor 负责监听和分配事件,处理资源池负责处理事件。
- 1. 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程。
- 2. 子进程 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。
- 3. 当有新的事件发生时,subReactor 会调用连接对应的 Handler 来进行响应。
- 4.Handler 完成 read ——> 业务处理 ——> send 的完整业务流程。
- 图片
-
Proactor (异步网络模型):核心组件包括 Proactor 和异步操作处理器。
- 1.Proactor Initiator 负责创建 Proactor 和 Handler,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核。
- 2.Asynchronous Operation Processor 负责处理注册请求,并完成 I/O 操作。
- 3.Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor。
- 4.Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理。
- 5.Handler 完成业务处理,Handler 也可以注册新的 Handler 到内核进程。
- 图片
-
-
集群高性能
-
本质:通过增加更多的服务器来提升系统整体的计算能力
-
复杂性:增加任务分配器,以及选择一个合适的任务分配算法。(任务分配器,更通俗的叫法是 负载均衡器)
-
负责均衡分类
- DNS 负载均衡:实现地理级别的负载均衡。例如北方用户访问北京的机房;南方用户访问深圳的机房。
- 硬件负载均衡:通过单独的硬件设备来实现集群级别的负载均衡。这类设备和路由器交换机类似,可以理解为一个用于负载均衡的基础网络设备。
- 软件负载均衡:通过负载均衡软件实现机器级别的负载均衡。
-
负载均衡架构
- 实际使用的时候,可以灵活地使用上述三种负载均衡方法,首先通过 DNS 负载均衡找到最近城市的服务器 ip, 通过硬件负载均衡找到城市对应的集群组,最后通过软件负载均衡在集群组内找到所需的集群。
-
负载均衡的算法
- 任务平分类:轮询、加权轮询
- 负载均衡类:负载最低优先
- 性能最优类:响应时间最短优先
- Hash 类:根据任务的某些关键信息进行 hash 运算,从而映射到指定主机
-
高可用架构#
CAP#
- CAP 理论
- BASE 理论
高可用的本质是通过冗余来实现#
存储高可用#
-
常用的高可用存储架构有主备、主从、主主、集群、分区。
-
主备复制:客户端的操作都是通过主机完成,备机仅起到备份作用,不参与实际业务读写操作。
-
主从复制:主机负责读写操作,从机只负责读操作,不负责写操作。
-
主备倒换与主从倒换
-
设计关键
- 1. 主备间状态判断:包括状态传递的渠道和状态检测的内容。
- 2. 倒换决策:倒换时机、倒换策略、自动程度。
- 3. 数据冲突解决:故障主机恢复后,数据如何同步。
-
常见架构
- 互连式:主备直接建立状态传递的渠道。
- 中介式:主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息。
- 模拟式:主备之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写的响应情况来判断主机的状态。
-
-
主主复制
- 概念:两台主机都是主机,互相将数据复制给对方,客户端可以任意挑选其中的一台机器进行读写操作。
- 很多数据不能双向复制:例如用户注册 ID、库存等。
-
数据集群
-
概念:集群就是多台机器组合在一起形成一个统一的系统。(主备、主从、主主架构本质上隐含一个假设:主机能够存储所有数据。)
-
集群分类
-
数据集中集群
-
数据分散集群
- Elasticsearch 集群
-
-
分布式事务算法
-
目的:保证分散在多个节点上的数据统一提交或回滚,以满足 ACID 要求。
-
二阶段提交 (Two-Phase Commit Protocol,2PC)
- Commit 请求阶段和 Commit 提交阶段
-
三阶段提交 (Three-Phase Commit Protocol,3PC)
- 提交判断阶段;提交准备阶段;提交执行阶段。(针对二阶段提交算法存在的单点故障问题,引入准备阶段,当协调者故障后,参与者可以通过超时提交来避免一直阻塞。)
-
-
分布式一致性算法
-
目的:保证同一份数据在多个节点上的一致性
-
机制:复制状态机
- 副本:多个分布式服务器组成一个集群,每个服务器都包含完整状态机的一个副本。
- 状态机:状态机接受输入,然后执行操作,将状态改变为下一个状态。
- 算法:使用算法来协调各个副本的处理逻辑,使得副本的状态机保持一致。
-
算法:Paxos、Raft、ZAB
-
-
-
数据分区
-
概念:指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。
-
需要考虑的问题
-
数据量:数据量越大,分区规则会越复杂,考虑的情况也越多。
-
分区规则:洲际分区、国家分区、城市分区
-
复制规则
- 集中式:存在一个总的备份中心,所有分区的数据备份到备份中心。
- 互备式:每个分区备份另外一个分区的数据。
- 独立式:每个分区自己有独立的备份中心。
-
-
-
计算高可用#
-
主备
- 冷备:备机业务未启动
- 热备:备机业务已启动
-
主从
-
对称集群:集群中每个服务器的角色一致,可以执行所有任务。
-
非对称集群:集群中的服务器分为多个不同的角色,不同的角色执行不同的任务。
业务高可用#
-
异地多活
-
目的:应对系统级的故障。
-
架构:同城异区、跨城异地、跨国异地。
-
设计技巧
-
1. 保证核心业务的异地多活
-
2. 核心数据的最终一致性
-
3. 采用多种手段同步数据
- 消息队列
- 二次读取
- 存储系统同步
- 回溯读取
- 重新生成数据方式
-
4. 只保证绝大部分用户的异地多活
-
-
设计步骤:业务分级、数据分类、数据同步、异常处理
-
-
接口级的故障应对方案
-
降级:将某些业务或接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。
- 系统后门降级
- 独立降级系统
-
熔断:通过设定阈值,来应对依赖的外部系统故障的情况。
-
限流:只允许系统能够承受的访问量进入,超出部分的请求将丢弃。
- 基于请求限流
- 基于资源限流
-
排队:让用户等待很长时间,才能得到处理或长时间等待后仍然无法得到响应。
-
XMind: ZEN - Trial Version