Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
先从 github 上下载 sentinel-dashboard-1.8.0.jar 文件;
官方下载地址:https://github.com/alibaba/Sentinel/releases
在 linux 服务器上选择并建立目录;
mkdir sentinel-docker
进入 sentinel-docker 目录,并把下载的 sentinel-dashboard-1.8.0.jar 放至该目录;
cd sentinel-docker
新建一个 Dockerfile 文件,内容如下:
FROM openjdk:8
ENV SENTINEL_HOME /opt/sentinel-dashboard
RUN mkdir -p ${SENTINEL_HOME}
COPY sentinel-dashboard-1.8.0.jar ${SENTINEL_HOME}
RUN chmod -R +x ${SENTINEL_HOME}/*jar
WORKDIR ${SENTINEL_HOME}
EXPOSE 8858
EXPOSE 8719
CMD java ${JAVA_OPS} -jar sentinel-dashboard-1.8.0.jar
新建一个 sentinel-dashboard.yaml 文件,内容如下:
version: '2'
services:
sentinel-dashboard:
image: sentinel-dashboard:1.8.0
container_name: sentinel-dashboard
restart: on-failure
build:
context: .
dockerfile: Dockerfile
ports:
- "8858:8858"
- "8719:8719"
environment:
- JAVA_OPS=-Dserver.port=8858
执行 sentinel-dashboard.yaml 脚本启动容器:
docker-compose -f sentinel-dashboard.yaml up
登录 Sentinel 控制台,默认用户名为 sentinel,密码为 sentinel:
http://(安装Sentinel机器的IP):8858
Sentinel 控制台里目前什么都没有,如何使用呢?这里我们先不急,先来配置 Spring Boot 项目客户端。
添加 pom 文件依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
在 application.yml 添加配置:
server:
port: 10801
servlet:
context-path: /api/member
spring:
application:
name: member-service
cloud:
sentinel:
eager: true
transport:
port: 8719
dashboard: (安装Sentinel机器的IP):8858
新建一个 Service 类添加一个方法,添加注解 @SentinelResource:
@Service
public class MemberService {
@SentinelResource(value = "sayHello", fallback = "sayHelloFail")
public String sayHello() {
return "Hello, Member! ";
}
public String sayHelloFail() {
return "I am sorry, Member! ";
}
}
新建一个 Controller 类来调用这个 Service 的方法:
@RestController
@RequestMapping
public class HelloController {
@Resource
private MemberService memberService;
@RequestMapping("/service")
public String service() {
return memberService.sayHello();
}
}
启动 Spring 项目,此时再去控制台会发现项目列表中已经出现,实时监控暂时没数据:
http://(安装Sentinel机器的IP):8858
注意:仔细的同学发现这里的端口并不是 8719,因为我机器上另外开了几个服务,配置同样是 8719 端口,Sentinel 会自动识别冲突,并且按照 (端口号+1) 的规则自动分配端口。
访问下 /api/member/service 这个请求,看看结果:
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
Hello, Member!
重复多访问几次 /api/member/service 这个请求,发现实时监控面板出现数据:
打开刚才的“簇点链路”菜单,尝试对访问路径限流:11
资源名:唯一名称,默认请求路径。
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。
阈值类型
QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
线程数:当调用该api的线程数达到阈值的时候,进行限流。
流控模式
直接:api达到限流条件时,直接限流。
关联:当关联的资源达到阈值时就限流自己。A接口与B接口关联,当B接口到达阈值,让A接口限流起到保护B接口的作用。例如支付接口与下单接口,当支付接口到达阈值,让下单接口限流,起到保护支付接口的作用。
链路:只记录指定链路上的流量(指定资源入口资源进来的流量,如果达到阈值,就进行限流)。
流控效果
快速失败:是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm up:即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
排队等候:会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
我设置的 QPS的值是1,直接api,快速失败,所以 1秒内去多次请求 /api/member/service:
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
Blocked by Sentinel (flow limiting)
事实证明已经被 Sentinel 限制访问,直接快速失败了。
首先,为了防止多规则的干扰,将刚才设置的 访问路径限流规则 删去。
打开刚才的“簇点链路”菜单,尝试对服务资源限流:
QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
我设置的 QPS的值是1,直接api,快速失败,所以 1秒内去多次调用 memberService.sayHello():
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
I am sorry, Member!
结果为什么没有报错?居然能正常返回?仔细的同学会发现都是我设置了降级方法 sayHelloFail() 的功劳:
@SentinelResource(value = "sayHello", fallback = "sayHelloFail")
public String sayHello() {
return "Hello, Member! ";
}
public String sayHelloFail() {
return "I am sorry, Member! ";
}
打开刚才的“簇点链路”菜单,尝试对异常服务熔断:
熔断策略
慢调用比例:选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例:当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
我们对代码进行一点小改造,并且重启项目:
@RequestMapping("/service")
public String service(String name) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(400);
return memberService.sayHello(name);
}
我设置的 最大容忍响应时间是 200ms,比例是 50%,代码中强制延迟 400ms,也就是 100% 超时,并且设置了在单位时间内 5 次请求有 50% 是这种情况才会熔断,所以 1 秒内去超过 5 次请求 /api/member/service:
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
Blocked by Sentinel (flow limiting)
事实证明已经被 Sentinel 限制访问,异常服务熔断了。
我设置的 熔断时间为 2 秒,在 2 秒后再去请求 2 次 /api/member/service:
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
Hello, Member!
Blocked by Sentinel (flow limiting)
结果为什么是这样?不是才请求 2 次吗?而且 2 秒后不是应该放行了吗?因为我代码中强制延迟 400ms,2 秒后的请求还是超时,第一次成功是因为需要统计数据。
Sentinel 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
类似 4.2章节 的情形,如果我把代码中强制延迟 400ms 写在资源服务中:
@SentinelResource(value = "sayHello", fallback = "sayHelloFail")
public String sayHello(String name) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(400);
return "Hello, Member! " + name;
}
public String sayHelloFail(String name) {
return "I am sorry, Member! " + name;
}
一样的设置,一样的请求, 1 秒内去超过 5 次请求 /api/member/service:
#### 请求测试
GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
请求得到结果:
I am sorry, Member!
事实证明已经被 Sentinel 限制访问,异常服务虽然已经被熔断,但是设置了降级方法 sayHelloFail() 还是能正常返回结果。
热点就是经常访问的数据;
比如商品接口的 QPS 限定的是 100,有一天要秒杀,带着秒杀商品 ID 的请求的 QPS 限制在 50,这样还能有 50 的 QPS 用来访问其他的商品;
我们先对代码进行一点小改造,加一个请求参数 name**,并且重启项目**:
@Service
public class MemberService {
@SentinelResource(value = "sayHello", fallback = "sayHelloFail")
public String sayHello(String name) {
return "Hello, Member! " + name;
}
public String sayHelloFail(String name) {
return "I am sorry, Member! " + name;
}
}
@RestController
@RequestMapping
public class HelloController {
@Resource
private MemberService memberService;
@RequestMapping("/service")
public String service(String name) {
return memberService.sayHello(name);
}
}
打开的“热点规则”菜单,新增一个资源名为“sayHello”的热点规则:
参数索引 设置 0,表示第一个请求参数
单机阈值 设置 10,统计窗口时长 设置 1
参数值 设置 字符串 baicai,限流阈值 设置 1
总的来说,就是同样的请求 /api/member/service?name=xxx, 当参数传 baicai 时,QPS 例外控制为 1,而参数传别的任何值,QPS 控制为 10。
尝试 1秒内去多次请求 /api/member/service?name=baicai:
#### 请求测试
GET http://localhost:10801/api/member/service?name=baicai
Accept: */*
Cache-Control: no-cache
请求得到结果:
I am sorry, Member! baicai
尝试 1秒内去多次请求 /api/member/service?name=luobo:
#### 请求测试
GET http://localhost:10801/api/member/service?name=luobo
Accept: */*
Cache-Control: no-cache
请求得到结果:
Hello, Member! luobo
事实证明当参数为 baicai 时,QPS 超过 1 就被限制访问,但是设置了降级方法 sayHelloFail() 还是能正常返回结果。
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
测试结果和以上类似,这里就不做过多阐述了。看到这里,Sentinel 的强大好用是否已经征服你了呢?