【深入理解SpringCloud微服务】Sentinel实战与原理剖析
【深入理解SpringCloud微服务】Sentinel实战与原理剖析
- Sentinel功能
- Sentinel三种用法
- 硬编码
- 注解
- 拦截器
- 什么时候使用注解方式,什么时候使用拦截器方式?
- Sentinel原理
Sentinel功能
Sentinel和Hystrix一样,也是一个微服务容错保护框架,想要知道Sentinel拥有什么功能,看一下它的控制台就知道了。
除了流控和熔断以外,比起Hystrix还增加了热点规则、系统规则、授权规则、集群流控等功能,所有Sentinel对比Hystrix是功能更丰富的。
Sentinel三种用法
硬编码
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.4</version>
</dependency>
示例代码:
/**
* @author huangjunyi
* @date 2024/4/9 18:30
* @desc
*/
public class SentinelDemo {
// 注册流控规则
static {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
// 定义资源名称
rule.setResource("hello");
// QPS模式限流,每秒2个
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
private static void hello() {
Entry entry = null;
try {
// 通过SphU.entry(资源名)记录统计数据并执行给定资源的规则检查
entry = SphU.entry("hello");
System.out.println("hello");
} catch (BlockException e) {
System.out.println("限流了");
} finally {
if (entry != null) {
// 最后必须调用Entry#exit()才算完成
// Entry#exit()会进行一些数据的统计以及决定断路器是否打开
entry.exit();
}
}
}
public static void main(String[] args) {
hello();
hello();
// 第三次就限流了
hello();
}
}
执行代码后,可以看到第三次输出就流控了。
Sentinel的整体思路就是两步:
- 服务启动的时候往各种Mananger注册规则,比如以流控为例,就是往FlowRuleManager当中注册流控规则FlowRule,并且每个规则都有一个资源名称与其对应。
- 然后执行目标业务逻辑前先执行SphU.entry(资源名),根据资源名称从FlowRuleManager(还是以流控为例)寻找对应的FlowRule进行校验,校验通过则执行业务逻辑,校验失败则抛异常。最后还要调用entry.exit()进行收尾工作。
Sentinel硬编码的使用方式是最原始的使用方式,后面两种方式都是基于硬编码方式做的扩展。想要理解Sentinel的原理,首先要了解硬编码方式的实现原理。
注解
引入依赖:除了sentinel-core以外,还要引入sentinel-annotation-aspectj
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.4</version>
</dependency>
配置类:
/**
* @author huangjunyi
* @date 2024/4/10 19:45
* @desc
*/
@Configuration
public class AspectConfig {
// 配置Sentinel的切面类,才能使@SentinelResource注解生效
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
初始化流控规则:
/**
* @author huangjunyi
* @date 2024/4/8 19:06
* @desc
*/
@Component
public class FlowRuleInit {
@PostConstruct
public void init() {
initFlowRules();
}
// 初始化流控规则,也可以在控制台中配置
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("hello");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
Controller:
/**
* @author huangjunyi
* @date 2024/4/8 19:00
* @desc
*/
@RestController
public class HelloController {
// @SentinelResource主键指定资源名,以及降级回调方法
@SentinelResource(value = "hello", blockHandler = "flowBlockHandler")
@GetMapping("/hello")
public String hello() {
System.out.println("hello");
return "hello";
}
// 被流控时的降级回调方法
public String flowBlockHandler(BlockException blockException) {
return "被流控了!";
}
}
注解方式就是在目标方法上添加@SentinelResource注解,注解指定资源名称与配置的流控规则一致,流控效果就会生效。
如果是引入的spring-cloud-starter-alibaba-sentinel依赖,那就不需要配置Sentinel切面类了,spring-cloud-starter-alibaba-sentinel已经通过自动装配的方式帮我们配置好了。
拦截器
最后是拦截器的方式,这种方式是最方便的,不需要添加注解,默认是请求路径作为资源名称。
引入依赖:除了sentinel-core以外,还要引入sentinel-spring-webmvc-adapter
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.8.4</version>
</dependency>
配置拦截器
/**
* @author huangjunyi
* @date 2024/4/8 19:04
* @desc
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
config.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
// 设置为true,资源名称前面要带方法名,设置为false则不需要
config.setHttpMethodSpecify(true);
config.setWebContextUnify(true);
config.setOriginParser(request -> request.getHeader("S-user"));
// 添加Sentinel的拦截器
registry.addInterceptor(new SentinelWebInterceptor(config))
.order(Integer.MIN_VALUE)
.addPathPatterns("/**");
}
}
配置流控规则,或者直接在控制台配置:
/**
* @author huangjunyi
* @date 2024/4/8 19:06
* @desc
*/
@Component
public class FlowRuleInit {
@PostConstruct
public void init() {
initFlowRules();
}
// 初始化流控规则,也可以在控制台中配置
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
// 由于拦截器的httpMethodSpecify属性配置为true,
// 资源名称前面要带上方法名
rule.setResource("GET:/hello");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
Controller:
/**
* @author huangjunyi
* @date 2024/4/8 19:00
* @desc
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
System.out.println("hello");
return "hello";
}
}
使用拦截器方式,就不需要声明@SentinelResource注解了,如果通过控制台配置规则的话,甚至连上面FlowRuleInit的流控规则配置都可以不需要,只需要引入依赖jar包,然后配置一个拦截器即可。
Sentinel拦截器方式其实就是在拦截器SentinelWebInterceptor中执行硬编码时的模板代码。
在SentinelWebInterceptor的preHandle方法中执行SphU.entry(resourceName)方法,如果检查通过,则执行业务逻辑;检查不通过则抛出异常BlockException,SentinelWebInterceptor也会catch住异常并做相应处理。
最后SentinelWebInterceptor的afterCompletion方法会调用entry.exit()进行收尾。
什么时候使用注解方式,什么时候使用拦截器方式?
注解方式是网上示例最多的,但是如果我们有一百个一千个接口,难不成每个接口声明一个注解?也不是不可以,但何必这么麻烦呢?此时通过拦截器方式,默认以接口路径作为资源名称,不是更方便吗?剩下的就是在控制台配置规则。
但是拦截器方式也有注解方式做不到的地方,比如配置降级处理逻辑,以及热点参数限流。
这样,什么时候使用注解方式,什么时候使用拦截器方式,就很清楚了。
Sentinel原理
Sentinel底层利用了责任链模式,去处理每个不同的规则。
当第一次调用SphU.entry()方法时,会通过SPI机制加载指定的所有ProcessorSlot实现类,组装成一个ProcessorSlotChain,ProcessorSlotChain中每个Slot通过next指针链接形成一个单选链表。
SphU.entry()方法每次都会以责任链的方式沿着ProcessorSlotChain里的每个Slot往下执行,一旦某个Slot校验不通过,则终止不再往下,chain中的所有slot都校验通过,则返回Entry对象。
ProcessorSlotChain中有许多类型的Slot,我们不需要全部了解,但是有两个特别重要的我们必须要知道,一个是FlowSlot,另一个是DegradeSlot。FlowSlot对应流控规则的处理,DegradeSlot对应熔断规则的处理。它们俩一般放在chain的最末尾。