首先需要下载控制台jar包并在本地启动
官方文档 , 下载地址,注意版本需要和maven引入的版本一致
第二步,启动
java -jar sentinel-dashboard.jar
# 这些参数 -D 是固定写法,后面再跟一些配置参数
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中 -Dserver.port=8080
用于指定 Sentinel 控制台端口为 8080
,默认就是8080,我们可以修改。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
。可以参考 鉴权模块文档 配置用户名和密码。
注:若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档。
然后在浏览器中进行访问
用户名密码默认都是sentinel,登录进去是没有东西的,之后sentinel客户端结合使用之后就会有内容了
客户端接入控制台
引入坐标
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
<version>x.y.zversion>
dependency>
配置启动参数
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx
指定客户端监控 API 的端口(默认是 8719)。
除了修改 JVM 参数,也可以通过配置文件取得同样的效果。更详细的信息可以参考 启动配置项。
优先级顺序:JVM -D 参数的优先级最高。若 properties 和 JVM 参数中有相同项的配置,以 JVM 参数配置的为准。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAxodfzw-1658027726248)(E:\Java笔记\image\springcloud_alibaba\image-20220622223542947.png)]
服务启动成功之后,还需要请求一下接口,sentinel控制台才能有显示。也就是接下来的触发访问量
确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。
注意:您还需要根据您的应用类型和接入方式引入对应的 适配依赖,否则即使有访问量也不能被 Sentinel 统计。
我们现在在代码中写了一些资源的服务容错规则,所以现在这些菜单中就会有我们配置的内容,到时候和SpringCloud Alibaba整合之后,刚进来肯定是没有的,我们需要为每一个资源设置对应的规则,还有就是我们结合SpringCloud Alibaba之后,我们在控制台配置了规则之后,各个微服务重启之后,我们配置的东西也就消失了
第一步,引入场景启动器,这里就不需要引入上面那么多的依赖了
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
第二步,配置文件中设置控制台地址
server:
port: 8092
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
# 设置sentinel控制台的地址
dashboard: 127.0.0.1:8080
监控接口通过的QPS 和 拒绝的QPS。
显示了每秒钟通过的QPS 、拒绝的QPS、响应时间
每一个接口都有一个实时监控界面,我们可以通过右上角进行搜索关键字
用来显示微服务所有可以进行设置容错规则的资源,我们也可以在此界面为各个资源设置对应的容错规则,然后再相应的菜单下就能看到了
服务中的接口如果没有使用@SentinelResource
注解来指定资源名的话默认就会根据@RequestMapper
注解指定的访问uri路径来命名
流量控制(flow control) ,原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阀值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保证应用的高可用。
添加流控步骤:
在簇点链路菜单下,选择要进行流控的资源,然后点击右侧的流控按钮
QPS(Query Per Second):每秒请求树,就是服务器再一秒钟内处理了多少个请求
然后选择阀值类型并输入阀值
接下来进行测试,1秒钟之内发送请求超过设定的阀值后就会被拒绝,这里是sentinel被流控之后的响应信息,如果我们想要自定义流控拒绝之后的响应信息就可以使用@SentinelResource
注解
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
return "下单成功!";
}
// value 指定资源名
@RequestMapping("/flow")
@SentinelResource(value = "flow", blockHandler = "flowBlockHandler")
public String flow(){
System.out.println("正常访问");
return "正常访问!";
}
/**
* 返回值 请求参数要和源方法一致,注意添加的是BlockException 不是BlockedException
*/
public String flowBlockHandler(BlockException e){
System.out.println("flow 流控了");
return "flow 流控了!";
}
}
然后重启服务,就会发现我们之前设置的规则都没了,没有暂时还没有持久化,我们现在需要重新为flow接口添加流控规则
添加完流控规则后进行访问测试
并发线程数控制用于保护业务线程池不被慢调用耗尽。
比如调用下游服务由于某种原因迟迟得不到响应,所有的请求都会堆积在调用方这边,也就会占用更多的线程,最终撑爆线程池,拖垮服务。
sentinel并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阀值,新的请求就会立即拒绝,效果类似于信号量隔离。并发数控通常在调用端进行配置。
我们一般会使用压测,测试接口的瓶颈,然后再设置线程数的阀值,这里进行测试就暂时想阀值设置为很小。
接下来进行测试,我们需要修改一下这个rest接口,不能让它请求就立刻响应,加一个睡眠几秒。
@RequestMapping("/flow")
@SentinelResource(value = "flow", blockHandler = "flowBlockHandler")
public String flow() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
System.out.println("正常访问");
return "正常访问!";
}
/**
* 返回值 请求参数要和源方法一致,注意添加的是BlockException 不是BlockedException
*/
public String flowBlockHandler(BlockException e){
System.out.println("flow 流控了");
return "flow 流控了!";
}
然后使用两个不同的浏览器访问这个接口,就能够体现出并发线程的流控了。
在此之前,我们如果想要自定义返回结果需要通过@SentinelResource
注解中的blockHandler
属性来指定,如果不想在每个接口上面都使用@SentinelResource
注解的话我们就可以使用BlockException来进行统一异常处理。
但是如果我们想针对不同的业务逻辑返回特定的数据的话,就还是需要使用@SentinelResource
注解中的blockHandler
属性来指定。
首先创建一个返回对象实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result error(Integer code, String msg){
return new Result(code, msg);
}
}
定义统一异常处理
package com.hs.springcloud.exception;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hs.springcloud.domain.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description: BlockException统一异常处理类,我们需要让该类实现BlockExceptionHandler接口,并且被spring容器管理
* @Author 胡尚
* @Date: 2022/6/26 15:55
* @Version 1.0
*/
@Component
@Slf4j
public class MyBlockException implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// 根据最后一个参数BlockException来判断数据哪一种容错规则了
// getRule()会包含一些资源信息、规则的详细信息
log.info("BlockExceptionHandler BlockException================={}", e.getRule());
Result result = null;
if (e instanceof FlowException){
result = Result.error(500, "接口限流了");
}else if (e instanceof DegradeException){
result = Result.error(500, "服务降级了");
}else if (e instanceof ParamFlowException){
result = Result.error(500, "热点参数限流了");
}else if (e instanceof SystemBlockException){
result = Result.error(500, "触发系统保护规则了");
}else if (e instanceof AuthorityException){
result = Result.error(500, "授权规则不通过");
}
// 返回json数据
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), result);
}
}
最后进行测试,首先将原来flow接口上面的@SentinelResource
注解删掉,测试统一的异常处理
如果需要针对不同的接口去做不同的业务逻辑处理的话,还是需要使用@SentinelResource
注解来指定
流控规则中还有一些高级选项
这里有三种流控模式:直接、关联、链路
直接
默认的流控模式,之前使用的都是直接流控模式
直接流控模式:我们对某个资源设置流控规则,一旦流量超过指定的阀值之后受影响而限制的资源也就是它本身。
关联
当关联资源达到阀值后,对当前资源流控。
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。
可以使用关联限流来避免具有关联关系的资源之间过度的争抢
关联流控模式:一旦/order/add
的流量超过了这里设置的阀值,受影响而限制的是/order/flow
资源
链路
链路流控模式:这里主要是有一个调用链路,它是根据这个调用链路来进行流控的。
下图中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一颗调用树,这里有一个test1接口和test2接口,他们都调用了getUser资源。Sentinel允许只根据某个入口的统计信息对资源限流。
我们需要明确一点,sentinel不仅仅可以针对接口解析流控,还可以针对业务方法进行流控
创建一个service接口
@Service
public class UserServiceImpl implements UserService {
@SentinelResource(value = "getUser")
@Override
public User getUser() {
return new User("hushang");
}
}
然后在controller中创建两个接口调用getUser()方法
@RequestMapping("/test1")
public User test1() {
return userService.getUser();
}
@RequestMapping("/test2")
public User test2() {
return userService.getUser();
}
接下来再控制台设置流控规则
问题一:
配置完后,我们会发现链路规则不生效,原因如下:
从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0版本开始(对应SCA的2.1.1.RELEASE) , 官方在CommonFilter引入了web-context-unify参数,用来控制支付收敛context,将其设置为false即可根据不同的URL进行链路限流
# 设置为false使链路限流生效,默认将调用链路收敛了
spring.cloud.sentinel.web-context-unify=false
问题二:
此场景拦截不到BlockException,因为我们一旦使用了@SentinelResource
注解就不会去运用统一异常处理了 所以我们只能在@SentinelResource
注解中使用blockHandler
属性来指定,不然就直接抛异常到页面上面了
所以我们需要在service层来指定
@Service
public class UserServiceImpl implements UserService {
@SentinelResource(value = "getUser", blockHandler = "getUserBlockHandler")
@Override
public User getUser() {
return new User("hushang");
}
/**
* 这里的最后一个参数BlockException 是一定要加的
*/
public User getUserBlockHandler(BlockException e) {
return new User("userService 被限流了");
}
}
重启服务,重新在控制台配置限流规则,然后进行测试
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。线程数是没有流控效果的。
流量控制的效果包括以下几种:直接拒绝、Warm Up、排队等待。对应代码开发中 FlowRule
中的 controlBehavior
字段。
快速失败(RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException
。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。处理激增流量,当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子:codeFactor 默认是3,也就是刚开始放行的请求数 = QPS阀值 / 3 , 然后再看看递增,逐渐提升至设定的QPS阀值
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
如下图所示,我设置一个预热时长为7秒,它不会一瞬间接收20个请求,它会慢慢的接收,可能刚开始第一秒只能接收3个,可能第二秒最多只能接收5,就这样在我们设定的预热时长内慢慢的增加,当7秒后才会每秒接收20的请求,
使用场景就比如电商项目的秒杀场景,如果刚开始一万个请求直接访问,这个时候缓存中也没有数据,这么多请求就直接落到了数据库中,把数据库弄宕机了,这也就是缓存击穿。这种情况就可以使用warm up,先放一些请求进来,去访问数据库,然后将查询的数据存入缓存中,在放更多的请求进来,之后的请求就可以去缓存中获取数据,这就可以避免激增流量瞬间打垮冷系统。这只是冷系统中的一种例子。
案例:
设置下图所示,5秒后才会每秒接收10个请求
然后每秒发送10个请求,结果如下
排队等待(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。处理脉冲流量,详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
如下图所示,QPS的阀值是10,如果一秒钟之内有25个请求,剩下的15个请求B不会立刻失败掉,而是会等待5秒,在我们指定是超时时间中处理10个请求A,然后再来处理多余的15个请求B。
5秒钟之内处理10的请求A,处理完一个请求A就来处理请求B,如果在规定的5秒内,10的请求A都还没有处理完,那么15个请求B就直接拒绝掉,就和上厕所一样,总共只有10个坑,出来一个人就进去一个人,在外面等待的人最多只能等待5秒。
下图是快速失败和排队等待的区别:
熔断降级通常在服务消费方进行配置。
熔断策略:
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。触发熔断后的处理措施:
熔断降级规则(DegradeRule)包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
接下来使用控制台的图片来介绍熔断策略
首先是慢调用比例
单位时间statIntervalMs默认是1000ms,在控制台没有设置这个值的窗口,但是代码方式可以设置。
异常比例:
在单位时间内,最少发送5次请求,如果这其中出现异常的比例大于了0.4就会触发熔断10s。
结果如下:
异常数
单位时间内,发送8次请求,如果其中出现了超过5次异常就触发熔断
服务消费方调用服务提供方的时候,如果服务提供者发生了异常,会把异常传播给服务消费方,我们这个时候就可以对服务提供方进行熔断降级的处理 ,去调用降级的方法。
我们使用openfeign的话,还需要启动nacos注册中心
服务提供方:
@RestController
@RequestMapping("/stock")
public class StockController {
// 这里创建一个接口,并且每次调用都抛出异常,然后启动该服务
@RequestMapping("/reduce2")
public String reduce2(){
int i = 1/0;
return "库存减少!";
}
}
服务消费方:
第一步:首先导入maven坐标
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
第二步,yml配置文件编写
server:
port: 8093
spring:
application:
name: order-service
cloud:
# nacos相关配置
nacos:
server-addr: 127.0.0.1:8848
discovery:
username: nacos
password: nacos
namespace: public
# 添加feign对sentinel的支持
feign:
sentinel:
enabled: true
第三步,创建Feign的调用接口
// 这里要使用fallback属性来指定下一步创建降级的类
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
@RequestMapping("/reduce2")
String reduce2();
}
第四步,创建OpenFeign的降级的类,这个类必须去实现上一步中Feign的调用接口,并注入到spring容器中
@Component
public class StockFeignServiceFallback implements StockFeignService{
@Override
public String reduce2() {
return "降级了,进入兜底方法了!";
}
}
第五步,编写controller接口并进行测试
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private StockFeignService stockFeignService;
@RequestMapping("/add")
public String add(){
String msg = stockFeignService.reduce2();
return "下单成功!" + msg;
}
}
最后进行测试,之前不加降级的兜底方法的话这里会直接抛异常,现在会执行降级方法
热点即经常访问的数据,很多时我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制,就可以使用热点规则
如下所示,可能id=1或id=2的访问量比较大,id为其他值的时候访问量比较平缓,这个时候我们就可以针对id=1或id=2的时候来进行流控
@RequestMapping("/get/{id}")
public String getById(@PathVariable("id") Integer id){
System.out.println("通过id获取数据");
return "正常 通过id获取数据";
}
常用场景:
接下来我们实际实现一下
创建一个接口
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById", blockHandler = "getByIdBlockHandler")
public String getById(@PathVariable("id") Integer id){
System.out.println("通过id获取数据");
return "正常 通过id获取数据";
}
public String getByIdBlockHandler(@PathVariable("id") Integer id, BlockException e){
System.out.println("通过id获取数据,热点异常处理");
return "热点异常处理";
}
然后启动服务,sentinel控制台中设置规则的界面如下
参数索引
就是我们要进行热点流控的参数,是接口中的第几个参数,也就是rest接口形参的位置,索引是从0开始的。
单机阈值
这个阈值主要是针对第二行的QPS来进行设置的,这里首先是对这个参数id 设置一个全局的阈值,也就是不管id等于多少,都先设置一个公共的阈值,点击新增按钮后,在根据id具体的值来设置具体的阈值。
这里有两种填写情况
统计窗口时长
QPS一般都是每秒的访问数据,这里是设置统计几秒中的QPS
点击新增按钮之后,如果想为参数特定的值来设置阈值的步骤如下:
为特定的值设置阈值
添加完成之后记得在点击一些编辑按钮,查看一下我们刚刚添加的是否存在,我刚刚就是没有生效,然后添加了多个规则,然后删除,才生效
然后进行测试
在线上环境中,可能会有很多不可预知的一些因素来威胁系统的稳定性。比如:
控制台界面如下:
注意系统规则只针对入口资源(EntryType=IN)生效。
maxQPS * minQPS
估算得出。设定参考值一般是CPU cores * 2.5
CPU使用率
如果cpu的使用率大于了10%,就会触发系统保护规则
此时我本机的cpu使用率超过了这个阈值,现在访问任何接口都会报错