架构设计学习笔记

https: segmentfault com a 1190000041783756?utm_source=sf-similar-article 一、基础架

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 那样的处理。

架构设计策略之寻找够用的设计

最优的设计策略不是追求让架构设计达到完美的状态,应该清楚这是不可能,因为在现实开发中会有时间、资金成本、技术、知识、业务变化等限制导致架构设计不可能做到完美。

因此,我们的目标是找到一个够用的设计,这个架构设计能适应当前企业环境(满足利益相关方的需求等)和灵活应对业务变化。

寻找够用的架构设计,可以参考如下策略:

  • 快速验证解决方案:解决方案验证的速度越快,就越快找到合适的架构设计,项目就能越快受益。

  • 设法降低风险:架构设计失败是很严重的问题,必须时刻考虑可能出现的风险,并根据风险来进行设计。

  • 努力简化问题:运用分治、知识和抽象等方法,去理解和简化复杂性不断增长的问题。

  • 快速迭代学习:运用思维沉淀循环快速学习,快速积累知识,就能快速实现目标。

  • 同时考虑问题的解法和证法:能解答问题的设计方案可能有很多,但能证明适用有效的设计方案,可能寥寥无几。因此,需要同时考虑问题的解法和证法,以便高效地找到够用的设计。