alibaba微服务通过使用sentinel来进行流量控制,其底层使用actuator来进行监控
在服务消费方引入依赖
<!-- sentinel 流量控制依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<!-- sentinel 底层通过actuator来进行监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置对外暴露的健康监控检查项
# 配置对外暴露的健康监控检查项
management:
endpoints:
web:
exposure:
include: '*' # * 表示全部暴露
下载安装sentinel
去这里下载:https://github.com/alibaba/Sentinel/releases/tag/1.7.0
然后在解压后的目录下打开cmd,运行java -jar -Dserver.prot=9100 sentinel-dashboard-1.7.0.jar,指定他在9100端口运行,默认时8080端口
使用sentinel来进行监控
- 将消费端服务与sentinel监控进行绑定
spring:
cloud:
# 将本服务与sentinel监控进行绑定
sentinel:
transport:
dashboard: localhost:8080
- 在启动消费服务与sentinel后就可以在localhost:9100查看监控台
- 注意:需要先启动sentinel,再启动消费服务,由于消费服务中加了openfeign,如果不开启nacos就会报错起不来
流控规则
在sentinel界面中添加流控规则
上面设置了一秒钟只能访问/test这个接口一次,超过访问次数就会报错
现在介绍下几种流控模式:
关联模式下,关联资源请求超过设置的数目时,访问资源/list就会失败
链路模式:如下图所示,假设我们需要先访问/check,check返回正确后又调用list,在这种情况下访问/check超过限制就会导致调用list失败;和关联模式不同的地方在于,在关联模式下只要访问关联资源超过限制再调用list就会失败,而在链路模式下需要check请求去调用list请求
在等待模式下,请求阈值不能超过1000,超过一千就表示一个请求的响应时间小于1毫秒,这是不合理的;
如果阈值设置为1,超时时间设置为2000毫秒,就表示一秒钟如果有四个及以上请求时就会报错,否则剩下的请求就会排队执行
服务熔断
介绍下这种熔断机制:首先当一个接口每秒钟有20%的处理产生异常时,就会将这个接口触发熔断五秒钟,在这五秒钟内对这个接口的所有操作都会快速失败,五秒过后就会处于半开状态,只有在五秒后第一哥请求这个接口的操作没有发生异常时就会恢复,发生异常就又回到熔断状态进行循环
RT模式:在RT模式下,如下图我设置RT为200毫秒,意思是一秒钟的平均响应时间为200毫秒,也就是说一秒钟可以处理五次请求,超过就会促发熔断,时间窗口表示熔断时间长度
异常比例:当一秒内 QPS>=5 并且 20% 的失败比例,触发熔断,熔断的时间窗口是5s
异常数:注意:时间窗口一定要 >= 60s;在61秒内该请求发生了5次异常就会触发熔断
通过代码来实现限流策略
package com.guming.controller;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.guming.feign.TestFeign;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* 消费者控制器
* @author 先生
* @date 2021/01/23
*/
@RestController
public class ConsumerController {
@Autowired
public TestFeign TestFeign;
/**
* 得到的名字
* @return {@link String}
*/
@GetMapping("/test")
public String getName(){
initFlowRole();
String name = "";
for (int i = 0; i < 5; i++) {
name = TestFeign.getName("我叫王五");
System.out.println(name);
}
return name;
}
/**
* 初始化限流
* @return {@link String}
*/
public static void initFlowRole(){
// 定义一个集合用来存放限流规则
List<FlowRule>flowRules = new ArrayList<>();
// 使用FlowRule来制定限流规则,里面的参数是需要限流的接口
FlowRule flowRule = new FlowRule("/test");
/** 流控模式
* STRATEGY_DIRECT:直接 ;
* STRATEGY_RELATE:关联;
* STRATEGY_CHAIN:链路
*/
flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT);
/** 阈值类型
* FLOW_GRADE_QPS:QPS;
* FLOW_GRADE_THREAD:线程数
*/
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 每秒钟单机阈值,超过就会报错
flowRule.setCount(1);
/** 设置流控效果
* CONTROL_BEHAVIOR_DEFAULT : 直接拒接
* CONTROL_BEHAVIOR_WARM_UP : Warm up 预热 ,需要设置预热时长
* flowRule.setWarmUpPeriodSec(10) : 设置预热时长 单位秒
* CONTROL_BEHAVIOR_RATE_LIMITER : 排队等待 ,需要设置超时时长
* flowRule.setMaxQueueingTimeMs(1000) : 超时时长 单位毫秒
*/
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
flowRules.add(flowRule);
// 将限流策略进行装载
FlowRuleManager.loadRules(flowRules);
}
}
通过代码来实现服务降级
sentinel流量监控原理
当我们在服务中添加了sentinel依赖,并与sentinel进行了绑定后,就会开辟一个8719端口,这个端口的服务就负责监控本服务,并且提供了很多api来给服务调用,这些api可以用来监控项目运行状态;
这个端口是依次增加的,这个服务是8719,下一个服务就是8720
sentinel限流与异常处理结合
最开始被限流后界面返回一串英文,现在我们可以自定义返回内容。
先定义一个返回类
package com.guming.vo;
public class ResponseCode {
private String code;
private String message;
private Object date;
public ResponseCode(String code, String message) {
this.code = code;
this.message = message;
}
public ResponseCode(String code, String message, Object date) {
this.code = code;
this.message = message;
this.date = date;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getDate() {
return date;
}
public void setDate(Object date) {
this.date = date;
}
}
限流与熔断异常处理
然后通过一个类实现BlockExceptionHandler来处理限流和熔断异常
package com.guming.handler;
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.flow.FlowException;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.guming.vo.ResponseCode;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 无论是熔断异常还是限流异常都会经过handle来处理
*
* @author 先生
* @date 2021/01/23
*/
@Component
public class UrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
String msg = null;
// 如果e是限流异常 就会为true
if(e instanceof FlowException){// 限流异常处理
msg = "目前访问量太高,请稍后再试";
}else if(e instanceof DegradeException){// 熔断异常处理
msg = "目前该接口已被熔断,请稍后再试";
}else if(e instanceof AuthorityException){// 授权规则异常
msg = "授权不通过";
}
// 设置响应状态码
httpServletResponse.setStatus(500);
// 设置响应格式
httpServletResponse.setCharacterEncoding("UTF-8");
// 设置浏览器接受格式
httpServletResponse.setContentType("application/json;charset=utf-8");
// 0bjectMapper 是内置Jackson的序列化工具类,这用于将对象转为JSON字符串
ObjectMapper objectMapper = new ObjectMapper();
// 属性为null时不进行序列化输出
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// getWriter 是通过什么方式进行输出
objectMapper.writeValue(httpServletResponse.getWriter(),new ResponseCode("500",msg));
}
}
开启基于openfeign的限流熔断
feign:
cilent:
config:
default:
loggerLevel: HEADERS # 将每一次openFeign请求的头信息在日志中输出
sentinel: # 开启基于openfeign的限流熔断
enabled: true
开启了基于openfeign的限流熔断后可以对我们的接口调用其他服务的接口进行限流和熔断
spring:
jackson:
default-property-inclusion: non_null # 全局jackson不对null做序列化输出
当我们请求的feign被限流后不会返回数据,而是报错,这种方式不友好。我们可以自己定义在请求的feign接口被限流后仍然可以返回我们定义的数据。
首先在feign类中定义限流后调用的类
@FeignClient(name = "manage-nacos",fallbackFactory = TestFeignFlowFactory.class)
public interface TestFeign {
@PostMapping("/get/name")
public String getName(@RequestParam("name") String name);
}
在TestFeignFlowFactory 类中需要实现FallbackFactory,里面的参数是我们的feign请求类,然后会实现我们在feign类中的方法,在这个方法中可以返回我们定义的数据
@Component
public class TestFeignFlowFactory implements FallbackFactory<TestFeign> {
@Override
public TestFeign create(Throwable throwable) {
return new TestFeign() {
@Override
public String getName(String name) {
return "已被限流";
}
};
}
}