随着分布式系统变得越来越流行,服务之间的可靠性变得比以往任何时候都更加重要。Sentinel是强大的流控制组件,以“流”为切入点,涵盖多个领域,包括流控制,并发限制,电路中断和自适应系统保护,以确保微服务的可靠性。 一句话讲就是Spring Cloud Alibaba用来替换之前的Hystrix的技术。
用来做系统流量控制、熔断降级、系统的负载保护等。
1.下载地址 2.启动
java -jar sentinel-dashboard-1.7.2.jar3.访问Dashboard,用户名密码都是sentinel
1.新建一个springboot项目 2.引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- sentinel-datasource-nacos 后续持久化用 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>3.启动类添加注解@EnableDiscoveryClient
@SpringBootApplication @EnableDiscoveryClient public class AlibabaSentinelServiceApplication { public static void main(String[] args) { SpringApplication.run(AlibabaSentinelServiceApplication.class, args); } }4.application.properties配置
server.port=8401 spring.application.name=cloudalibaba-sentinel-service spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.transport.port=8719 spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848 spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name} spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP spring.cloud.sentinel.datasource.dsl.nacos.data-type=json spring.cloud.sentinel.datasource.dsl.nacos.rule-type.=flow management.endpoints.web.exposure.include=*5.提供测试接口
@RestController public class FlowLimitController { private static Logger log = LoggerFactory.getLogger(FlowLimitController.class); @GetMapping("/testA") public String testA(){ // try { // TimeUnit.MILLISECONDS.sleep(800); // } catch (InterruptedException e) { // e.printStackTrace(); // } return "testA-----"; } @GetMapping("/testB") public String testB(){ log.info(Thread.currentThread().getName() + "...testB "); return "testB -----"; } }6.启动sentinel和nacos 不懂nacos可以参考
配置如下: 当你连续点击看到如下限流提示(1s请求数量超过配置阈值)
配置如下: 为了方便测试让每个被调用的方法睡眠一会
@GetMapping("/testA") public String testA(){ //测试线程阈值 try { TimeUnit.MILLISECONDS.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } return "testA-----"; }当你连续点击看到如下限流提示(1s请求数量超过配置阈值)
配置如下 用postman模拟与/testA关联的/testB到达阈值 postman启动之后调用/testA可以看到该接口被限流。
配置如下(访问流量忽然增大时,从阈值/冷加载因子 开始经过预热时长达到每秒可访问阈值,即从流量增大时刚开始可以支持每秒2次访问,经过3s可以支持每秒6次访问接口),默认冷加载因子(coldFactor)为3 可以点击访问/testA接口测试,开始慢慢慢点击没问题,然后加快速度会出现限流,过一会之后,限流就没有了。
配置如下 为了方便查看调用,接口中打印线程名称
@GetMapping("/testB") public String testB(){ log.info(Thread.currentThread().getName() + "...testB "); return "testB -----"; }重启项目调用,可以在控制台中看到调用记录
官方文档
RT: 当资源的平均响应时间超过阈值(这里阈值即自己配置的毫秒值)且在时间窗口内通过的请求>= 5,两个条件同时满足触发熔断降级窗口期后关闭断路器。RT最大4900(更大通过 -Dcsp.sentinel.statistic.max.rt=xxx 来配置) 异常比例: QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级。异常数: 异常数(分钟统计)超过阈值,触发降级;时间窗口结束后,关闭降级Sentienl熔断降级会在调用调用链路中某个资源出现不稳定时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。 当资源降级后,在接下来的降级时间窗口之内,对该资源的调用自动熔断(默认行为是抛出DegradeException)
1.添加测试接口
@GetMapping("/testD") public String testD(){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("testD 测试RT"); return "testD -----"; }为了让平均响应时间增大,在代码中加入sleep休眠。 2.利用JMater进行压力测试 1s 10个线程访问/testD接口 3.配置如下 4.启动JMeter,自己调用/testD,可以看到该接口已被熔断降级。 熔断分析:1.平均响应时间(1000ms)超出阈值(200ms) 2.在时间窗口内通过请求10*5=50>=5。满足两个条件,所以发生熔断降级
资源每秒请求量>=5&&每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口之内,对这个方法的调用都会自动的返回(服务熔断)。异常比率阈值范围[0.0,1.0],代表0%-100%。
1.提供测试异常比例接口
@GetMapping("/testException") public String testException(){ log.info("testException 异常比例"); int age = 10 /0 ; return "testException -----"; }2.利用JMater进行压力测试,除接口外其他配置同上面测试RT 3.sentinel配置 4.重启项目并启动JMeter进行测试,调用/testException可以服务熔断提示信息
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若时间窗口小于60s,则结束熔断状态后可能再进入熔断状态。
1.提供测试接口
@GetMapping("/testExceptionCount") public String testExceptionCount(){ log.info("testExceptionCount 异常数"); int age = 10 /0 ; return "testExceptionCount -----"; }2.sentinel配置 3.重启项目进行测试 前面5次访问都报错,后面访问进入熔断后降级。 熔断后降级如下图
1.提供接口进行测试
@GetMapping("/testHotKey") @SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey") public String testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2){ return "testHotKey -----"; } public String dealTestHotKey(String p1, String p2, BlockException blockException){ return "dealTestHotKey---------"; }2.sentinel配置 上面的配置第一个参数p1,当QPS超过1秒1次点击后马上被限流。 3.重启项目测试
1.sentinel配置如下 以上配置含义,当第一个参数p1的值为5时接口/testHotKey的流量阈值为200
注意:
@SentinelResource处理的是控制台配置的违规情况,有blockHandler方法配置的兜底处理。 但是@SentinelResource不管代码中出现的运行时期异常(RuntimeException)
1.提供接口
/** * (违反sentinel配置)手动配置兜底处理blockHandler * @return */ @GetMapping(value = "/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException") public CommonResult byResource(){ return new CommonResult(200, "按资源名称限流测试OK"); } public CommonResult handleException(BlockException blockException){ return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服务不可用" ); } /** * 默认处理 * @return */ @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl(){ return new CommonResult(200, "by url限流测试OK"); }2.sentinel配置 资源名称: url:
3.重新启动测试 访问byResource当每秒访问超过1次时,使用自定义的兜底处理。 访问/rateLimit/byUrl当每秒访问超过1次时,使用默认处理。
以上配置存在问题:
1.系统默认的,没有体现我们自己的业务需求2.依照现有条件,我们自定义的处理方法和业务代码耦合在一块,不直观3.每个业务方法都有一个兜底的,代码膨胀加剧4.全局同一处理方法没有体现1.提供接口
public class CustomerBlockHandler { public static CommonResult handlerException(BlockException exception) { return new CommonResult(444, "客户自定义,global handlerException---1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(444, "客户自定义,global handlerException---2"); } } @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler(){ return new CommonResult(200, "客户自定义 限流测试OK"); }2.sentinel中配置 3.重启测试
1.创建三个springboot项目,分别为alibaba-consumer2、alibaba-provider3、alibaba-provider4(alibaba-provider3/alibaba-provider4一样) 由于东西过多这里不罗列,详细参考https://github.com/xiaoxiaoshou/springclouddemo
主要示例代码:
@RestController @Slf4j @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) public class CircleBreakerController { private static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback") //没有配置 // @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常 // @SentinelResource(value = "fallback",blockHandler = "blockHandler") // 配置了blockHandler,只负责sentinel控制台配置违规 @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler") // 配置了blockHandler和fallback // @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) // 忽略运行时IllegalArgumentException异常不进行自定义处理 public CommonResult<Payment> fallback(@PathVariable("id") Long id){ CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class); if(id == 4){ throw new IllegalArgumentException("IllegalArgumentException,非法参数异常"); }else if(commonResult.getData() == null){ throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常"); } return commonResult; } // 本例是fallback public CommonResult handlerFallback(Long id, Throwable e){ Payment payment = new Payment(id, null); return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment); } public CommonResult blockHandler(Long id, BlockException exception){ Payment payment = new Payment(id, null); return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment); } } fallback对应方法(handlerFallback方法)处理代码运行时期异常blockHandler对应方法(blockHandler)处理Sentinel中违规exceptionsToIgnore忽略运行时期某个异常不进行自定义处理配置Sentinel(对资源fallback阈值为1)并启动项目测试,可以看到既可以处理调用过程中对应Sentinel违规也可以处理运行时期异常。
1.主要配置
#激活sentinel对feign的支持 feign: sentinel: enabled: true2.绑定对应服务
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping("/paymentSQL/{id}") CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); } @Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(444, "fallback"); } }3.提供访问接口
@Resource private PaymentService paymentService; @GetMapping("/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ return paymentService.paymentSQL(id); }4.重启项目即可冲过consumer接口调用第三方接口
各熔断器的比较:
当你每次重启对应服务,你会发现在Sentinel中配置对应的规则就没有了,在生产环境中我们需要配置规则的持久化(持久化工具都可以,官方推荐Nacos)。
1.引入依赖
<!-- sentinel-datasource-nacos 后续持久化用 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>2.配置文件配置
spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848 spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name} spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP spring.cloud.sentinel.datasource.dsl.nacos.data-type=json spring.cloud.sentinel.datasource.dsl.nacos.rule-type=flow3.在nacos中配置
[ { "resource":"/rateLimit/byUrl", "limitApp":"default", "grade":1, "count":1, "strategy":0, "controlBehavior":0, "clusterMode":false } ]配置项含义:
resource:资源名称limitApp:来源应用grade:阈值类型,0表示线程数,1表示QPScount:单机阈值strategy:流控模式,0表示直接,1表示级联,2表示链路controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等候clusterMode:是否集群