分布式系统Sentinel整合Open-Feign限流
一、前言
提示:本文讲到的代码部分来自上文
上文链接地址
二、sentinel控制台
1、调用与被调用方引入依赖
<!--sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、控制台搭建
控制台搭建地址
下载安装包sentinel-dashboard-1.8.0.jar
3、启动
//启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本,
//-Dserver.port=8100 用于指定 Sentinel 控制台端口为 8100
//默认用户名和密码都是 sentinel
//普通进程
java -Dserver.port=8100 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
//守护进程
nohup java -Dserver.port=8100 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar &
三、限流配置
1、多个微服务接入Sentinel配置
调用与被调用都需配置port不一样
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9999
#dashboard: 8080 控制台端口
#port: 9999 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
设置QPS单机阀值为2,一秒大于两个会被流控
效果图
四、面板介绍
资源名:默认是请求路径,可自定义
针对来源:对哪个微服务进行限流,默认是不区分来源,全部限流,这个是针对区分上游服务进行限流, 比如 other1服务 被 other2服务、other3服务调用,就可以针对来源进行限流
-
流量控制(flow control)
原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。 -
两种规则
-
基于统计并发线程数的流量控制
并发数控制用于保护业务线程池不被慢调用耗尽
Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目)
如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。 -
基于统计QPS的流量控制
当 QPS 超过某个阈值的时候,则采取措施进行流量控制(一秒内访问)
-
-
流量控制的效果包括以下几种
- 直接拒绝:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝
- Warm Up:冷启动/预热,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值
- 匀速排队:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
- 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。
匀速排队模式暂时不支持 QPS > 1000 的场景
- 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。
Warm Up:冷启动/预热,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值
五、基于并发线程数进行限流配置
流控规则会下发到微服务,微服务如果重启,则流控规则会消失可以持久化配置
@RestController
@RequestMapping("other2")
public class OtherController2 {
/**
* 基于并发线程数进行限流配置,测试
* @return
*/
@RequestMapping("getusername6")
String getusername6() {
try {
TimeUnit.SECONDS.sleep(3);//让程序睡三秒中达到延迟效果
} catch (InterruptedException e) {
e.printStackTrace();
}
return "other2的getUserName6反回的数据";
}
}
六、降级熔断策略
-
慢调用比例(响应时间): 选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
- 比例阈值:修改后不生效-目前已经反馈给官方那边的bug
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
-
异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 比例阈值
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断
-
异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断
- 异常数:
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
1、熔断实操
测试代码
@RestController
@RequestMapping("other2")
public class OtherController2 {
int temp = 0;
/**
* 熔断,测试
* @return
*/
@RequestMapping("getusername7")
String getusername7() {
temp++;
if(temp%3 == 0){
throw new RuntimeException();
}
return "other2的getUserName7反回的数据";
}
}
2、自定义降级异常数据
- 异常种类
- FlowException //限流异常
DegradeException //降级异常
ParamFlowException //参数限流异常
SystemBlockException //系统负载异常
AuthorityException //授权异常
- FlowException //限流异常
实现BlockExceptionHandler并且重写handle方法
调用方
@Component
public class OtherConfig2 implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Map<String, Object> backMap = new HashMap<>();
if (e instanceof FlowException) {
backMap.put("code", -1);
backMap.put("msg", "限流-异常啦");
backMap.put("data", null);
} else if (e instanceof DegradeException) {
backMap.put("code", -2);
backMap.put("msg", "降级-异常啦");
backMap.put("data", null);
} else if (e instanceof ParamFlowException) {
backMap.put("code", -3);
backMap.put("msg", "热点-异常啦");
backMap.put("data", null);
} else if (e instanceof SystemBlockException) {
backMap.put("code", -4);
backMap.put("msg", "系统规则-异常啦");
backMap.put("data", null);
} else if (e instanceof AuthorityException) {
backMap.put("code", -5);
backMap.put("msg", "认证-异常啦");
backMap.put("data", null);
}
// 设置返回json数据
httpServletResponse.setStatus(200);//响应消息的状态码
httpServletResponse.setHeader("content-Type", "application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(backMap, SerializerFeature.WriteMapNullValue));
}
}
效果图
七、Sentinel整合OpenFeign配置容错设计实战
但是Feign在调用远程服务的时候,由于远程服务的原因,可能会产生异常。就需要进行相应的容错设计。
当Feign远程调用服务出现问题后,进入到了容错工厂类中的同名方法,执行容错逻辑。而且,Feign调用产生的异常信息也可以获取到。
1、加入依赖
<!--sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、开启Feign对Sentinel的支持
调用方
#开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
3、配置feign容错类
调用方的service
@FeignClient(value = "spring-other1" ,fallback = OtherServiceFallBack2.class)//服务名称记得和nacos保持一样
例如
@FeignClient(value = "spring-other1" ,fallback = OtherServiceFallBack2.class)//服务名称记得和nacos保持一样
public interface OtherService2 {
/**
* 新建接口用于调用Other的getUserName2接口服务
*
* @param userName
* @return
*/
@RequestMapping("/other1/getusername2")
String getOther1UserName2(@RequestParam("name") String userName);
}
4、FallBack类继承外调service接口
@Service
public class OtherServiceFallBack2 implements OtherService2 {
@Override
public String getOther1UserName2(String userName) {
return userName+"——该返回为异常返回";
}
}
5、调用方controller
@RestController
@RequestMapping("other2")
public class OtherController2 {
/**
* 使用Open-Feign
*
* @param userName
* @return
*/
@RequestMapping("getusername4")
String getUserName4(@RequestParam("name") String userName) {
String other1UserName2 = otherService2.getOther1UserName2(userName);
//写入服务名即可
return other1UserName2 + "-该字段为other2的getUserName反回的数据";
}
}
6、当被调用方服务出现宕机异常
效果图
八、升级jdk11
下载jdk11
maven项目配置
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>