调用三个依赖服务会共享商品详情服务的线程池
.
如果其中的商品评论服务不可用
,
就会出现线程池里所有线程都因等待
响应而被阻塞
,
从而造成
服务雪崩
.
如图所示
:
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用
,
并将不可用逐渐放大的过程,就叫服务雪崩效应
导致服务不可用的原因: 程序
Bug
,大流量请求,硬件故障,缓存击穿
【
大流量请求
】:
在秒杀和大促开始前
,
如果准备不充分
,
瞬间大量请求会造成服务提供者的不可用。
【
硬件故障
】:
可能为硬件损坏造成的服务器主机宕机
,
网络硬件故障造成的服务提供者的不可访问。
【
缓存击穿
】:
一般发生在缓存应用重启
,
缓存失效时高并发,所有缓存被清空时
,
以及短时间内大量缓存失效时。大量的
缓存不命中
,
使请求直击后端
,
造成服务提供者超负荷运行
,
引起服务不可用。
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求
流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调
用时
,
会产生大量的等待线程占用系统资源。一旦线程资源被耗尽
,
服务调用者提供的服务也将处于不可用状态
,
于是服务
雪崩效应产生了。
1.2
解决方案
超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,
一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。
服务限流
(
资源隔离
)
限制请求核心服务提供者的流量,使大流量拦截在核心服务之外,这样可以更好的保证核心服务提供者不出问题,对于
一些出问题的服务可以限制流量访问,只分配固定线程资源访问,这样能使整体的资源不至于被出问题的服务耗尽,进
而整个系统雪崩。那么服务之间怎么限流,怎么资源隔离?例如可以通过线程池
+
队列的方式,通过信号量的方式。
如下图所示
,
当商品评论服务不可用时
,
即使商品服务独立分配的
20
个线程全部处于同步等待状态
,
也不会影响其他依赖服 务的调用。
服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。
现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路
被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数
/
失败率达到一定阈值,就
“
跳闸
”
,断路
器打开
——
此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如
10
秒),断路器会进入半开状
态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不
成功,断路器继续回到打开状态,过段时间再进入半开状态尝试
——
通过
”
跳闸
“
,应用可以保护自己,而且避免浪费资
源;而通过半开的设计,可实现应用的
“
自我修复
“
。
所以,同样的道理,
当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。
比如
我们设置了超时时间为
1s,
如果短时间内有大量请求在
1s
内都得不到响应,就意味着这个服务出现了异常,此时就没有必 要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的
fallback
(回退)回调,
返回一个缺省值。 例如:
(
备用接口
/
缓存
/mock
数据
)
。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当
然这也要看适合的业务场景
2. Sentinel:
分布式系统的流量防卫兵
2.1 Sentinel
是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel
是面向分布式服务架构的流量控制组件,主要以
流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定
性。
源码地址:
https://github.com/alibaba/Sentinel
官方文档:
https://github.com/alibaba/Sentinel/wiki
Sentinel
具有以下特征
:
丰富的应用场景
:
Sentinel
承接了阿里巴巴近
10
年的双十一大促流量的核心场景,例如秒杀(即突发流量控
制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
完备的实时监控
:
Sentinel
同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,
甚至
500
台以下规模的集群的汇总运行情况。
广泛的开源生态
:
Sentinel
提供开箱即用的与其它开源框架
/
库的整合模块,例如与
Spring Cloud
、
Dubbo
、
gRPC
的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入
Sentinel
。
完善的
SPI
扩展点
:
Sentinel
提供简单易用、完善的
SPI
扩展点。您可以通过实现扩展点,快速的定制逻辑。
例如定制规则管理、适配数据源等。
阿里云提供了 企业级的
Sentinel
服务,
应用高可用服务 AHAS
2.2.2 Sentinel
工作主流程
https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B
在
Sentinel
里面,所有的资源都对应一个资源名称(
resourceName
),每次资源调用都会创建一个
Entry
对象。
Entry
可以
通过对主流框架的适配自动创建,也可以通过注解的方式或调用
SphU
API
显式创建。
Entry
创建的时候,同时也会创建一
系列功能插槽(
slot chain
),这些插槽有不同的职责,例如
:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来
限流降级;
ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的
RT, QPS, thread count
等等,这
些信息将用作为多维度限流,降级的依据;
StatisticSlot
则用于记录、统计不同纬度的
runtime
指标监控信息;
FlowSlot
则用于根据预设的限流规则以及前面
slot
统计的状态,来进行流量控制;
AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;
SystemSlot
则通过系统的状态,例如
load1
等,来控制总的入口流量;
@SentinelResource
注解实现
@SentinelResource
注解用来标识资源是否被限流、降级。
blockHandler:
定义当资源内部发生了
BlockException
应该进入的方法(捕获的是
Sentinel
定义的异常)
fallback:
定义的是资源内部发生了
Throwable
应该进入的方法
exceptionsToIgnore
:配置
fallback
可以忽略的异常
源码入口:
com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
1.
引入依赖
1
<
dependency
>
2
<
groupId
>
com
.
alibaba
.
csp
groupId
>
3
<
artifactId
>
sentinel
‐
annotation
‐
aspectj
artifactId
>
4
<
version
>
1.8.0
version
>
5
dependency
>
2.
配置切面支持
1
@Configuration
2
public class
SentinelAspectConfiguration
{
3
4
@Bean
5
public
SentinelResourceAspect
sentinelResourceAspect
() {
6
return new
SentinelResourceAspect
();
7
}
8
}
3.
UserController
中编写测试逻辑,添加
@SentinelResource
,并配置
blockHandler
和
fallback
1
@
RequestMapping
(
value
=
"/findOrderByUserId/{id}"
)
2
@
SentinelResource
(
value
=
"findOrderByUserId"
,
3
fallback
=
"fallback"
,
fallbackClass
=
ExceptionUtil
.
class
,
4
blockHandler
=
"handleException"
,
blockHandlerClass
=
ExceptionUtil
.
class
5
)
6
public
R
findOrderByUserId
(
@
PathVariable
(
"id"
)
Integer id
) {
7
//ribbon
实现
8
String url
=
"http://mall‐order/order/findOrderByUserId/"
+
id
;
9
R
result
=
restTemplate
.
getForObject
(
url
,
R
.
class
);
10
11
if
(
id
==
4
){
12
throw new
IllegalArgumentException
(
"
非法参数异常
"
);
13
}
14
15
return
result
;
16
}
4.
编写
ExceptionUtil
,注意如果指定了
class
,方法必须是
static
方法
1
public class
ExceptionUtil
{
2
3
public static
R
fallback
(
Integer id
,
Throwable e
){
4
return
R
.
error
(
‐
2
,
"===
被异常降级啦
==="
);
5
}
6
7
public static
R
handleException
(
Integer id
,
BlockException e
){
8
return
R
.
error
(
‐
2
,
"===
被限流啦
==="
);
9
}
10
}
5.
流控规则设置可以通过
Sentinel dashboard
配置
客户端需要引入
Transport
模块来与
Sentinel
控制台进行通信。
1
<
dependency
>
2
<
groupId
>
com
.
alibaba
.
csp
groupId
>
3
<
artifactId
>
sentinel
‐
transport
‐
simple
‐
http
artifactId
>
4
<
version
>
1.8.0
version
>
5
dependency
>
6.
启动
Sentinel
控制台
下载控制台
jar
包并在本地启动:可以参见
此处文档
1
#
启动控制台命令
2
java
‐
jar sentinel
‐
dashboard
‐
1.8.0
.
jar
用户可以通过如下参数进行配置:
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为
sentinel
;
-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为
123456
;如果省略这两个参数,默认用户和密码均为
sentinel
;
-Dserver.servlet.session.timeout=7200
用于指定
Spring Boot
服务端
session
的过期时间,如
7200
表示
7200
秒;
60m
表示
60
分钟,
默认为
30
分钟;
访问
http://localhost:8080/#/login
,
默认用户名密码:
sentinel/sentinel
Sentinel
会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;
2.4 Spring Cloud Alibaba
整合
Sentinel
1.
引入依赖
1
<
dependency
>
2
<
groupId
>
com
.
alibaba
.
cloud
groupId
>
3
<
artifactId
>
spring
‐
cloud
‐
starter
‐
alibaba
‐
sentinel
artifactId
>
4
dependency
>
5
6
<
dependency
>
7
<
groupId
>
org
.
springframework
.
boot
groupId
>
8
<
artifactId
>
spring
‐
boot
‐
starter
‐
actuator
artifactId
>
9
dependency
>
2.添加yml配置,
为微服务设置
sentinel
控制台地址
添加Sentinel后,需要暴露/actuator/sentinel端点,而Springboot默认是没有暴露该端点的,所以需要设置,测试
http://localhost:8800/actuator/sentinel
1
server
:
2
port
:
8800
3
4
spring
:
5
application
:
6
name
:
mall
‐
user
‐
sentinel
‐
demo
7
cloud
:
8
nacos
:
9
discovery
:
10
server
‐
addr
:
127.0.0.1
:
8848
11
12
sentinel
:
13
transport
:
14
#
添加
sentinel
的控制台地址
15
dashboard
:
127.0.0.1
:
8080
16
#
指定应用与
Sentinel
控制台交互的端口,应用本地会起一个该端口占用的
HttpServer
17
# port
:
8719
18
19
#
暴露
actuator
端点
20
management
:
21
endpoints
:
22
web
:
23
exposure
:
24
include
:
'*'
3.在sentinel控制台中设置流控规则
资源名
:
接口的
API
针对来源
:
默认是
default
,当多个微服务都调用这个资源时,可以配置微服务名来对指定的微服务设置阈值
阈值类型
:
分为
QPS
和线程数 假设阈值为
10
QPS
类型
:
只得是每秒访问接口的次数
>10
就进行限流
线程数
:
为接受请求该资源分配的线程数
>10
就进行限流
Sentinel
提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则
管理和推送的功能。
Sentinel
控制台包含如下功能
:
查看机器列表以及健康情况:收集
Sentinel
客户端发送的心跳包,用于判断机器是否在线。
监控
(
单机和集群聚合
)
:通过
Sentinel
客户端暴露的监控
API
,定期拉取并且聚合应用监控信
息,最终可以实现秒级的实时监控。
规则管理和推送:统一管理推送规则。
鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
阿里云提供了 企业级的
Sentinel
控制台,
应用高可用服务 AHAS
1.3
流控规则
流量控制
(
flow control
),其原理是监控应用流量的
QPS
或并发线程数等指标,当达到指定的阈值时对流
量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
同一个资源可以创建多条限流规则。
FlowSlot
会对该资源的所有限流规则依次遍历,直到有规则触发限流
或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限
流效果。
BlockException异常统一处理
springwebmvc
接口资源限流入口在
HandlerInterceptor
的实现类
AbstractSentinelInterceptor
的
preHandle方法中,对异常的处理是
BlockExceptionHandler
的实现类
sentinel 1.7.1 引入了
sentinel-spring-webmvc-adapter.jar
自定义BlockExceptionHandler 的实现类统一处理BlockException
并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务
不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致
线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程
池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程
数目太多,线程上下文切换的
overhead
比较大,特别是对低延时的调用有比较大的影响。
Sentinel
并发控
制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超
出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。
流控效果
当
QPS
超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:
快速失败(直接
拒绝)
、
Warm Up
(预热)、
匀速排队(排队等待)
。对应
FlowRule
中的
controlBehavior
字段。
快速失败
(
RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式是默认的流量控制方式,
当
QPS
超过任意规则的阈值后,新
的请求就会被立即拒绝,拒绝方式为抛出
FlowException
。
这种方式适用于对系统处理能力确切已知的情况
下,比如通过压测确定了系统的准确水位时。
Warm Up
Warm Up
(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热
/
冷启动方式。当系统长期处于低水位的情
况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过
"
冷启动
"
,让通过的流量缓
慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子
: codeFactor
默认是
3
,即请求
QPS
从
threshold / 3
开始,经预热时长逐渐升至设定的
QPS
阈值。
通常冷启动的过程系统允许通过的
QPS
曲线如下图所示
匀速排队
匀速排队(`RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER`)方式会严格控制请求通过的间隔时
间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用
进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级规则说明
熔断降级规则(
DegradeRule
)包含下面几个重要的属性:
熔断策略
慢调用比例
慢调用比例
(
SLOW_REQUEST_RATIO
)
:
选择以慢调用比例作为阈值,需要设置允许的慢调用
RT
(即最大的 响应时间),请求的响应时间大于该值则统计为慢调用
。
当单位统计时长(
statIntervalMs
)内请求数目大
于设置的最小请求数目,并且慢调用的比例大于阈值
,则接下来的熔断时长内请求会自动被熔断。经过熔 断时长后熔断器会进入探测恢复状态(HALFOPEN
状态),若接下来的一个请求响应时间小于设置的慢 调用 RT
则结束熔断,若大于设置的慢调用
RT
则会再次被熔断。
异常比例
异常比例
(
ERROR_RATIO
)
:
当单位统计时长(
statIntervalMs
)内请求数目大于设置的最小请求数目,并
且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
经过熔断时长后熔断器会进入探测恢
复状态(
HALFOPEN
状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔
断。异常比率的阈值范围是
[0.0, 1.0]
,代表
0% 100%
。
异常数
异常数
(
ERROR_COUNT
)
:
当单位统计时长内的异常数目超过阈值之后会自动进行熔断
。经过熔断时长后
熔断器会进入探测恢复状态(
HALFOPEN
状态),若接下来的一个请求成功完成(没有错误)则结束熔
断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
热点参数限流
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的
Top K
数据,并对其访问进行限制。比如:
商品
ID
为参数,统计一段时间内最常购买的商品
ID
并进行限制
用户
ID
为参数,针对一段时间内频繁访问的用户
ID
进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。
热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
系统规则
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平
均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系
统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
Load
自适应
(仅对
Linux/Unixlike
机器生效):系统的
load1
作为启发指标,进行自适应系统
保护。当系统
load1
超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系
统保护(
BBR
阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是
CPU cores *
2.5
。
CPU usage
(
1.5.0+
版本):当系统
CPU
使用率超过阈值即触发系统保护(取值范围
0.0
1.0
),比较灵敏。
平均
RT
:当单台机器上所有入口流量的平均
RT
达到阈值即触发系统保护,单位是毫秒。
并发线程数
:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口
QPS
:当单台机器上所有入口流量的
QPS
达到阈值即触发系统保护。
授权控制规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用
Sentinel
的来源访问控
制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(
origin
)限制资源是否通过,若配置白名
单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求
通过。
来源访问控制规则(
AuthorityRule
)非常简单,主要有以下配置项:
resource
:资源名,即限流规则的作用对象。
limitApp
:对应的黑名单
/
白名单,不同
origin
用
,
分隔,如
appA,appB
。
strategy
:限制模式,
AUTHORITY_WHITE
为白名单模式,
AUTHORITY_BLACK
为黑名单模式,默认为白名
单模式。
滑动时间窗口算法
滑动时间窗口,又称rolling window。为了解决计数器法统计精度太低的问题,引入了滑动窗口 算法。下面这张图,很好地解释了滑动窗口算法:
限流算法小结
计数器 VS 滑动窗口:
1
计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。
2
滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存
储空间。
3
也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。
漏桶算法 VS 令牌桶算法:
1
漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。
2
因为默认的令牌桶算法,取走
token
是不需要耗费时间的,也就是说,假设桶内有
100
个
token
时,
那么可以瞬间允许
100
个请求通过。
3
当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。