美文网首页
架构笔记(一)

架构笔记(一)

作者: 追风还是少年 | 来源:发表于2023-10-09 16:05 被阅读0次

架构设计的关键思维是判断和取舍,程序设计的关键思维是逻辑和实现

架构定义

要想准确地理解架构的定义,关键就在于把三组容易混淆的概念梳理清楚:

  • 系统与子系统
    一个系统的架构,只包括顶层这一个层级的架构,而不包括下属子系统层级的架构
  • 模块与组件
    从业务逻辑的角度来拆分系统后,得到的单元就是“模块”;从物理部署的角度来拆分系统后,得到的单元就是“组件”。划分模块的主要目的是职责分离;划分组件的主要目的是单元复用
  • 框架与架构
    框架关注的是“规范”,架构关注的是“结构”。
    框架是一整套开发规范,架构是某一套开发规范下的具体落地方案,包括各个模块之间的组合关系以及它们协同起来完成功能的运作规则

架构设计的历史背景

  • 结构化程序设计
    自顶向下、逐步细化、模块化

架构设计的目的

为了解决软件系统复杂度带来的问题

架构复杂度来源

  • 高性能
    软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
    集群的复杂度:任务分配、任务分解
    简单的系统更加容易做到高性能、可以针对单个任务进行扩展。
  • 高可用
    计算高可用、存储高可用。
    高可用状态决策: 独裁式、协商式、民主式
  • 可扩展性
    可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。
    正确预测变化、完美封装变化:
    正确预测变化:不能每个设计点都考虑可扩展性、不能完全不考虑可扩展性、所有的预测都存在出错的可能性。
    完美封装变化:系统需要拆分出变化层和稳定层、需要设计变化层和稳定层之间的接口
  • 低成本、安全、规模
    安全:功能安全、架构安全。
    规模:功能越来越多,导致系统复杂度指数级上升、 数据越来越多,系统复杂度发生质变

架构设计原则

  • 合适原则
    合适优于业界领先,优先满足业务需求
  • 简单原则
    (1)结构复杂
    (2)逻辑复杂
    简单优于复杂,挑选简单方案快速落地验证
  • 演化原则
    演化优于一步到位,适当预测业务发展,感觉预测不准就不预测,等真的出现问题的时候演进即可

架构设计流程

  • 识别复杂度
  • 设计备选方案
    备选方案的数量以 3 ~ 5 个为最佳,备选方案不需要过于详细。
    备选阶段关注的是技术选型,而不是技术细节,技术选型的差异要比较明显
    备选方案不要只局限于已经熟悉的技术。
  • 评估和选择备选方案
    360 度环评,列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。
    按优先级选择,即架构师综合当前的业务发展情况、团队人员规模和技能、业务发展预测等因素,将质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级……以此类推。
  • 详细方案设计
    将方案涉及的关键技术细节给确定下来

高性能数据库集群

  • 读写分离
    将访问压力分散到集群的多个节点,但是没有分散存储压力。
    引入设计复杂度的细节点:
    (1)主从延迟
    写操作后的读操作指定发送给主库、读从库失败再读一次主库、关键业务读写操作全部指向主库&非关键业务采用读写分离
    (2)分配机制
    程序代码封装、中间件封装
    适用场景:读多写少、数据及时性要求不高
  • 分库分表
    既可分散访问压力,又可分散存储压力
    (1)业务分库
    业务分库指的是按照业务模块将数据分散到不同的数据库服务器。
    带来问题:join操作问题、事务问题、成本问题
    (2)分表
    单表数据拆分有两种方式:垂直分表和水平分表
    垂直分表:适合将表中某些不常用且占了大量空间的列拆分出去
    水平分表:水平分表适合表行数特别大的表
    水平分表相比垂直分表,引入更多复杂性,主要表现在下面几个方面:
    路由
    水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算。
    范围路由:选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中;缺点是分布不均匀。
    Hash路由:选取某个列(或者某几个列组合也可以)的值进行Hash运算,然后根据Hash结果分散到不同的数据库表中;Hash路由设计的复杂点主要体现在初始表数量的选取上,表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题;Hash路由的优点是表分布比较均匀,缺点是扩充新的表很麻烦,所有数据都要重新分布。
    配置路由:配置路由就是路由表,用一张独立的表来记录路由信息;配置路由的缺点就是必须多查询一次,会影响整体性能;而且路由表本身如果太大,性能同样可能成为瓶颈,如果我们再次将路由表分库分表,则又面临一个死循环式的路由算法选择问题。
    join操作
    count()操作
    count()相加、记录数表
    order by操作

高性能NoSQL

常见的NoSQL方案:

  • K-V存储:解决关系数据库无法存储数据结构的问题,以Redis为代表。
    Redis的缺点主要体现在并不支持完整的ACID事务,Redis虽然提供事务功能,但Redis的事务和关系数据库的事务不可同日而语,Redis的事务只能保证隔离性和一致性(I和C),无法保证原子性和持久性(A和D)。
  • 文档数据库:解决关系数据库强schema约束的问题,以MongoDB为代表。
    特别适合电商和游戏这类的业务场景,缺点:不支持事务、无法实现关系数据库的join操作
  • 列式数据库:解决关系数据库大数据场景下的I/O问题,以HBase为代表。
    除了节省I/O,列式存储还具备更高的存储压缩比,能够节省更多的存储空间;一般将列式存储应用在离线的大数据分析和统计场景中,因为这种场景主要是针对部分列单列进行操作,且数据写入后就无须再更新删除
  • 全文搜索引擎:解决关系数据库的全文搜索性能问题,以Elasticsearch为代表。
    倒排索引,基本原理是建立单词到文档的索引

