https://segmentfault.com/a/1190000041783756?utm_source=sf-similar-article
一、基础架构
框架与架构
框架关注的是“规范”,架构关注的是“结构”。
框架是一整套开发规范。
软件架构指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。
架构定义 4R架构
4R:软件系统的顶层(Rank)结构,它定义了系统由哪些角色(Role)组成,角色之间的关系(Relation)和运作规则(Rule)。
架构设计的目的
主要目的是为了解决软件系统复杂度带来的问题。
- 遵循这条准则能够让“新手”架构师心中有数,而不是一头雾水。
- 遵循这条准则能够让“老鸟”架构师有的放矢,而不是贪大求全。
复杂性来源
高性能
单台计算机内部为了高性能带来的复杂度。
- 进程和线程
多台计算机集群为了高性能带来的复杂度
- 任务分配,负载均衡
- 任务分解,微服务
衡量指标
- 响应时间、TPS、服务器资源利用率等
高可用
本质上都是通过“冗余”来实现高可用。
- 高性能增加机器目的在于“扩展”处理性能;
- 高可用增加机器目的在于“冗余”处理单元。
计算高可用
- “计算”指的是业务的逻辑处理。计算有一个特点就是无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的
存储高可用
- 存储与计算相比,有一个本质上的区别:将数据从一台机器搬到到另一台机器,需要经过线路进行传输。
- 存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。
高可用状态决策
- 无论是计算高可用还是存储高可用,其基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用
- 独裁式、协商式、民主式
可扩展性
系统为了应对将来需求变化而提供的一种扩展能力。
设计具备良好可扩展性的系统,有两个基本条件:
- 正确预测变化
- 原则:只预测 2 年内的可能变化,不要试图预测 5 年甚至 10 年后的变化。
- 完美应对变化
- 方案一:提炼出“变化层”和“稳定层”,核心思想是通过变化层来隔离变化
- 方案二:提炼出“抽象层”和“实现层”,核心思想就是通过实现层来封装变化。
- 原则:1 写 2 抄 3 重构原则
其他来源
成本
- 往往只有“创新”才能达到低成本目标。
- 低成本本质上是与高性能和高可用冲突的,所以低成本很多时候不会是架构设计的首要目标,而是架构设计的附加约束。
安全
- 功能安全:其实就是“防小偷”
- 架构安全:就是“防强盗”
规模
- 规模带来复杂度的主要原因就是“量变引起质变”
- 数据越来越多,系统复杂度发生质变
架构设计原则
合适原则,“合适优于业界领先”
简洁原则,“简洁优于复杂”
演进原则,“演化优于一步到位”
避坑
- 时刻提醒自己不要贪大求全
- 避免盲目照搬大公司的做法
最佳实践
- 应该认真分析当前业务的特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要
- 在运行过程中不断完善架构,不断随着业务演化架构
架构设计流程
识别复杂度
- 将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题。
- 设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,
- 设计目标设定为峰值的 4 倍是根据业务发展速度来预估的,不是固定为 4 倍,不同的业务可以是 2 倍,也可以是 8 倍,但一般不要设定在 10 倍以上,更不要一上来就按照 100 倍预估。
设计备选方案
详细设计方案
二、高性能架构
高性能关系型数据库
读写分离
读写分离的基本原理是将数据库读写操作分散到不同的节点上。
散了数据库读写操作的压力,但没有分散存储压力。
- 主从复制延迟
- 写操作后的读操作指定发给数据库主服务器
- 读从机失败后再读一次主机
- 关键业务读写操作全部指向主机,非关键业务采用读写分离
- 分配机制
- 程序代码封装 如 TDDL
- 中间件封装 如 MySQL Router
分库分表
单库数据量大的风险
- 数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降。
- 数据文件会变得很大,数据库备份和恢复需要耗费很长时间。
- 数据文件越大,极端情况下丢失数据的风险越高
分库
按照业务模块将数据分散到不同的数据库服务器。
优点:分散存储和访问压力
问题在于:
- join 操作问题
- 事务问题
- 成本问题
分表
单表数据量过大。垂直分表和水平分表。
- 垂直分表
- 垂直分表适合将表中某些不常用且占了大量空间的列拆分出去
- 垂直分表引入的复杂性主要体现在表操作的数量要增加。
- 水平分表
- 路由
- 范围路由
- Hash路由
- 配置路由
- join 操作
- count() 操作
- count() 相加
- 记录数表
- order by 操作
- 路由
高性能NoSQL数据库
常见的 NoSQL 方案
- K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表
- 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表
- 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表
- 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表
高性能缓存架构
单纯依靠存储系统的性能提升不够的,典型的场景
- 需要经过复杂运算后得出的数据,存储系统无能为力
- 读多写少的数据,存储系统有心无力
缓存的架构设计要点
缓存穿透
- 缓存穿透是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。
- 对异常查询直接设置默认值到缓存中。
缓存雪崩
- 缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况。
- 更新锁机制
- 后台更新机制
缓存热点
-
如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大
-
复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
-
设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值
高可用架构之FMEA方法
FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等
在架构设计领域,FMEA 的具体分析方法
- 给出初始的架构设计图
- 假设架构中某个部件发生故障
- 分析此故障对系统功能造成的影响
- 根据分析结果,判断架构是否需要进行优化
七、高可用架构设计之异地多活
两个标准
- 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
- 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
代价很高
- 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
- 成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。
架构模式
同城异区
- 结合复杂度、成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构。
- 关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。架构设计上可以将两个机房当作本地机房来设计,无须额外考虑。
跨城异地
- 跨城异地距离较远带来的网络传输延迟问题,给异地多活架构设计带来了复杂性,如果要做到真正意义上的多活,业务系统需要考虑部署在不同地点的两个机房,在数据短时间不一致的情况下,还能够正常提供业务。
- 关键在于数据不一致的情况下,业务不受影响或者影响很小,这从逻辑的角度上来说其实是矛盾的,架构设计的主要目的就是为了解决这个矛盾。
- 这就引入了一个看似矛盾的地方:数据不一致业务肯定不会正常,但跨城异地肯定会导致数据不一致。
- 如果是强一致性要求的数据,例如银行存款余额、支付宝余额等,这类数据实际上是无法做到跨城异地多活的。
跨国异地
- 为不同地区用户提供服务
- 只读类业务做多活
技巧
- 保证核心业务的异地多活
- 保证核心数据最终一致性
- 尽量减少异地多活机房的距离,搭建高速网络
- 尽量减少数据同步,只同步核心业务相关的数据
- 保证最终一致性,不保证实时一致性
- 采用多种手段同步数据
- 消息队列方式:对于账号数据,由于账号只会创建,不会修改和删除(假设我们不提供删除功能),我们可以将账号数据通过消息队列同步到其他业务中心。
- 二次读取方式:第一次读取本地,本地失败后第二次读取对端
- 存储系统同步方式:对于密码数据,由于用户改密码频率较低,而且用户不可能在 1 秒内连续改多次密码,所以通过数据库的同步机制将数据复制到其他业务中心即可,用户信息数据和密码类似。
- 回源读取方式:当用户在 A 中心登录后,然后又在 B 中心登录,B 中心拿到用户上传的 session id 后,根据路由判断 session 属于 A 中心,直接去 A 中心请求 session 数据即可;反之亦然,A 中心也可以到 B 中心去获取 session 数据。
- 重新生成数据方式:对于“回源读取”场景,如果异常情况下,A 中心宕机了,B 中心请求 session 数据失败,此时就只能登录失败,让用户重新在 B 中心登录,生成新的 session 数据。
- 只保证绝大部分用户的异地多活
- 核心思想:采用多种手段,保证绝大部分用户的核心业务异地多活!
四步走
业务分级
数据分类
数据同步
异常处理
异地多活,是富家子的操作,追求最后的 0.00001 的收益。大厂可以搞搞,小厂如果不是对可用性有特别特别高的需求还是算了吧。
架构设计 8-高可用架构设计之故障处理
核心思想
- 优先保证核心业务
- 优先保证绝大部分用户
应对方法
降级
熔断
限流
排队
限流桶算法
漏桶
将请求放入“桶”(消息队列等),业务处理单元(线程、进程和应用等)从桶里拿请求处理,桶满则丢弃新的请求。
- 设计关键点:
- 流入速率不固定:可能瞬间流入非常多的请求,例如 0 点签到、整点秒杀。
- 匀速 (极速) 流出:这是理解漏桶算法的关键,也就是说即使大量请求进入了漏桶,但是从漏桶流出的速度是匀速的,速度的最大值就是系统的极限处理速度。需要注意的是:如果漏桶没有堆积,那么流出速度就等于流入速度,这个时候流出速度就不是匀速的。这样就保证了系统在收到海量请求的时候不被压垮,这是第一层的保护措施。
- 桶满则丢弃请求:这是第二层保护措施,也就是说漏桶不是无限容量,而是有限容量,例如漏桶最多存储 100 万个请求,桶满了则直接丢弃后面的请求。
- 优点
- 实现简单
- 提供双层保护措施
- 缺点
- 突发大量流量时丢弃的请求较少,因为漏桶本身有缓存请求的作用。
- 桶大小动态调整比较困难(例如 Java BlockingQueue),需要不断的尝试才能找到符合业务需求的最佳桶大小。
- 无法精确控制流出速度,也就是业务的处理速度。
- 适用场景:主要适用于瞬时高并发流量的场景(例如刚才提到的 0 点签到、整点秒杀等)。
令牌桶
令牌桶算法和漏桶算法的不同之处在于,桶中放入的不是请求,而是“令牌”,这个令牌就是业务处理前需要拿到的“许可证”。也就是说,当系统收到一个请求时,先要到令牌桶里面拿“令牌”,拿到令牌才能进一步处理,拿不到就要丢弃请求。
- 设计关键点
- 有一个处理单元往桶里面放令牌,放的速率是可以控制的。
- 桶里面可以累积一定数量的令牌,当突发流量过来的时候,因为桶里面有累积的令牌,此时的业务处理速度会超过令牌放入的速度。
- 如果令牌不足,即使系统有能力处理,也会丢弃请求。
- 优点:可以动态调整处理速率,实现更加灵活。
- 缺点
- 突发大量流量的时候可能丢弃很多请求,因为令牌桶不能累积太多令牌。
- 实现相对复杂。
- 适用场景
- 一种是需要控制访问第三方服务的速度,防止把下游压垮,例如支付宝需要控制访问银行接口的速率;
- 一种是需要控制自己的处理速度,防止过载,例如压测结果显示系统最大处理 TPS 是 100,那么就可以用令牌桶来限制最大的处理速度。
架构设计 9-可扩展架构之分层架构
所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
不同的拆分方式,本质上决定了系统的扩展方式。
-
面向流程拆分 分层架构
-
面向服务拆分 SOA & 微服务
-
面向功能拆分 微内核架构
核心要点
- 需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构
- 分层架构之所以能够较好地支撑系统扩展,本质在于隔离关注点(separation of concerns),即每个层中的组件只会处理本层的逻辑
- 并不是简单地分层就一定能够实现隔离关注点从而支撑快速扩展,分层时要保证层与层之间的依赖是稳定的,才能真正支撑快速扩展。
- 分层结构的另外一个特点就是层层传递,也就是说一旦分层确定,整个业务流程是按照层进行依次传递的,不能在层之间进行跳跃。
架构设计 10-可扩展架构之面向服务拆分架构
微服务与 SOA 的关系
三种主要观点:
- 微服务是 SOA 的实现方式
- 微服务是去掉 ESB 后的 SOA
- 微服务是一种和 SOA 相似但本质上不同的架构理念
服务粒度
- SOA 的服务粒度要粗一些
- 微服务的服务粒度要细一些
服务通信
- SOA 采用了 ESB 作为服务间通信的关键组件,负责服务定义、服务路由、消息转换、消息传递,总体上是重量级的实现
- 微服务推荐使用统一的协议和格式,例如,RESTful 协议、RPC 协议,无须 ESB 这样的重量级实现。
服务交付
- SOA 对服务的交付并没有特殊要求,因为 SOA 更多考虑的是兼容已有的系统
- 微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。
应用场景
- SOA 更加适合于庞大、复杂、异构的企业级系统,这也是 SOA 诞生的背景。
- 微服务更加适合于快速、轻量级、基于 Web 的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于 Web,虽然开发技术可能差异很大(例如,Java、C++、.NET 等),但对外接口基本都是提供 HTTP RESTful 风格的接口,无须考虑在接口层进行类似 SOA 的 ESB 那样的处理。
架构设计策略之寻找够用的设计
最优的设计策略不是追求让架构设计达到完美的状态,应该清楚这是不可能,因为在现实开发中会有时间、资金成本、技术、知识、业务变化等限制导致架构设计不可能做到完美。
因此,我们的目标是找到一个够用的设计,这个架构设计能适应当前企业环境(满足利益相关方的需求等)和灵活应对业务变化。
寻找够用的架构设计,可以参考如下策略:
-
快速验证解决方案:解决方案验证的速度越快,就越快找到合适的架构设计,项目就能越快受益。
-
设法降低风险:架构设计失败是很严重的问题,必须时刻考虑可能出现的风险,并根据风险来进行设计。
-
努力简化问题:运用分治、知识和抽象等方法,去理解和简化复杂性不断增长的问题。
-
快速迭代学习:运用思维沉淀循环快速学习,快速积累知识,就能快速实现目标。
-
同时考虑问题的解法和证法:能解答问题的设计方案可能有很多,但能证明适用有效的设计方案,可能寥寥无几。因此,需要同时考虑问题的解法和证法,以便高效地找到够用的设计。