1. 分布式架构介绍
什么是分布式系统
分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。通俗的理解,所谓分布式系统,就是一个业务拆分成多个子业务,分布在不同的服务器节点,共同构成的系统称为分布式系统,同一个分布式系统中的服务器节点在空间部署上是可以随意分布的,这些服务器可能放在不同的机柜中,也可能在不同的机房中,甚至分布在不同的城市。
分布式与集群的区别
集群: 多个服务器做同一个事情
分布式: 多个服务器做不同的事情
分布式系统面临的问题
通信异常
网络本身的不可靠性,因此每次网络通信都会伴随着网络不可用的风险(光纤、路由、DNS等硬件
设备或系统的不可用),都会导致最终分布式系统无法顺利进行一次网络通信,另外,即使分布式
系统各节点之间的网络通信能够正常执行,其延时也会大于单机操作,存在巨大的延时差别,也会
影响消息的收发过程,因此消息丢失和消息延迟变的非常普遍。
网络分区
网络之间出现了网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被
切分成了若干个孤立的区域,分布式系统就会出现局部小集群,在极端情况下,这些小集群会独立
完成原本需要整个分布式系统才能完成的功能,包括数据的事务处理,这就对分布式一致性提出非
常大的挑战。
节点故障
节点故障是分布式系统下另一个比较常见的问题,指的是组成分布式系统的服务器节点出现的宕机
或"僵死"现象,根据经验来说,每个节点都有可能出现故障,并且经常发生。
三态
分布式系统每一次请求与响应存在特有的“三态”概念,即成功、失败和超时。
重发
分布式系统在发生调用的时候可能会出现 失败 超时 的情况. 这个时候需要重新发起调用。
幂等
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是
说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
场景1:select * from T where id=1 无论执行多少次都不会改变状态,是幂等的
场景2:update T set age=18 where id=1 无论执行成功多少次状态都一致的,是幂等的
场景3:update T set age=age + 1 where id=1 每次执行结果都不一样,不是幂等的
2. 分布式理论
数据一致性
分布式数据一致性,指的是数据在多份副本中存储时,各副本中的数据是一致的。
分布式系统当中,数据往往会有多个副本。多个副本就需要保证数据的一致性。这就带来了同步的
问题,因为网络延迟等因素, 我们几乎没有办法保证可以同时更新所有机器当中的包括备份所有数据. 就会有数据不一致的情况。
总得来说,我们无法找到一种能够满足分布式系统中数据一致性解决方案。因此,如何既保证数据的一致性,同时又不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的。于是,一致性级别由此诞生。
强一致性
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,
但实现起来往往对系统的性能影响大。但是强一致性很难实现。
弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据
能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。
最终一致性
最终一致性也是弱一致性的一种,它无法保证数据更新后,所有后续的访问都能看到最新数值,而
是需要一个时间,在这个时间之后可以保证这一点(就是在一段时间后,节点间的数据会最终达到
一致状态),而在这个时间内,数据也许是不一致的,这个系统无法保证强一致性的时间片段被称
为不一致窗口。不一致窗口的时间长短取决于很多因素,比如备份数据的个数、网络传输延迟
速度、系统负载等。
CAP定理
CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式
计算系统来说,不可能同时满足以下三点:
- 一致性(Consistency):这里指的是强一致性,在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果. 也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据。
- 可用性(Availability):系统中非故障节点收到的每个请求都必须有响应。在可用系统中,如果我们的客户端向服务器发送请求,并且服务器未崩溃,则服务器必须最终响应客户端,不允许服务器忽略客户的请求。每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据。
- 分区容错性(Partition tolerance):允许网络丢失从一个节点发送到另一个节点的任意多条消息,即不同步。也就是说,节点1和节点2发送给对方的任何消息都是可以放弃的,也就是说节点1和节点2可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。
CAP三者的选择
- CA (Consistency + Availability):关注一致性和可用性,它需要非常严格的全体一致的协议。CA系统不能容忍网络错误或节点错误,一旦出现这样的问题,整个系统就会拒绝写请求,因为它并不知道对面的那个结点是否挂掉了,还是只是网络问题。唯一安全的做法就是把自己变成只读的。
- CP (consistency + partition tolerance):关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议。这样的系统只需要保证大多数结点数据一致,而少数的结点会在没有同步到最新版本的数据时变成不可用的状态。这样能够提供一部分的可用性。
- AP (availability + partition tolerance):这样的系统关心可用性和分区容忍性。因此,这样的系统不能达成一致性,需要给出数据冲突,给出数据冲突就需要维护数据版本。
如何进行三选二
- 选择AP:放弃了一致性,满足分区容错,那么节点之间就有可能失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会容易导致全局数据不一致性。对于互联网应用来说,机器数量庞大,节点分散,网络故障再正常不过了,那么此时就是保障AP,放弃C的场景,而从实际中理解,像网站这种偶尔没有一致性是能接受的,但不能访问问题就非常大了。
- 选择CP:对于银行来说,就是必须保证强一致性,也就是说C必须存在,那么就只用CA和CP两种情况,当保障强一致性和可用性(CA),那么一旦出现通信故障,系统将完全不可用。另一方面,如果保障了强一致性和分区容错(CP),那么就具备了部分可用性。实际究竟应该选择什么,是需要通过业务场景进行权衡的。
BASE理论
BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写 ,Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是: 既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
Basically Available(基本可用)
- 响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎
可以在 1 秒返回结果。 - 功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大
促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
Soft state(软状态)
什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。
软状态指的是:允许系统中的数据存在中间状态,并认为该状态不会影响系统的整体可用性,即允
许系统在多个不同节点的数据副本存在数据延时。
Eventually consistent(最终一致性)
在一定的时间期限过后,应当保证所有副本保持数据一致性。从而达到数据的最终一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素。
3. 分布式系统设计策略
在分布式环境下,有几个问题是普遍关心的
- 如何检测当前节点还活着?
- 如何保障高可用?
- 容错处理
- 负载均衡
心跳检测
心跳检测,顾名思义,就是以固定的频率向其他节点汇报当前节点状态的方式。收到心跳,一般可以认为一个节点和现在的网络是良好的。当然,心跳汇报时,一般也会携带一些附加的状态、元数据信息,以便管理。
若Server没有收到Node3的心跳时,Server认为Node3失联。但是失联是失去联系,并不确定是否
是Node3故障,有可能是Node3处于繁忙状态,导致调用检测超时;也有可能是Server与Node3之间链路出现故障或闪断。所以心跳不是万能的,收到心跳可以确认节点正常,但是收不到心跳也不能认为该节点就已经宣告“死亡”。此时,可以通过一些方法帮助Server做决定: 周期检测心跳机制、累计失效检测机制。
- 周期检测心跳机制:Server端每间隔 t 秒向Node集群发起监测请求,设定超时时间,如果超过超时时间,则判断“死亡”。可以把该节点踢出集群
- 累计失效检测机制:在周期检测心跳机制的基础上,统计一定周期内节点的返回情况(包括超时及正确返回),以此计算节点的“死亡”概率。另外,对于宣告“濒临死亡”的节点可以发起有限次数的重试,以作进一步判断。如果超过次数则可以把该节点踢出集群
高可用HA设计
系统高可用性的常用设计模式包括三种:主备(Master-SLave)、互备(Active-Active)和集群
(Cluster)模式。
- 主备模式:主机宕机时,备机接管主机的一切工作,待主机恢复正常后,按使用者的设定以自动(热备)或手动(冷备)方式将服务切换到主机上运行。在数据库部分,习惯称之为MS模式。MS模式即Master/Slave模式,这在数据库高可用性方案中比较常用,如MySQL、Redis等就采用MS模式实现主从复制。保证高可用
- 互备模式:指两台主机同时运行各自的服务工作且相互监测情况。在数据库高可用部分,常见的互备是MM模式。MM模式即Multi-Master模式,指一个系统存在多个master,每个master都具有read-write能力,会根据时间戳或业务逻辑合并版本。
- 集群模式:指有多个节点在运行,同时可以通过主控节点分担服务请求。集群模式需要解决主控节点本身的高可用问题,一般采用主备模式。
容错性
容错顾名思义就是IT系统对于错误包容的能力。容错的处理是保障分布式环境下相应系统的高可用或者健壮性,一个典型的案例就是对于缓存穿透 问题的解决方案。
问题描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,或者有人恶意攻击。如频繁发起为id为“-1”的条件进行查询,可能DB就挂掉了。
解决方法:临时存放null值 或 使用布隆过滤器
负载均衡
负载均衡:其关键在于使用多台集群服务器共同分担计算任务,把网络请求及计算分配到集群可用的不同服务器节点上,从而达到高可用性及较好的用户操作体验。
负载均衡器有硬件解决方案,也有软件解决方案。硬件解决方案有著名的F5,软件有LVS、HAProxy、Nginx等。
以Nginx为例,负载均衡有以下6种策略:
- 轮询:默认方式,每个请求会按时间顺序逐一分配到不同的后端服务器
- weight:权重方式,在轮询策略的基础上指定轮询的几率,权重越大,接受请求越多
- ip_hash:依据ip分配方式,相同的客户端的请求一直发送到相同的服务器,以保证
session会话 - least_conn:最少连接方式,把请求转发给连接数较少的后端服务器
- fair(第三方):按照服务器端的响应时间来分配请求,响应时间短的优先分配
- url_hash(第三方):依据URL分配方式,按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器
4. 分布式架构服务调用
服务调用
和传统的单体架构相比,分布式多了一个远程服务之间的通信,不管是 soa 还是微服务,他们本
质上都是对于业务服务的提炼和复用。那么远程服务之间的调用才是实现分布式的关键因素。
HTTP 应用协议的通信框架
- java 原生 HttpURLConnection是基于http协议的,支持get,post,put,delete等各种请求方
式,最常用的就是get和post - HttpClient 是Apache Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本。
- OKHttp是一个当前主流的网络请求的开源框架, 用于替代HttpUrlConnection和Apache HttpClient
- Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率,所以很多客户端比如 Android或者第三方服务商都是使用 RestTemplate 请求 restful 服务。
RPC 框架
- Java RMI(Romote Method Invocation)是一种基于Java的远程方法调用技术,是Java特有的一种RPC实现。
- Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能. 相比
WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。 - Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
- gRPC是由Google公司开源的一款高性能的远程过程调用(RPC)框架,可以在任何环境下运行。该框架提供了负载均衡,跟踪,智能监控,身份验证等功能,可以实现系统间的高效连接。
跨域调用
在分布式系统中, 会有调用其他业务系统,导致出现跨域问题,跨域实质上是浏览器的一种保护处
理。如果产生了跨域,服务器在返回结果时就会被浏览器拦截(注意:此时请求是可以正常发起的,只是浏览器对其进行了拦截),导致响应的内容不可用
常见的解决方案
- 使用HttpClient内部转发
- 使用设置响应头允许跨域:response.setHeader(“Access-Control-Allow-Origin”, “*”); 设置响应头允许跨域.
- 基于Nginx搭建企业级API接口网关
- 使用Zuul或GateWay搭建微服务API接口网关
5. 分布式服务治理
服务协调
分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某
种临界资源,防止造成"脏数据"的后果。
分布式锁也就是我们分布式协调技术实现的核心内容。
分布式锁两种实现方式:
1. 基于缓存(Redis等)实现分布式锁
- 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID, 释放锁的时候进行判断。
- 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
SETNX :set一个key为value的字符串,返回1;若key存在,则什么都不做,返回0。
expire: 为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
delete :删除key
2. ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名, 基于ZooKeeper实现分布式锁的步骤如下:
- 创建一个目录mylock
- 线程A想获取锁就在mylock目录下创建临时顺序节点
- 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前
线程顺序号最小,获得锁 - 线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点
- 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果
是则获得锁
服务削峰
互联网的业务场景,例如,春节火车票抢购,大量的用户需要同一时间去抢购;以及大家熟知的阿里双11秒杀, 短时间上亿的用户涌入,瞬间流量巨大(高并发)。削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则。
1. 消息队列解决削峰
要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转
换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送
出去。消息队列中间件主要解决应用耦合,异步消息, 流量削锋等问题。常用消息队列系统:目前在生产环境,使用较多的消息队列有 RabbitMQ、Kafka、RocketMQ 等。
2. 流量削峰漏斗:层层削峰
分层过滤其实就是采用“漏斗”式设计来处理请求的,这样就像漏斗一样,尽量把数据量和请求量一
层一层地过滤和减少了。如下图所示:
分层过滤的核心思想:
- 通过在不同的层次尽可能地过滤掉无效请求。
- 通过CDN过滤掉大量的图片,静态资源的请求。
- 再通过类似Redis这样的分布式缓存过滤请求
服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换
种简单的方式处理,从而释放服务器资源以保证核心服务正常运作或高效运作
当触发服务降级后,新的交易再次到达时,我们该如何来处理这些请求呢?从分布式,微服务架构全局的视角来看,降级处理方案:
- 页面降级 —— 可视化界面禁用点击按钮、调整静态页面
- 延迟服务 —— 如定时任务延迟处理、消息入MQ后延迟处理
- 写降级 —— 直接禁止相关写操作的服务请求
- 读降级 —— 直接禁止相关读的服务请求
- 缓存降级 —— 使用缓存方式来降级部分读频繁的服务接口
针对后端代码层面的降级处理策略,则我们通常使用以下几种处理措施进行降级处理:
- 抛异常
- 返回NULL
- 调用Mock数据
- 调用Fallback处理逻辑
服务限流
限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系
统,一旦达到限制速率则可以拒绝服务、排队或等待。
服务熔断
【熔断】, 熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
举例说明:当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游. 它们的调用链如下:
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务 A也变为不可用,整个调用链路被拖垮。
像这种调用链路的连锁故障,叫做雪崩。
熔断机制实现
1. Spring Cloud Hystrix
Spring Cloud Hystrix是基于Netflix的开源框架Hystrix实现,该框架实现了服务熔断、线程隔离等
一系列服务保护功能。对于熔断机制的实现,Hystrix设计了三种状态:
- 熔断关闭状态(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
- 熔断开启状态(Open):在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开启状态。进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。
- 半熔断状态(Half-Open):在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
三个状态的转化关系如下图:
2. Sentinel
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,防止避免影
响到其它的资源,最终产生雪崩的效果。
Sentinel 熔断手段:
- 通过并发线程数进行限制
- 通过响应时间对资源进行降级
- 系统负载保护