高性能缓存架构

适用场景:

  • 需要经过复杂运算后得出的数据,存储系统无能为力

  • 读多写少的数据,存储系统有心无力

  • 缓存穿透

  • 缓存雪崩
    缓存雪崩的常见解决方法有两种:更新锁机制和后台更新机制
    后台更新机制:
    由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存。
    后台定时机制需要考虑一种特殊的场景,当缓存系统内存不够时,会“踢掉”一些缓存数据,从缓存被“踢掉”到下一次定时更新缓存的这段时间内,业务线程读取缓存返回空值,而业务线程本身又不会去更新缓存,因此业务上看到的现象就是数据丢了。解决的方式有两种:
    (1)后台线程除了定时更新缓存,还要频繁地去读取缓存,如果发现缓存被“踢了”就立刻更新缓存,这种方式实现简单,但读取时间间隔不能设置太长,因为如果缓存被踢了,缓存读取间隔时间又太长,这段时间内业务访问都拿不到真正的数据而是一个空的缓存值,用户体验一般。
    (2)业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断缓存是否存在,存在就不执行更新操作。这种方式实现依赖消息队列,复杂度会高一些,但缓存更新更及时,用户体验更好。

  • 缓存热点
    缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。
    以微博为例,对于粉丝数超过100万的明星,每条微博都可以生成100份缓存,缓存的数据是一样的,通过在缓存的key里面加上编号进行区分,每次读缓存时都随机读取其中某份缓存。
    缓存副本设计有一个细节需要注意,就是不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存雪崩效应。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值。

单服务器高性能模式

PPC与TPC

单服务器高性能的关键之一就是服务器采取的并发模型,并发模型有如下两个关键设计点:

  • 服务器如何管理连接
  • 服务器如何处理请求

PPC是Process Per Connection的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的UNIX网络服务器所采用的模型。
PPC模式中,当连接进来时才fork新进程来处理连接请求,由于fork进程代价高,用户访问时可能感觉比较慢,prefork模式的出现就是为了解决这个问题;prefork就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去fork进程的操作,让用户访问更快、体验更好。
TPC是Thread Per Connection的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。与进程相比,线程更轻量级,创建线程的消耗比进程要少得多;同时多线程是共享进程内存空间的,线程通信相比进程通信更简单。因此,TPC实际上是解决或者弱化了PPC fork代价高的问题和父子进程通信复杂的问题。
和prefork类似,prethread模式会预先创建线程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去创建线程的操作,让用户感觉更快、体验更好。
由于多线程之间数据共享和通信比较方便,因此实际上prethread的实现方式相比prefork要灵活一些,常见的实现方式有下面几种:
(1)主进程accept,然后将连接交给某个线程处理。
(2)子线程都尝试去accept,最终只有一个线程accept成功

Reactor与Proactor

  • Reactor
    Reactor模式的核心组成部分包括Reactor和处理资源池(进程池或线程池),其中Reactor负责监听和分配事件,处理资源池负责处理事件。
    目前著名的开源系统Nginx采用的是多Reactor多进程,采用多Reactor多线程的实现有Memcache和Netty
  • Proactor
    Reactor是非阻塞同步网络模型,因为真正的read和send操作都需要用户进程同步操作。这里的“同步”指用户进程在执行read和send这类I/O操作的时候是同步的,如果把I/O操作改为异步就能够进一步提升性能,这就是异步网络模型Proactor。

高性能负载均衡

负载均衡分类:

  • DNS负载均衡
    DNS是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡
  • 硬件负载均衡
    硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5和A10
  • 软件负载均衡
    软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有Nginx和LVS,其中Nginx是软件的7层负载均衡,LVS是Linux内核的4层负载均衡。4层和7层的区别就在于协议和灵活性,Nginx支持HTTP、E-mail协议;而LVS是4层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等

DNS负载均衡、硬件负载均衡、软件负载均衡,每种方式都有一些优缺点,但并不意味着在实际应用中只能基于它们的优缺点进行非此即彼的选择,反而是基于它们的优缺点进行组合使用。具体来说,组合的基本原则为:DNS负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡。

负载均衡算法:

  • 轮询
    负载均衡系统收到请求后,按照顺序轮流分配到服务器上
  • 加权轮询
    负载均衡系统根据服务器权重进行任务分配,这里的权重一般是根据硬件配置进行静态配置的,采用动态的方式计算会更加契合业务,但复杂度也会更高。
    加权轮询是轮询的一种特殊形式,其主要目的就是为了解决不同服务器处理能力有差异的问题。
    加权轮询解决了轮询算法中无法根据服务器的配置差异进行任务分配的问题,但同样存在无法根据服务器的状态差异进行任务分配的问题。
  • 负载最低优先
    负载均衡系统将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标来衡量。例如:
    (1)LVS这种4层网络负载均衡设备,可以以“连接数”来判断服务器的状态,服务器连接数越大,表明服务器压力越大。
    (2)Nginx这种7层网络负载系统,可以以“HTTP请求数”来判断服务器状态(Nginx内置的负载均衡算法不支持这种方式,需要进行扩展)。
    (3)如果我们自己开发负载均衡系统,可以根据业务特点来选择指标衡量系统压力。如果是CPU密集型,可以以“CPU负载”来衡量系统压力;如果是I/O密集型,可以以“I/O负载”来衡量系统压力。
    负载最低优先类算法是站在服务器的角度来进行分配的,负载最低优先的算法解决了轮询算法中无法感知服务器状态的问题。
  • 性能最优类
    性能最优优先类算法则是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的。
    和负载最低优先类算法类似,性能最优优先类算法本质上也是感知了服务器的状态,只是通过响应时间这个外部标准来衡量服务器状态而已。
  • Hash类
    源地址Hash(源IP地址)、ID Hash(session id)

