目录
1 .Sentinel 是什么
2.Sentinel快速开始
3.启动 Sentinel 控制台
4. Spring Cloud Alibaba整合Sentine
5.Sentinel控制台规则配置详解
6.Sentinel规则持久化
7.基于Nacos配置中心控制台实现推送
1.1Sentinel和Hystrix对比
https://github.com/alibaba/Sentinel/wiki/Sentinel%E4%B8%8EHystrix%E7%9A%84%E5%AF%B9%E6%AF%94
com.alibaba.csp
sentinel-core
1.8.0
package com.tuling.sentinelnew.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.tuling.sentinelnew.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
// 进行sentinel流控
@RequestMapping(value = "/hello")
public String hello() {
Entry entry = null;
try {
// 1.sentinel针对资源进行限制的
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("=====" + str + "=====");
return str;
} catch (BlockException e1) {
// 资源访问阻止,被限流或被降级
//进行相应的处理操作
log.info("block!");
return "被流控了!";
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* 定义规则
*
* spring 的初始化方法
*/
@PostConstruct
private static void initFlowRules() {
// 流控规则
List rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 为哪个资源进行流控
rule.setResource(RESOURCE_NAME);
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
// Set limit QPS to 20.
rule.setCount(1);
rules.add(rule);
// 通过@SentinelResource来定义资源并配置降级和流控的处理方法
FlowRule rule2 = new FlowRule();
//设置受保护的资源
rule2.setResource(USER_RESOURCE_NAME);
// 设置流控规则 QPS
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
// Set limit QPS to 20.
rule2.setCount(1);
rules.add(rule2);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
@PostConstruct // 初始化
public void initDegradeRule() {
/*降级规则 异常*/
List degradeRules = new ArrayList<>();
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource(DEGRADE_RESOURCE_NAME);
// 设置规则侧率: 异常数
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 触发熔断异常数 : 2
degradeRule.setCount(2);
// 触发熔断最小请求数:2
degradeRule.setMinRequestAmount(2);
// 统计时长: 单位:ms 1分钟
degradeRule.setStatIntervalMs(60 * 1000); // 时间太短不好测
// 一分钟内: 执行了2次 出现了2次异常 就会触发熔断
// 熔断持续时长 : 单位 秒
// 一旦触发了熔断, 再次请求对应的接口就会直接调用 降级方法。
// 10秒过了后——半开状态: 恢复接口请求调用, 如果第一次请求就异常, 再次熔断,不会根据设置的条件进行判定
degradeRule.setTimeWindow(10);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
/*
慢调用比率--DEGRADE_GRADE_RT
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
degradeRule.setCount(100);
degradeRule.setTimeWindow(10);
//请求总数小于minRequestAmount时不做熔断处理
degradeRule.setMinRequestAmount(2);
// 在这个时间段内2次请求
degradeRule.setStatIntervalMs(60*1000*60); // 时间太短不好测
// 慢请求率:慢请求数/总请求数> SlowRatioThreshold ,
// 这里要设置小于1 因为慢请求数/总请求数 永远不会大于1
degradeRule.setSlowRatioThreshold(0.9);*/
}
/**
* @param id
* @return
* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
* 怎么使用: 1.添加依赖sentinel-annotation-aspectj
* 2.配置bean——SentinelResourceAspect
* value 定义资源
* blockHandler 设置 流控降级后的处理方法(默认该方法必须声明在同一个类)
* 如果不想在同一个类中 blockHandlerClass 但是方法必须是static
* fallback 当接口出现了异常,就可以交给fallback指定的方法进行处理
* 如果不想在同一个类中 fallbackClass 但是方法必须是static
*
* blockHandler 如果和fallback同时指定了,则blockHandler优先级更高
* exceptionsToIgnore 排除哪些异常不处理
*/
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME, fallback = "fallbackHandleForGetUser",
/*exceptionsToIgnore = {ArithmeticException.class},*/
/*blockHandlerClass = User.class,*/ blockHandler = "blockHandlerForGetUser")
public User getUser(String id) {
int a = 1 / 0;
return new User("xushu");
}
public User fallbackHandleForGetUser(String id, Throwable e) {
e.printStackTrace();
return new User("异常处理");
}
/**
* 注意:
* 1. 一定要public
* 2. 返回值一定要和源方法保证一致, 包含源方法的参数。
* 3. 可以在参数最后添加BlockException 可以区分是什么规则的处理方法
*
* @param id
* @param ex
* @return
*/
public User blockHandlerForGetUser(String id, BlockException ex) {
ex.printStackTrace();
return new User("流控!!");
}
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME, entryType = EntryType.IN,
blockHandler = "blockHandlerForFb")
public User degrade(String id) throws InterruptedException {
// 异常数\比例
throw new RuntimeException("异常");
/* 慢调用比例
TimeUnit.SECONDS.sleep(1);
return new User("正常");*/
}
public User blockHandlerForFb(String id, BlockException ex) {
return new User("熔断降级");
}
}
StartApplication.java
package com.tuling.sentinelnew;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class,args);
}
/**
* @description: 注解支持的配置Bean
* @method: sentinelResourceAspect
* @author: wang fei
* @date: 2023/1/22 16:46:26
* @param: []
* @return: org.springframework.beans.factory.annotation.Value
**/
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
测试结果:
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
package com.wang.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @BelongsProject: SpringCloudAlibabaLearn
* @BelongsPackage: com.wang.controllter
* @Author: wang fei
* @CreateTime: 2023-01-16 16:48
* @Description: TODO
* @Version: 1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/pay")
@SentinelResource(value = "pay",blockHandler = "flowBlockHandler")
public String pay(){
return "success";
}
@GetMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "success";
}
//自定义流控处理返回方法
public String flowBlockHandler(BlockException e){
return "流控成功 ,阻塞服务";
}
@GetMapping("/add")
public String add(){
return "下单成功";
}
@GetMapping("/get")
public String get(){
return "查询成功";
}
/**
* 热点规则,必须使用@SentinelResource
* @param id
* @return
* @throws InterruptedException
*/
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById",blockHandler = "HotBlockHandler")
public String getById(@PathVariable("id") Integer id) throws InterruptedException {
System.out.println("正常访问");
return "正常访问";
}
public String HotBlockHandler(@PathVariable("id") Integer id,BlockException e) throws InterruptedException {
return "热点异常处理";
}
}
package com.wang.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.wang.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 飞
*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
// getRule() 资源 规则的详细信息
log.info("BlockExceptionHandler BlockException================"+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100,"接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101,"服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(102,"热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103,"触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104,"授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
Result.java
package com.wang.domain;
/**
* @author 飞
*/
public class Result {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static Result error(Integer code, String msg){
return new Result(code,msg);
}
}
server:
port: 8070
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
web-context-unify: false # 默认将调用链路收敛, 导致链路流控效果无效
datasource:
flow-rule:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
username: nacos
password: nacos
dataId: order-sentinel-flow-rule
rule-type: flow
java ‐jar sentinel‐dashboard‐1.8.0.jar
java ‐Dserver.port=8858 ‐Dsentinel.dashboard.auth.username=xushu Dsentinel.dashboard.auth.password=123456 ‐jar D:\s
erver\sentinel‐dashboard‐1.8.0.jar
pause
Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;
2.2步骤
参考文档: https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
package com.wang.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.wang.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 飞
*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
// getRule() 资源 规则的详细信息
log.info("BlockExceptionHandler BlockException================"+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100,"接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101,"服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(102,"热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103,"触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104,"授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
编辑流控规则
jmeter测试 :
查看实时监控,可以看到通过QPS存在缓慢增加的过程
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下 来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的 请求。
查看实时监控,可以看到断路器熔断效果
org.springframework.cloud
spring-cloud-starter-openfeign
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
feign:
sentinel:
# openfeign整合sentinel
enabled: true
package com.wang.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value="stock-nacos",path = "/stock",fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
@RequestMapping("/reduct")
public String reduct2();
}
package com.wang.feign;
import org.springframework.stereotype.Component;
@Component
public class StockFeignServiceFallback implements StockFeignService {
@Override
public String reduct2() {
return "降级啦!!!";
}
}
测试结果
配置授权规则
package com.wang.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class MyRequestOriginParser implements RequestOriginParser {
/**
* 通过request获取来源标识,交给授权规则进行匹配
* @param request
* @return
*/
@Override
public String parseOrigin(HttpServletRequest request) {
// 标识字段名称可以自定义
String origin = request.getParameter("serviceName");
// if (StringUtil.isBlank(origin)){
// throw new IllegalArgumentException("serviceName参数未指定");
// }
return origin;
}
}
云上版本 AHAS Sentinel 提供开箱即用的全自动托管集群流控能力,无需手动指定/分配 token server 以及管理连接状 态,同时支持分钟小时级别流控、大流量低延时场景流控场景,同时支持 Istio/Envoy 场景的 Mesh 流控能力。
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.csp
sentinel-datasource-nacos
[
{
"resource": "/order/pay",
"controlBehavior": 0,
"count": 3.0,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
server:
port: 8070
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
web-context-unify: false # 默认将调用链路收敛, 导致链路流控效果无效
datasource:
flow-rule:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
username: nacos
password: nacos
dataId: order-sentinel-flow-rule
rule-type: flow
package com.wang.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @BelongsProject: SpringCloudAlibabaLearn
* @BelongsPackage: com.wang.controllter
* @Author: wang fei
* @CreateTime: 2023-01-16 16:48
* @Description: TODO
* @Version: 1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/pay")
// @SentinelResource(value = "pay",blockHandler = "flowBlockHandler")
public String pay(){
return "success";
}
@GetMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "success";
}
//自定义流控处理返回方法
public String flowBlockHandler(BlockException e){
return "流控成功 ,阻塞服务";
}
@GetMapping("/add")
public String add(){
return "下单成功";
}
@GetMapping("/get")
public String get(){
return "查询成功";
}
/**
* 热点规则,必须使用@SentinelResource
* @param id
* @return
* @throws InterruptedException
*/
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById",blockHandler = "HotBlockHandler")
public String getById(@PathVariable("id") Integer id) throws InterruptedException {
System.out.println("正常访问");
return "正常访问";
}
public String HotBlockHandler(@PathVariable("id") Integer id,BlockException e) throws InterruptedException {
return "热点异常处理";
}
}
登录sentinel