1.服务协调
分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成“脏数据”的后果。
分布式锁也是分布式协调技术实现的核心内容。
分布式锁两种实现方式:
- 基于缓存(Redis等)实现分布式锁
- 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,释放锁的时候进行判断
- 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放
- ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名,基于Zookeeper实现分布式锁步骤如下:
- 创建一个目录mylock
- 线程A想获取锁就在mylock目录下创建临时顺序节点
- 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获取锁
- 线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点
- 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获取锁
2.服务削峰
1)为什么要削峰
主要还是来自于互联网的业务场景,例如,春节火车票抢购,大量的用户需要同一时间去抢购;双十一短时间上亿的用户涌入,瞬间流量巨大(高并发)
2)流量削峰方案
削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数量要尽量少”的原则。
(1)消息队列解决削峰
要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去
消息队列中间件主要解决应用耦合,异步消息,流量削峰等问题。常用消息队列系统:目前在生产环境,使用较多的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、RocketMQ等。
在这里,消息队列就像“水库”一样,拦截上游的洪水,消减进入下游河道的洪峰流量,从而达到减免洪水灾害的目的。
(2)流量削峰漏斗:层层削峰
分层过滤其实就是采用“漏斗”式设计来处理请求的,这样就像漏斗一样,尽量把数据量和请求量一层一层地过滤和减少了。如下图所示:
- 分层过滤的核心思想
- 通过在不同的层次尽可能地过滤掉无效请求
- 通过CDN过滤掉大量的图片,静态资源的请求
- 再通过类似Redis这样的分布式缓存过滤请求
- 分层过滤的基本原则
- 对写数据进行基于时间的合理分片,过滤掉过期的失效请求
- 对写请求做限流保护,将超出系统承载能力的请求过滤掉
- 涉及到的读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题
- 对写数据进行强一致性校验,只保留最后有效的数据
3.服务降级
1)什么是服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心服务正常运作或高效运作。
整个架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些不重要或不紧急的服务或任务进行服务的延迟使用或暂停使用。
2)降级策略
当触发服务降级后,新的交易再次到达时,我们该如何来处理这些请求呢?从分布式微服务架构全局的视角来看,降级处理方案:
- 页面降级:可视化界面禁用点击按钮、调整静态页面
- 延迟服务:如定时任务延迟处理、消息入MQ后延迟处理
- 写降级:直接禁止相关写操作的服务请求
- 读降级:直接禁止相关读的服务请求
- 缓存降级:使用缓存方式来降级部分读频繁的服务接口
针对后端代码层面的降级处理策略,则我们通常使用以下几种处理措施进行降级处理:
- 抛异常
- 返回NULL
- 调用Mock数据
- 调用Fallback处理逻辑
3)分级降级
结合服务能否降级的优先原则,并根据台风预警(都属于风暴预警)的等级进行参考设计,可将分布式服务架构的所有服务进行故障风暴等级划分为以下四种:
4.服务限流
1)什么是服务限流
限流并非新鲜事,在生活中亦无处不在,下面例举一二:
- 博物馆:限制每天参观总人数以保护文物
- 地铁安检:有若干安检口,乘客依次排队,工作人员根据安检快慢决定是否放任进去。遇到节假日,可以增加安检口来提高处理能力,同时增加排队等待区长度
- 水坝泄洪:水坝可以通过闸门控制泄洪速度
以上“限流”例子,可以让服务提供者提供稳定的服务客户
限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。
2)多维度限流
在请求到达目标服务接口的时候,可以使用多维度的限流策略,这样就可以让系统平稳度过瞬间来临的并发
3)限流算法
(1)限流算法-计数器(固定窗口)
计数器限制每一分钟或者每一秒钟内请求不超过一定的次数,在下一秒钟计数器清零重新计算
客户端在第一分钟的59秒请求100,在第二分钟的第1秒又请求了100次,2秒内后端会受到200次请求的压力,形成了流量突刺
(2)限流算法-计数器(滑动窗口)
滑动窗口其实是细分后的计数器,它将每个时间窗口又细分成若干个时间片段,每过一个时间片段,整个时间窗口就会往右移动一格
时间窗口往右滑动一格,这时这个时间窗口其实已经打满了100次,客户端将被拒绝访问,时间窗口划分的越细,滑动窗口的滚动就越平滑,限流的效果就会越精确
(3)限流算法-漏桶
漏桶算法类似一个限制出水速度的水桶,通过一个固定大小FIFO队列+定时取队列元素的方式实现,请求进入队列后会被匀速的取出处理(桶地步开口匀速出水),当队列被占满后后来的请求会直接拒绝(水倒的太快从桶中漏出来)
优点是可以削峰填谷,不论请求多大多块,都只会均速发给后端,不会出现突刺现象,保证下游服务正常运行,缺点就是在桶队列的请求会排队,响应时间拉长。
(4)限流算法令牌桶
令牌桶算法是以一个恒定的速度往桶里面放置令牌(如果桶里面的令牌满了就废弃),每进来一个请求去桶里面找令牌,有的话就拿走令牌继续处理,没有就拒绝请求
令牌桶的优点是可以应对突发流量,当桶里有令牌时请求可以快速的响应,也不会产生漏桶队列中的等待时间,缺点就是相对漏桶一定程度上减小了对下游服务的保护
5.服务熔断
1)什么是服务熔断
在互联网系统中,当下游服务因访问压力过大而响应变得慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
如果不采取熔断措施,我们的系统会怎样呢?
举例说明:
当前系统中有A、B、C三个服务,服务A是上游,服务B是中游,服务C是下游,它们的调用链如下:
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮
像这种调用链路的连锁故障,叫做雪崩
2)熔断机制
在这种时候,就需要熔断机制来挽救整个系统
需要解释两点:
- 开启熔断
- 在固定时间窗口,接口调用超时比率达到一个阈值,就会开启熔断
- 进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果
- 熔断回复
- 熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态恢复过来,再次接受调用方的远程调用
3)熔断机制实现
(1)SpringCloud Hystrix
SpringCloud Hystrix是基于Netflix的开源框架Hystrix实现,该框架实现了服务熔断、线程隔离等一系列服务保护功能。
对于熔断机制的实现,Hystrix设计了三种状态:
- 熔断关闭状态(Closed)
- 服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
- 熔断开启状态(Open)
- 在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开启状态。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
- 半熔断状态(Half-Open)
- 在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态
三个状态的转化关系如下图:
(2)Sentinel
Sentinel和Hystrix的原则是一致的:当调用链路中某个资源出现不稳定,例如,表现为timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,防止避免影响到其他的资源,最终产生雪崩的效果。
Sentinel熔断手段:
- 通过并发线程数进行限制
- 通过响应时间对资源进行降级
- 系统负载保护
6.服务链路追踪
1)什么是链路追踪
分布式微服务架构上通过业务来划分服务的,通过REST调用对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。
分布式链路追踪(Distributed Tracing),也叫分布式链路追踪,分布式跟踪,分布式追踪等等。其实就是将一次分布式请求还原成调用链路。显示的在后端查看一次分布式请求的调用情况,比如各个节点上耗时、请求具体打到哪台机器上、每个服务节点的请求状态等等。
2)链路跟踪具备的功能
(1)故障快速定位
通过调用链跟踪,一次请求的逻辑轨迹可以完整清晰的展示出来。开发中可以在业务日志中添加调用链ID,可以通过调用链结合业务日志快速定位到错误信息。
(2)各个调用环节的性能分析
在调用链的各个环节分别添加调用延时,可以分析系统的性能瓶颈,可以进行针对性的优化。通过分析各个环节的平均延时,QPS等信息,可以找到系统的薄弱环节,对一些模块做调整
(3)数据分析
调用链绑定业务后查看具体每条业务数据对应的链路问题,可以得到用户的行为路径,经过了哪些服务器上的哪个服务,汇总分析应用在很多业务场景。
(4)生成服务调用拓扑图
通过可视化分布式系统的模块和他们之间的相互联系来理解拓扑。点击某个节点会展示这个模块的详情,比如它当前的状态和请求数量。
3)链路跟踪设计原则
(1)设计目的
- 低侵入性,应用透明
- 低损耗
- 大范围部署,扩展性
(2)埋点和生成日志
埋点即系统在当前节点的上下文信息,可以分为客户端埋点、服务端埋点,以及客户端和服务端双向型埋点。埋点日志通常要包含以下内容:
TraceId、RPCId、调用的开始时间、调用类型、协议类型、调用方ip和端口、请求的服务名等信息;调用耗时、调用结果、异常信息、消息报文等。
(3)抓取和存储日志
日志的采集和存储有许多开源的工具可以选择,一般来说,会使用离线+实时的方式去存储日志,主要是分布式日志采集的方式。典型的解决方案如Flume结合Kafka
(4)分析和统计调用链数据
一条调用链的日志散落在调用经过的各个服务器上,首先需要按TraceId汇总日志,然后按照RpcId对调用链进行顺序整理。调用链数据不要求百分之百准确,可以允许中间的部分日志丢失
(5)计算和展示
汇总得到各个应用节点的调用链日志后,可以针对性的对各个业务线进行分析。需要对具体日志进行整理,进一步储存在HBase或者关系型数据库中,可以进行可视化的查询。
4)链路跟踪Trace模型
Trace调用模型,主要有以下概念
术语 | 解释 |
---|---|
Trace | 一次完整的分布式调用跟踪链路 |
Span | 跟踪服务调用基本结构,表示跨服务的一次调用;多Span形成树形结构,组合成一次Trace追踪记录 |
Annotation | 在Span中的标注点,记录整个Span时间段内发生的事件: |
Cs CLIENT_SEND,客户端发起请求 | |
Cr CLIENT_RECIEVE,客户端收到响应 | |
Sr SERVER_RECIEVE,服务端收到请求 | |
Ss SERVER_SEND,服务端发送结果 | |
BinaryAnnotation | 可以认为是特殊的Annotation,用户自定义事件: |
Event 记录普通事件 | |
Exception 记录异常事件 |
Client&Server:对于跨服务的一次调用,请求发送方为client,服务提供方为Server各术语在一次分布式调用中,关系如下图所示:
链路跟踪系统实现:
大的互联网公司都有自己的分布式跟踪系统,比如Google的Dapper,Twitter的zipkin,淘宝的鹰眼,新浪的Watchman,京东的Hydra等等。