高可用分析

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

在架构设计领域,FMEA的具体分析方法是:

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

FMEA分析的方法其实很简单,就是一个FMEA分析表,常见的FMEA分析表格包含下面部分:

  • 功能点
    当前的FMEA分析涉及的功能点,注意这里的“功能点”指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的。
  • 故障模式
    故障模式指的是系统会出现什么样的故障,包括故障点和故障形式。需要特别注意的是,这里的故障模式并不需要给出真正的故障原因,我们只需要假设出现某种故障现象即可。
    此外,故障模式的描述要尽量精确,多使用量化描述,避免使用泛化的描述。
  • 故障影响
    当发生故障模式中描述的故障时,功能点具体会受到什么影响;故障影响也需要尽量准确描述
  • 严重程度
    严重程度指站在业务的角度故障的影响程度,一般分为“致命/高/中/低/无”五个档次。严重程度按照这个公式进行评估:严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度
  • 故障原因
    不同的故障原因发生概率不相同、不同的故障原因检测手段不一样、不同的故障原因的处理措施不一样
  • 故障概率
    这里的概率就是指某个具体故障原因发生的概率,具体评估时重点关注:硬件、开源系统、自研系统
  • 风险程度
    风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率。
  • 已有措施
    针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等
  • 规避措施
    规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段
  • 解决措施
    解决措施指为了能够解决问题而做的一些事情,一般都是技术手段
  • 后续规划
    综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划。这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。同时需要考虑资源的投入情况,优先将风险程度高的系统隐患解决

高可用存储架构

  • 双机架构
    常见的高可用存储架构有主备、主从、主主、集群、分区。
    (1)主备复制
    主备架构中的“备机”主要还是起到一个备份作用,并不承担实际的业务读写操作,如果要把备机改为主机,需要人工操作。
    综合主备复制架构的优缺点,内部的后台管理系统使用主备复制架构的情况会比较多,例如学生管理系统、员工管理系统、假期管理系统等,因为这类系统的数据变更频率低,即使在某些场景下丢失数据,也可以通过人工的方式补全
    (2)主从复制
    主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。我们可以理解为仆从是要帮主人干活的,这里的干活就是承担“读”的操作。也就是说,主机负责读写操作,从机只负责读操作,不负责写操作。
    综合主从复制的优缺点,一般情况下,写少读多的业务使用主从复制的存储架构比较多
    (3)双机切换
    主备复制和主从复制方案存在两个共性的问题:
    1、主机故障后,无法进行写操作
    2、如果主机无法恢复,需要人工指定新的主机角色。
    切换方案需要考虑设计点:
    1、主备间状态判断
    状态传递的渠道:是相互间互相连接,还是第三方仲裁?
    状态检测的内容:例如机器是否掉电、进程是否存在、响应是否缓慢等。
    2、切换决策
    切换时机:什么情况下备机应该升级为主机?是机器掉电后备机才升级,还是主机上的进程不存在就升级,还是主机响应时间超过2秒就升级,还是3分钟内主机连续重启3次就升级等。
    切换策略:原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?
    自动程度:切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)。
    3、数据冲突解决
    当原有故障的主机恢复后,新旧主机之间可能存在数据冲突。

常见的主备切换架构有三种形式:
1、互连式
互连式就是指主备机直接建立状态传递的渠道。
在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的
2、中介式
中介式指的是在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息。
虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的,其关键代价就在于如何实现中介本身的高可用。如果中介自己宕机了,整个系统就进入了双备的状态,写操作相关的业务就不可用了。这就陷入了一个递归的陷阱:为了实现高可用,我们引入中介,但中介本身又要求高可用,于是又要设计中介的高可用方案……如此递归下去就无穷无尽了
3、模拟式
模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态。

(4)主主复制
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作。
如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的。
主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景

  • 集群和分区
    (1)数据集群
    1、数据集中集群:
    数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为1主多备或者1主多从。无论是1主1从、1主1备,还是1主多备、1主多从,数据都只能往主机中写,而读操作可以参考主备、主从架构进行灵活多变。
    主机如何将数据复制给备机、备机如何检测主机状态、主机故障后,如何决定新的主机。
    2、数据分散集群
    数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。
    数据分散集群的复杂点在于如何将数据分配到不同的服务器上,算法需要考虑这些设计点:均衡性、容错性、可伸缩性。
    数据集中集群架构中,客户端只能将数据写到主机;数据分散集群架构中,客户端可以向任意服务器中读写数据。正是因为这个关键的差异,决定了两种集群的应用场景不同。一般来说,数据集中集群适合数据量不大,集群机器数量不多的场景。例如,ZooKeeper集群,一般推荐5台机器左右,数据量是单台服务器就能够支撑;而数据分散集群,由于其良好的可伸缩性,适合业务数据量巨大、集群机器数量庞大的业务场景。例如,Hadoop集群、HBase集群,大规模的集群可以达到上百台甚至上千台服务器。
    (2)数据分区
    数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。采用了数据分区的架构后,即使某个地区发生严重的自然灾害或者事故,受影响的也只是一部分数据,而不是全部数据都不可用;当故障恢复后,其他地区备份的数据也可以帮助故障地区快速恢复业务。
    设计一个良好的数据分区架构,考虑点:
    1、数据量:
    数据量的大小直接决定了分区的规则复杂度。
    2、分区规则:
    地理位置有近有远,因此可以得到不同的分区规则,包括洲际分区、国家分区、城市分区。具体采取哪种或者哪几种规则,需要综合考虑业务范围、成本等因素。
    通常情况下,洲际分区主要用于面向不同大洲提供服务,由于跨洲通讯的网络延迟已经大到不适合提供在线服务了,因此洲际间的数据中心可以不互通或者仅仅作为备份;国家分区主要用于面向不同国家的用户提供服务,不同国家有不同语言、法律、业务等,国家间的分区一般也仅作为备份;城市分区由于都在同一个国家或者地区内,网络延迟较低,业务相似,分区同时对外提供服务,可以满足业务异地多活之类的需求
    3、复制规则
    数据分区指将数据分散在多个地区,在某些异常或者灾难情况下,虽然部分数据受影响,但整体数据并没有全部被影响,本身就相当于一个高可用方案了。但仅仅做到这点还不够,因为每个分区本身的数据量虽然只是整体数据的一部分,但还是很大,这部分数据如果损坏或者丢失,损失同样难以接受。因此即使是分区架构,同样需要考虑复制方案。
    常见的分区复制规则有三种:
    1、集中式
    集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心
    2、互备式
    互备式备份指每个分区备份另外一个分区的数据
    3、独立式
    独立式备份指每个分区自己有独立的备份中心

