架构
系统: 一群有关联的个体 , 规则, 能力(产生了新能力)
子系统
模块:逻辑角度 -> 组件复用
组件: 物理角度 -> 单元分离
框架: 组件规范:mvc,等
架构:结构
1. 软件架构:指软件系统的顶层结构。
首先,系统是一群关联的个体组成,这些个体可以是子系统, 模块, 组件等;
其次, 系统中的个体需要根据某种规则运作, 架构需要明确个体运作和协作的规则;
第三,顶层结构的说法可以更好的区分系统和子系统。
2. 历史背景
三难
机器语言 -> 汇编语言 -> 高级语言
3.目的
解决软件系统复杂度的问题。
找复杂点
性能, 高可用, 扩展性, 存储高可靠, 安全性,成本。
4. 复杂度来源:高性能
单机:操作系统
多机:任务分配, 任务分解
5. 复杂度来源:高可用 -> 冗余
无中断
计算高可用:
- 任务分配器
- 连接(与业务处理器)
- 算法(分配)
存储高可用:
传输线路(故障)
状态决策:
- 独裁式
- 协商式
- 民主式: -> 脑裂 -> 解决:选举超一半
6. 复杂度来源:可扩展性
- 正确预测变化
- 完美封装变化
变化层, 接口稳定层
抽象层, 实现层
7. 复杂度来源:低成本, 安全, 规模
低成本 -> 附加约束
- 引入新事物
- 创造新事物
安全
- 功能安全:SQL注入, 代码漏洞
- 架构安全: 防火墙
规模: -> 量变引起质变 而带来的复杂度:
- 功能越来越多,系统复杂度指数增长
- 数据越来越多,系统复杂度发生质变
8. 架构设计原则
选择(不确定性)
- 合适原则: 合适由于业界领先
- 简单原则:简单优于复杂(结构复杂, 逻辑复杂,算法复杂)
- 演化原则:演化由于一步到位
9. 案例
10. 架构设计流程:识别复杂度
列出复杂度问题,排序依次解决;
理解需求,找复杂度 -> 可用排查法,从不同角度逐一分析
每秒TPS , 平均值, 峰值(2-4倍平均值)
设计目标用峰值计算。
复杂度
- 高性能
- 高可用
- 扩展性
TPS:写入
QPS:查询
eg. 目前TPS 是115, QPS是1150,
则峰值 x 3, TPS是345, QPS是3450 -> 这个量级不要求高性能;
预留后续发展, 按 峰值 x 4, TPS是 1380, QPS是13800 -> 高性能
不同的公司会有不同的复杂度分析: 安全,或成本
eg. 消息队列
前浪微博:复杂度体现在
- 高性能消息读取
- 高性能消息写入
- 高可用消息存储
- 高可用消息读取
11. 架构流程设计:设计备选方案
常见错误:
1.设计最优秀的方案
2. 只做一个方案
应设计3-5个备选方案。
备选方案差异要比较明显
备选方案的技术不要只限于已熟悉的技术
备选方案不用过于详细 -> 应关注技术选型,而不是细节
eg. 设计备选方案 - > 高性能读取,写入,高可用存储,读取。
1. 用kafka
2. 集群 mysql 存储
高性能读取,写入 -> Java, 基于netty开发消息队列
高可用存储,读取 -> 服务器的主备方案, mysql的主备复制。
3. 集群 自研存储方案
可参考kafka,自己实现一套存储和复制方案。
12. 架构设计流程:评估和选择备选方案
- 最简派
- 最牛派
- 最熟派
- 领导派
分场景使用
1. 360度环评表: 列出我们需要关注的质量属性点,分别从这些点的维度评估每个方案,再综合选合适当时情况的最优方案。
常见的方案质量属性点:性能,可用性, 硬件成本, 项目投入,复杂度,安全性,可扩展性,可维护性等。
2. 按优先级选择
13. 架构设计流程: 详细方案设计
确定方案 的关键细节
详细设计方案阶段,可能遇到一种极端情况,发现备选方案不可行。
一般情况下,主要原因是涉及备选方案时遗漏了关键技术点或关键质量属性。
这种情况下,可通过以下方式避免:
- 架构师不但要备选方案的设计和选型,还要对备选方案的关键细节有深入理解
- 通过分步骤,分阶段,分系统,降低复杂度
- 如果方案本身复杂,可采取设计团队,博采众长,汇集大家的智慧。
14. 高性能数据库:读写分离
本质:将访问压力分散到集群中的多个节点,但没分散存储压力。
基本原理:将数据库读写操作分散到不同的节点上。
主 从 集群: 主负责读/写,从负责读。
不同于 主 备
基本实现:
- 数据库服务器,搭建主从集群,一主一从或一主多从 都可以。
- 主机负责读写,从机负责读
- 数据库主机通过复制将数据同步到从机,每个数据库都存了所有数据
- 业务服务器将写发给主数据库,读发给从数据库。
有两个复杂点:1. 主从复制延迟; 2. 分配机制
1. 复制延迟
先假设延迟1秒,数据写入主库后立刻访问从库,读取不到最新数,例如注册,会有业务问题。
它的常规解决方法:
- 写操作后的读操作指定发主数据库 -> 和业务强绑定,对业务侵入和影响较大
- 读从机失败后,再读一次主机 -> 即二次读取,无业务绑定,只需对底层数据库的访问封装,代价小,但若有很多二次读取,将增加主机的读取压力
- 关键业务读写都指向主机,非关键业务读写分离
2. 分配机制
将读写区分,访问不同的数据库,一般有两种方式:程序代码封装和中间件封装
2.1 程序代码封装:在代码中抽象一个数据访问层,实现读写分离和数据库连接管理。
特点:实现简单,可根据业务做定制化功能;
每个语言实现一次;
故障情况下,如果主从发生切换,则需要所有系统都修改配置并重启;
eg. 淘宝的 TDDL(Taobao Distributed Data Layer)
基本原理:基于集中式的配置Jdbc datasource实现: 有主备,读写分离
基本架构
2.2 中间件封装
指独立出一套系统,实现读写分离和数据库服务器连接的管理。
一般建议,程序语言封装或用成熟中间件
Mysql Router,
Atlas : 基于Mysql Proxy
思考:
读写分离一般用于实现什么场景?支撑多的业务规模?
读多写少,实时性要求不高
15. 高性能数据库集群:分库分表
分散存储
数据量 从千万到亿 , 就会有单台瓶颈。
业务分库
按业务分到不同的数据库 -> 分库能支撑百万甚至千万规模业务。
存在问题:join 问题(不同库), 事务, 成本等。
分表
垂直拆分, 水平拆分
垂直:
水平:
复杂性
1. 路由 - 范围路由(分布不均匀), hash路由(分布均匀), 配置路由(用单独的表记录路由信息)
2. join
3. count (count相加,记录数表)
4. order by
实现方式:代码封装,中间件封装
16. 高性能NoSQL
not only SQL, 弥补数据库缺陷
关系数据库缺点:
- 存储的行记录,无法存数据结构 - 无法直接存列表
- schema 扩展不方便: 操作不存在的列会报错,扩充列要DDL,麻烦
- 大数据场景下, I/O较高; 单列统计 ,会读取整行
- 全文搜索功能较弱; like 整表扫描, 性能低
NoSQL分4类
- k-v存储:解决无法存数据结构的问题, 以Redis为代表
- 文档数据库:解决强schema约束问题,以MongoDB为代表
- 列式数据库:解决大数据场景下I/O问题,以HBase为代表
- 全文搜索索引:解决全文搜索性能问题,以Elasticsearch为代表
Redis
eg, lpop 移除list第一个元素。
缺点:不支持完整的ACID, 只保证隔离性和一致性,无法保证原子性和持久性。
文档数据库
特点:no-schema,可存储和读取任意数据,大部分是Json格式存储
优点:
- 新增字段简单
- 历史数据不会出错
- 更易存储复杂数据
适合场景:电商和游戏
缺点:不支持事务,无法join操作。
列式数据库
对比,关系数据库,行式存储:读多列,效率高,能一次完成对一行对个列写操作。
列式数据库:对某个列统计,节省I/O;有更高的压缩比(行:3:1 - 5:1; 列: 8:1, 30:1)
一般将列式存储用在大数据分析和统计场景;主要针对部分单列操作,且写入后无需再更新,删除;
全文搜索引擎
基本原理:倒排索引,是一个索引方法,是建立单词到文档的索引。
使用方式:将关系数据转换为文档数据Json。
Elasticsearch是分布式文档存储方式,每个字段的所有数据默认被索引。
17.高性能缓存架构
某些复杂场景,单靠存储系统,性能提升是不够的。
a. 要经过复杂运算后得到的数据
b. 读多写少的数据
缓存-基本原理:将可能重复使用的数据放到内存,一次生成,多次使用,避免每次使用都访问存储系统。
mechache 单台支持50000 TPS以上。
缓存-架构设计要点:
1. 缓存穿透:指缓存中没数据,都查了存储系统。
有两种情况:
a. 存储数据不存在 - 解决:可设置一个默认值
b. 缓存数据生成耗费大量的时间或资源
2. 缓存雪崩:指缓存失效后,引起系统性能急剧下降的情况
解决:
a. 更新锁机制:对缓存更新操作加锁保护,集群时需要设置分布式锁。
b. 后台更新:由后台线程更新缓存,而不是业务线程;缓存本身设置有效期永久,后台定时更新;
当内存不足时,会踢掉数据,可有两种办法:
1>. 后台除定时更新缓存,还要频繁读缓存
2>. 业务线程发现缓存失效,通过消息队列发一条消息通知后台线程更新缓存。
还可以用后台更新进行缓存预热。
3. 缓存热点
对于一些特别热点的数据,大部分业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器压力也很大。
缓存热点的解决方案是:复制多份缓存副本,将请求分散到多个缓存服务器,减轻缓存热点导致后台服务器压力。
一个细节要注意:不同的缓存副本不要设置统一的过期时间,应设置一个过期时间范围,不同副本过期时间是指定范围的随机值。
实现方式:
- 程序代码的中间层方式实现
- 独立中间件实现
18. 单服务器高性能模式:PPC与TPC
磁盘,操作系统,cpu,内存,网络,编程语言,架构, 都可能影响达到高性能。
架构师要考虑:高性能架构的设计
集中两个点
- 尽量提升单服务性能,将单服务器性能发挥到极致
- 如果单台服务器无法支持性能,设置服务器集群方案
还和编码有关
架构设计决定系统性能的上限,实现细节决定下限。
单服务器性能关键之一:服务器采用并发模型。
关键设计点:
- 服务器如何管理连接
- 服务器如何处理请求
这两个设计点都和操作系统的I/O模型及进程模型相关。
I/O模型: 阻塞, 非阻塞, 同步,异步
进程模型: 单进程, 多进程, 多线程
单服务器高性能模式:PPC, TPC
PPC
: process per connection ,指每次有新的连接就新建一个进程,去专门处理这个连接的请求。
适合连接没那么多的情况。
缺点:
- fork代价高
- 父子进程通信复杂
- 支持的并发连接数量有限
一般情况下,PPC最多能处理的并发连接数就几百。
prefork -> 提前创建进程
TPC
Thread per connection. 每次有新的连接就新建一个线程,专门处理这个连接的请求。
TPC基本流程:
略
TCP虽然解决了fork代价高和进程通信复杂的问题,但有其他问题:
- 高并发性能问题
- 没有进程仅通信,但线程间的互斥和共享,易导致死锁
- 多线程相互影响,一个线程异常,整个进程退出
因此,几百连接的场景,更多用PPC
prethread -> 提前创建线程
19.单服务器高性能模式:Reactor与Proactor
PPC和TPC无法支持高并发
Reactor
资源复用,进程池
read阻塞 -> 改为了非阻塞,进程轮询多个连接(当连接太多,轮询效率低)
再改进 -> 只有连接上有数据,进程才去处理 ->