高可用计算架构

计算高可用架构的设计复杂度主要体现在任务管理方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行:
1、哪些服务器可以执行任务
每个服务器都可以执行任务、只有特定服务器(通常叫“主机”)可以执行任务
2、任务如何重新执行
对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可。
设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行。
“任务分配器”是一个逻辑的概念,并不一定要求系统存在一个独立的任务分配器模块。
常见的计算高可用架构:

  • 主备
    主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制。
    根据备机状态的不同,主备架构又可以细分为冷备架构和温备架构。
    冷备:备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动(注意:备机的服务器是启动的),主机故障后,需要人工手工将备机的业务系统启动,并将任务分配器的任务请求切换发送给备机。
    温备:备机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。冷备可以节省一定的能源,但温备能够大大减少手工操作时间,因此一般情况下推荐用温备的方式。
    和存储高可用中的主备复制架构类似,计算高可用的主备架构也比较适合与内部管理系统、后台管理系统这类使用人数不多、使用频率不高的业务,不太适合在线的业务。
  • 主从
    和存储高可用中的主从复制架构类似,计算高可用的主从架构中的从机也是要执行任务的。任务分配器需要将任务进行分类,确定哪些任务可以发送给主机执行,哪些任务可以发送给备机执行。
  • 集群
    主备架构和主从架构通过冗余一台服务器来提升可用性,且需要人工来切换主备或者主从。这样的架构虽然简单,但存在一个主要的问题:人工操作效率低、容易出错、不能及时处理故障。因此在可用性要求更加严格的场景中,我们需要系统能够自动完成切换操作,这就是高可用集群方案。
    高可用计算的集群方案根据集群中服务器节点角色的不同,可以分为两类:一类是对称集群,即集群中每个服务器的角色都是一样的,都可以执行所有任务;另一类是非对称集群,集群中的服务器分为多个不同的角色,不同的角色执行不同的任务,例如最常见的Master-Slave角色。
    对称集群更通俗的叫法是负载均衡集群。
    非对称集群中不同服务器的角色是不同的,不同角色的服务器承担不同的职责

业务高可用的保障-异地多活

异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。判断一个系统是否符合异地多活,需要满足两个标准:

  • 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
  • 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务

根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。

  • 同城异区
    同城异区指的是将业务部署在同一个城市不同区的多个机房
  • 跨城异地
    跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些
  • 跨国异地
    跨国异地指的是业务部署在不同国家的多个机房。相比跨城异地,跨国异地的距离就更远了,因此数据同步的延时会更长,正常情况下可能就有几秒钟了。
    跨国异地多活的主要应用场景一般有这几种情况:
    为不同地区用户提供服务、只读类业务做多活

设计4大技巧:

  • 保证核心业务的异地多活
  • 保证核心数据最终一致性
    尽量减少异地多活机房的距离,搭建高速网络
    尽量减少数据同步,只同步核心业务相关的数据
    保证最终一致性,不保证实时一致性
  • 采用多种手段同步数据
    解决的方案就是拓开思路,避免只使用存储系统的同步功能,可以将多种手段配合存储系统的同步来使用,甚至可以不采用存储系统的同步方案,改用自己的同步方案。
    (1)消息队列方式
    (2)二次读取方式
    (3)存储系统同步方式
    (4)回源读取方式
    (5)重新生成数据方式
  • 只保证绝大部分用户的异地多活
    虽然我们无法做到100%可用性,但并不意味着我们什么都不能做,为了让用户心里更好受一些,我们可以采取一些措施进行安抚或者补偿。
    (1)挂公告
    (2)事后对用户进行补偿
    (3)补充体验
    采用多种手段,保证绝大部分用户的核心业务异地多活

异地多活设计4步走:

  • 业务分级
    按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。
    常见的分级标准:访问量大的业务、核心业务、产生大量收入的业务
  • 数据分类
    挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。
    常见的数据特征分析维度:数据量、唯一性、实时性、可丢失性、可恢复性
  • 数据同步
    确定数据的特点后,我们可以根据不同的数据设计不同的同步方案:
    (1)存储系统同步
    (2)消息队列同步
    (3)重复生成
    数据不同步到异地机房,每个机房都可以生成数据,这个方案适合于可以重复生成的数据。例如,登录产生的cookie、session数据、缓存数据等。
  • 异常处理
    论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对
    异常处理主要有以下几个目的:
    (1)问题发生时,避免少量数据异常导致整体业务不可用。
    (2)问题恢复后,将异常的数据进行修正。
    (3)对用户进行安抚,弥补用户损失。
    常见的异常处理措施有这几类:
    (1)多通道同步
    多通道同步的含义是采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。
    多通道同步设计的方案关键点有:
    1、一般情况下,采取两通道即可,采取更多通道理论上能够降低风险,但付出的成本也会增加很多。
    2、数据库同步通道和消息队列同步通道不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障;可以一个走公网连接,一个走内网连接。
    3、需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的。例如,新建账号数据就符合这个标准,而密码数据则不符合这个标准。
    (2)同步和访问结合
    这里的访问指异地机房通过系统的接口来进行数据访问。
    同步和访问结合方案的设计关键点有:

1、接口访问通道和数据库同步通道不能采用相同的网络连接,不能让数据库同步和接口访问都走同一条网络通道,可以采用接口访问走公网连接,数据库同步走内网连接这种方式。
2、数据有路由规则,可以根据数据来推断应该访问哪个机房的接口来读取数据。例如,有3个机房A、B、C,B机房拿到一个不属于B机房的数据后,需要根据路由规则判断是访问A机房接口,还是访问C机房接口。
3、由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问,这样可以大大降低跨机房的异地接口访问数量,适合于实时性要求非常高的数据。
(3)日志记录
日志记录主要用于用户故障恢复后对数据进行恢复,其主要方式是每个关键操作前后都记录相关一条日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复
(4)用户补偿
无论采用什么样的异常处理措施,都只能最大限度地降低受到影响的范围和程度,无法完全做到没有任何影响。
常见的补偿措施有送用户代金券、礼包、礼品、红包等

应对接口级故障

接口级故障的典型表现就是,系统并没有宕机、网络也没有中断,但业务却出现问题了,例如业务响应缓慢、大量访问超时和大量访问出现异常(给用户弹出提示“无法连接数据库”)
导致接口级故障的原因可以分为两大类:

  • 内部原因:包括程序bug导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等
  • 外部原因:包括黑客攻击,促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等

解决接口级故障的核心思想和异地多活基本类似,都是优先保证核心业务和优先保证绝大部分用户。
常见的应对方法:

  • 降级
    降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能
    常见的实现降级的方式:系统后门降级、独立降级系统
  • 熔断
    熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。
    实现熔断机制有两个关键点:
    需要有一个统一的API调用层,由API调用层来进行采样或者统计
    阈值的设计,实践中,一般都是先根据分析确定阈值,然后上线观察效果,再进行调优

熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看,好像都有禁止某个功能的意思。但它们的内涵是不同的,因为降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况

  • 限流
    降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。
    虽然“丢弃”这个词听起来让人不太舒服,但保证一部分请求能够正常响应,总比全部请求都不能响应要好得多。
    限流一般都是系统内实现的,常见的限流方式可以分为两类:基于请求限流和基于资源限流
    (1)基于请求限流
    基于请求限流指从外部访问的请求角度考虑限流。
    限制总量,也就是限制某个指标的累积上限。
    限制时间量,也就是限制一段时间内某个指标的上限。

(2)基于资源限流
基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,也就是找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源包括连接数、文件句柄、线程数和请求队列等。
基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实际设计时也面临两个主要的难点:如何确定关键资源,以及如何确定关键资源的阈值。
通常情况下,这也是一个逐步调优的过程:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化

限流算法:
(1)时间窗
它会限制一定时间窗口内的请求量或者资源消耗量,根据实现方式又可以细分为“固定时间窗”和“滑动时间窗”
固定时间窗:
固定时间窗算法的实现原理是,统计固定时间周期内的请求量或者资源消耗量,超过限额就会启动限流。
它的优点是实现简单,缺点是存在临界点问题
滑动时间窗:
为了解决临界点问题,滑动时间窗算法应运而生,它的实现原理是,两个统计周期部分重叠,从而避免短时间内的两个统计点分属不同的时间窗的情况。
总体上来看,滑动时间窗的限流效果要比固定时间窗更好,但是实现也会稍微复杂一些。
(2)桶算法
桶算法,用一个虚拟的“桶”来临时存储一些东西。根据桶里面放的东西,又可以细分为“漏桶”和“令牌桶”。

  • 漏桶
    漏桶算法的实现原理是,将请求放入“桶”(消息队列等),业务处理单元(线程、进程和应用等)从桶里拿请求处理,桶满则丢弃新的请求。
    漏桶算法的技术本质是总量控制,桶大小是设计关键。
    漏桶算法主要适用于瞬时高并发流量的场景(例如刚才提到的0点签到、整点秒杀等)。在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求。

  • 令牌桶算法
    令牌桶算法和漏桶算法的不同之处在于,桶中放入的不是请求,而是“令牌”,这个令牌就是业务处理前需要拿到的“许可证”。也就是说,当系统收到一个请求时,先要到令牌桶里面拿“令牌”,拿到令牌才能进一步处理,拿不到就要丢弃请求。
    令牌桶算法的技术本质是速率控制,令牌产生的速率是设计关键。
    令牌桶算法主要适用于两种典型的场景,一种是需要控制访问第三方服务的速度,防止把下游压垮,例如支付宝需要控制访问银行接口的速率;另一种是需要控制自己的处理速度,防止过载,例如压测结果显示系统最大处理TPS是100,那么就可以用令牌桶来限制最大的处理速度。
    刚才介绍漏桶算法的时候我提到漏桶算法可以应对瞬时高并发流量,现在介绍令牌桶算法的时候,我又说令牌桶允许突发流量。
    其实,令牌桶的“允许突发”实际上只是“允许一定程度的突发”,比如系统处理能力是每秒100 TPS,突发到120 TPS是可以的,但如果突发到1000 TPS的话,系统大概率就被压垮了。所以处理秒杀时高并发流量,还是得用漏桶算法。
    令牌桶在实际设计的时候,桶大小不能像漏桶那样设计很大,需要根据系统的处理能力来进行仔细的估算。

  • 排队
    排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间,全世界最有名的排队当属12306网站排队了。
    由于排队需要临时缓存大量的业务请求,单个系统内部无法缓存这么多数据,一般情况下,排队需要用独立的系统去实现,例如使用Kafka这类消息队列来缓存用户请求。

可扩展的架构模式

软件系统的这种天生和内在的可扩展的特性,既是魅力所在,又是难点所在。魅力体现在我们可以通过修改和扩展,不断地让软件系统具备更多的功能和特性,满足新的需求或者顺应技术发展的趋势。而难点体现在如何以最小的代价去扩展系统,因为很多情况下牵一发动全身,扩展时可能出现到处都要改,到处都要推倒重来的情况。这样做的风险不言而喻:改动的地方越多,投入也越大,出错的可能性也越大。因此,如何避免扩展时改动范围太大,是软件架构可扩展性设计的主要思考。

可扩展的基本思想

可扩展性架构的设计方法很多,但万变不离其宗,所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
拆,就是将原本大一统的系统拆分成多个规模小的部分,扩展时只修改其中一部分即可,无须整个系统到处都改,通过这种方式来减少改动范围,降低改动风险。
按照不同的思路来拆分软件系统,就会得到不同的架构。常见的拆分思路:

  • 面向流程拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分
  • 面向服务拆分:将系统提供的服务拆分,每个服务作为一部分
  • 面向功能拆分:将系统提供的功能拆分,每个功能作为一部分
    理解这三种思路的关键就在于如何理解“流程”“服务”“功能”三者的联系和区别。从范围上来看,从大到小依次为:流程>服务>功能。
    不同的拆分方式,本质上决定了系统的扩展方式。
    不同的拆分方式,将得到不同的系统架构,典型的可扩展系统架构有:
    面向流程拆分:分层架构。
    面向服务拆分:SOA、微服务。
    面向功能拆分:微内核架构。

分层架构和SOA

  • 分层架构
    分层架构是很常见的架构模式,它也叫N层架构,通常情况下,N至少是2层。例如,C/S架构、B/S架构。常见的是3层架构(例如,MVC、MVP架构)、4层架构,5层架构的比较少见,一般是比较复杂的系统才会达到或者超过5层,比如操作系统内核架构。
    按照分层架构进行设计时,根据不同的划分维度和对象,可以得到多种不同的分层架构
    (1)C/S架构、B/S架构
    划分的对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一层,支撑用户交互的后台作为另外一层
    (2)MVC架构、MVP架构
    划分的对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层,但各层的依赖关系比较灵活
    (3)逻辑分层架构
    划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责。虽然都是基于职责划分,但逻辑分层架构和MVC架构、MVP架构的不同点在于,逻辑分层架构中的层是自顶向下依赖的。典型的有操作系统内核架构、TCP/IP架构
  • SOA
    (1)服务
    所有业务功能都是一项服务,服务就意味着要对外提供开放的能力,当其他系统需要使用这项功能时,无须定制化开发。

服务可大可小,可简单也可复杂。例如,人力资源管理可以是一项服务,包括人员基本信息管理、请假管理、组织结构管理等功能;而人员基本信息管理也可以作为一项独立的服务,组织结构管理也可以作为一项独立的服务。到底是划分为粗粒度的服务,还是划分为细粒度的服务,需要根据企业的实际情况进行判断
(2)ESB
ESB的全称是Enterprise Service Bus,中文翻译为“企业服务总线”。从名字就可以看出,ESB参考了计算机总线的概念。计算机中的总线将各个不同的设备连接在一起,ESB将企业中各个不同的服务连接在一起。因为各个独立的服务是异构的,如果没有统一的标准,则各个异构系统对外提供的接口是各式各样的。SOA使用ESB来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通。
(3)松耦合
ESB的全称是Enterprise Service Bus,中文翻译为“企业服务总线”。从名字就可以看出,ESB参考了计算机总线的概念。计算机中的总线将各个不同的设备连接在一起,ESB将企业中各个不同的服务连接在一起。因为各个独立的服务是异构的,如果没有统一的标准,则各个异构系统对外提供的接口是各式各样的。SOA使用ESB来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通。

SOA解决了传统IT系统重复建设和扩展效率低的问题,但其本身也引入了更多的复杂性。SOA最广为人诟病的就是ESB,ESB需要实现与各种系统间的协议转换、数据转换、透明的动态路由等功能

微服务架构

SOA和微服务的关系和区别:

  • 服务粒度
    整体上来说,SOA的服务粒度要粗一些,而微服务的服务粒度要细一些
  • 服务通信
    OA采用了ESB作为服务间通信的关键组件,负责服务定义、服务路由、消息转换、消息传递,总体上是重量级的实现。微服务推荐使用统一的协议和格式,例如,RESTful协议、RPC协议,无须ESB这样的重量级实现。
  • 服务交付
    SOA对服务的交付并没有特殊要求,因为SOA更多考虑的是兼容已有的系统;微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。
  • 应用场景
    SOA更加适合于庞大、复杂、异构的企业级系统,这也是SOA诞生的背景。这类系统的典型特征就是很多系统已经发展多年,采用不同的企业级技术,有的是内部开发的,有的是外部购买的,无法完全推倒重来或者进行大规模的优化和重构。因为成本和影响太大,只能采用兼容的方式进行处理,而承担兼容任务的就是ESB。
    微服务更加适合于快速、轻量级、基于Web的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于Web,虽然开发技术可能差异很大(例如,Java、C++、.NET等),但对外接口基本都是提供HTTP RESTful风格的接口,无须考虑在接口层进行类似SOA的ESB那样的处理。

微服务的陷阱:

  • 服务划分过细,服务间关系复杂
  • 服务数量太多,团队效率急剧下降
  • 调用链太长,性能下降
  • 调用链太长,问题定位困难
  • 没有自动化支撑,无法快速交付
  • 没有服务治理,微服务数量多了后管理混乱

微服务架构最佳实践:

  • 服务粒度
    针对微服务拆分过细导致的问题,我建议基于团队规模进行拆分,类似贝索斯在定义团队规模时提出的“两个披萨”理论(每个团队的人数不能多到两张披萨都不够吃的地步),分享一个我认为微服务拆分粒度的“三个火枪手”原则,即一个微服务三个人负责开发。当我们在实施微服务架构时,根据团队规模来划分微服务数量,如果业务规继续发展,团队规模扩大,我们再将已有的微服务进行拆分。
    “三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均1个人维护1个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好都安排2个人维护,每个人都可以维护多个微服务
  • 拆分方法
    拆分方式:
    (1)基于业务逻辑拆分
    这是最常见的一种拆分方式,将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务。
    (2)基于可扩展拆分
    将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务。稳定的服务粒度可以粗一些,即使逻辑上没有强关联的服务,也可以放在同一个子系统中。
    这样拆分主要是为了提升项目快速迭代的效率,避免在开发的时候,不小心影响了已有的成熟功能导致线上问题。
    (3)基于可靠性拆分
    将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个,只要最终的服务数量满足“三个火枪手”的原则就可以。
    这样拆分带来下面几个好处:避免非核心服务故障影响核心服务、核心服务高可用方案可以更简单、能够降低高可用成本
    (4)基于性能拆分
    基于性能拆分和基于可靠性拆分类似,将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。常见的拆分方式和具体的性能瓶颈有关,可以拆分Web服务、数据库、缓存等。例如电商的抢购,性能压力最大的是入口的排队功能,可以将排队功能独立为一个服务。
    以上几种拆分方式不是多选一,而是可以根据实际情况自由排列组合,例如可以基于可靠性拆分出服务A,基于性能拆分出服务B,基于可扩展拆分出C/D/F三个服务,加上原有的服务X,最后总共拆分出6个服务(A/B/C/D/F/X)。
  • 基础设施
    大部分人主要关注的是微服务的“small”和“lightweight”特性,但实际上真正决定微服务成败的,恰恰是那个被大部分人都忽略的“automated”。为何这样说呢?因为服务粒度即使划分不合理,实际落地后如果团队遇到麻烦,自然会想到拆服务或者合服务;如果“automated”相关的基础设施不健全,那微服务就是焦油坑,让研发、测试、运维陷入我上一期讲的各种微服务陷阱中。
    通常情况下,我建议按照下面优先级来搭建基础设施:
    (1)服务发现、服务路由、服务容错:这是最基本的微服务基础设施
    (2)接口框架、API网关:主要是为了提升开发效率,接口框架是提升内部服务的开发效率,API网关是为了提升与外部服务对接的效率。
    (3)自动化部署、自动化测试、配置中心:主要是为了提升测试和运维效率
    (4)服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率
    以上3和4两类基础设施,其重要性会随着微服务节点数量增加而越来越重要,但在微服务节点数量较少的时候,可以通过人工的方式支撑,虽然效率不高,但也基本能够顶住。
    微服务架构最佳实践的基础设施:
    (1)自动化测试
    微服务将原本大一统的系统拆分为多个独立运行的“微”服务,微服务之间的接口数量大大增加,并且微服务提倡快速交付,版本周期短,版本更新频繁。如果每次更新都靠人工回归整个系统,则工作量大,效率低下,达不到“快速交付”的目的,因此必须通过自动化测试系统来完成绝大部分测试回归的工作
    (2)自动化部署
    相比大一统的系统,微服务需要部署的节点增加了几倍甚至十几倍,微服务部署的频率也会大幅提升(例如,我们的业务系统70%的工作日都有部署操作),综合计算下来,微服务部署的次数是大一统系统部署次数的几十倍。这么大量的部署操作,如果继续采用人工手工处理,需要投入大量的人力,且容易出错,因此需要自动化部署的系统来完成部署操作。
    (3)配置中心
    微服务的节点数量非常多,通过人工登录每台机器手工修改,效率低,容易出错。特别是在部署或者排障时,需要快速增删改查配置,人工操作的方式显然是不行的。除此以外,有的运行期配置需要动态修改并且所有节点即时生效,人工操作是无法做到的。综合上面的分析,微服务需要一个统一的配置中心来管理所有微服务节点的配置。
    (4)接口框架
    微服务提倡轻量级的通信方式,一般采用HTTP/REST或者RPC方式统一接口协议。但在实践过程中,光统一接口协议还不够,还需要统一接口传递的数据格式。
    接口框架不是一个可运行的系统,一般以库或者包的形式提供给所有微服务调用。
    (5)API网关
    系统拆分为微服务后,内部的微服务之间是互联互通的,相互之间的访问都是点对点的。如果外部系统想调用系统的某个功能,也采取点对点的方式,则外部系统会非常“头大”。因为在外部系统看来,它不需要也没办法理解这么多微服务的职责分工和边界,它只会关注它需要的能力,而不会关注这个能力应该由哪个微服务提供。
    除此以外,外部系统访问系统还涉及安全和权限相关的限制,如果外部系统直接访问某个微服务,则意味着每个微服务都要自己实现安全和权限的功能,这样做不但工作量大,而且都是重复工作。
    综合上面的分析,微服务需要一个统一的API网关,负责外部系统的访问操作。
    API网关是外部系统访问的接口,所有的外部系统接⼊系统都需要通过API网关,主要包括接入鉴权(是否允许接入)、权限控制(可以访问哪些功能)、传输加密、请求路由、流量控制等功能。
    (6)服务发现
    服务发现主要有两种实现方式:自理式和代理式
    1、自理式
    自理式结构就是指每个微服务自己完成服务发现。
    自理式服务发现实现比较简单,因为这部分的功能一般通过统一的程序库或者程序包提供给各个微服务调用,而不会每个微服务都自己来重复实现一遍;并且由于每个微服务都承担了服务发现的功能,访问压力分散到了各个微服务节点,性能和可用性上不存在明显的压力和风险。
    2、代理式
    代理式结构就是指微服务之间有一个负载均衡系统,由负载均衡系统来完成微服务之间的服务发现。
    代理式的方式看起来更加清晰,微服务本身的实现也简单了很多,但实际上这个方案风险较大。第一个风险是可用性风险,一旦LOAD BALANCER系统故障,就会影响所有微服务之间的调用;第二个风险是性能风险,所有的微服务之间的调用流量都要经过LOAD BALANCER系统,性能压力会随着微服务数量和流量增加而不断增加,最后成为性能瓶颈。因此LOAD BALANCER系统需要设计成集群的模式,但LOAD BALANCER集群的实现本身又增加了复杂性。
    (7)服务路由
    有了服务发现后,微服务之间能够方便地获取相关配置信息,但具体进行某次调用请求时,我们还需要从所有符合条件的可用微服务节点中挑选出一个具体的节点发起请求,这就是服务路由需要完成的功能。
    服务路由和服务发现紧密相关,服务路由一般不会设计成一个独立运行的系统,通常情况下是和服务发现放在一起实现的。对于自理式服务发现,服务路由是微服务内部实现的;对于代理式服务发现,服务路由是由LOAD BALANCER系统实现的。无论放在哪里实现,服务路由核心的功能就是路由算法。常见的路由算法有:随机路由、轮询路由、最小压力路由、最小连接数路由等。
    (8)服务容错
    系统拆分为微服务后,单个微服务故障的概率变小,故障影响范围也减少,但是微服务的节点数量大大增加。从整体上来看,系统中某个微服务出故障的概率会大大增加。在分析微服务陷阱时提到微服务具有故障扩散的特点,如果不及时处理故障,故障扩散开来就会导致看起来系统中很多服务节点都故障了,因此需要微服务能够自动应对这种出错场景,及时进行处理。否则,如果节点一故障就需要人工处理,投入人力大,处理速度慢;而一旦处理速度慢,则故障就很快扩散,所以我们需要服务容错的能力。
    常见的服务容错包括请求重试、流控和服务隔离。通常情况下,服务容错会集成在服务发现和服务路由系统中。
    (9)服务监控
    系统拆分为微服务后,节点数量大大增加,导致需要监控的机器、网络、进程、接口调用数等监控对象的数量大大增加;同时,一旦发生故障,我们需要快速根据各类信息来定位故障。这两个目标如果靠人力去完成是不现实的。
    服务监控的主要作用有:
    1、实时搜集信息并进行分析,避免故障后再来分析,减少了处理时间。
    2、服务监控可以在实时分析的基础上进行预警,在问题萌芽的阶段发觉并预警,降低了问题影响的范围和时间。
    通常情况下,服务监控需要搜集并分析大量的数据,因此建议做成独立的系统,而不要集成到服务发现、API网关等系统中。
    (10)服务跟踪
    服务监控可以做到微服务节点级的监控和信息收集,但如果我们需要跟踪某一个请求在微服务中的完整路径,服务监控是难以实现的。因为如果每个服务的完整请求链信息都实时发送给服务监控系统,数据量会大到无法处理。
    服务监控和服务跟踪的区别可以简单概括为宏观和微观的区别
    (11)服务安全
    系统拆分为微服务后,数据分散在各个微服务节点上。从系统连接的角度来说,任意微服务都可以访问所有其他微服务节点;但从业务的角度来说,部分敏感数据或者操作,只能部分微服务可以访问,而不是所有的微服务都可以访问,因此需要设计服务安全机制来保证业务和数据的安全性。
    服务安全主要分为三部分:接入安全、数据安全、传输安全。通常情况下,服务安全可以集成到配置中心系统中进行实现,即配置中心配置微服务的接入安全策略和数据安全策略,微服务节点从配置中心获取这些配置信息,然后在处理具体的微服务调用请求时根据安全策略进行处理。由于这些策略是通用的,一般会把策略封装成通用的库提供给各个微服务调用

相关文章

网友评论

      本文标题:架构笔记(一)

      本文链接:https://www.haomeiwen.com/subject/srwcbdtx.html