赞
踩
Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。官方网站:
官网https://sentinelguard.io/zh-cn/
从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性
Sentinel 的使用可以分为两个部分:
核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
mkdir -p /docker/alibaba/sentinel/{config,data,logs}
拷贝jar包进sentinel目录下
Dockerfile文件
- FROM openjdk:8-jre
- MAINTAINER yh
- COPY ./sentinel-dashboard-1.8.5.jar /app.jar
- EXPOSE 8718
- ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose.yml
- version: '3.9'
- services:
- sentinel:
- build:
- context: ./
- dockerfile: ./Dockerfile
- image: sentinel
- container_name: sentinel
- ports:
- - "8718:8718"
- environment:
- JVM_OPTS: -server -Xmx512M -Xms512M -XX:MaxMetaspaceSize=256M -XX:CompressedClassSpaceSize=50M -XX:ReservedCodeCacheSize=240M -XX:MaxDirectMemorySize=400M
- logging:
- driver: "json-file"
- options:
- max-size: "10m"
- max-file: "1"
- volumes:
- - "/docker/alibaba/sentinel/logs:/root/logs"
- - "/docker/alibaba/sentinel/logs:/app-logs"
- command: [
- "--server.port=8718",
- "--logging.file.path=/app-logs"
- ]
- restart: always
- network_mode: "host"
启动
docker-compose up -d
防火墙开放8718端口
- firewall-cmd --permanent --add-port=8718/tcp
-
-
- # 防火墙重载
-
- firewall-cmd --reload
访问验证
登录账号密码均为sentinel
我们在cart-service
模块中整合sentinel,连接sentinel-dashboard
控制台,步骤如下: 1)引入sentinel依赖
- <!--sentinel-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
- </dependency>
2)配置控制台
修改application.yaml文件,添加下面内容:
- spring:
- cloud:
- sentinel:
- transport:
- dashboard: localhost:8090
3)访问cart-service
的任意端点
重启cart-service
,然后访问查询购物车接口,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard
控制台。并展示出统计信息:
点击簇点链路菜单,会看到下面的页面:
所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel
监控的资源。默认情况下,Sentinel
会监控SpringMVC
的每一个Endpoint
(接口)。
因此,我们看到/carts
这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。
不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts
路径:
默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的。
所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径
作为簇点资源名:
首先,在cart-service
的application.yml
中添加下面的配置:
- spring:
- cloud:
- sentinel:
- transport:
- dashboard: localhost:8090
- http-method-specify: true # 开启请求方式前缀
然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。参数见最下方:
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
默认的流控模式,当接口达到限流条件时,直接开启限流功能。
当关联的资源达到阈值时,就限流自己
当与A关联的资源B达到阀值后,就限流A自己
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名,B惹事,A挂了
来自不同链路的请求对同一个目标访问时,实施针对性的不同限流措施,
比如C请求来访问就限流,D请求来访问就是OK
修改yml
- server:
- port: 8401
-
- spring:
- application:
- name: cloudalibaba-sentinel-service #8401微服务提供者后续将会被纳入阿里巴巴sentinel监管
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848 #Nacos服务注册中心地址
- sentinel:
- transport:
- dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
- port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
- web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路
- @Service
- public class FlowLimitService
- {
- @SentinelResource(value = "common")
- public void common()
- {
- System.out.println("------FlowLimitService come in");
- }
- }
@SentinelResource
@SentinelResource
是阿里巴巴开源的分布式系统的流量防卫兵Sentinel中的一个注解,用于标识资源,以便进行流量控制、熔断降级等操作。在Spring Cloud Alibaba Sentinel中,可以通过在方法上添加@SentinelResource
注解来定义资源
- package com.yanyu.cloud.controller;
-
- import com.yanyu.cloud.service.FlowLimitService;
- import jakarta.annotation.Resource;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- public class FlowLimitController
- {
-
- @GetMapping("/testA")
- public String testA()
- {
- return "------testA";
- }
-
- @GetMapping("/testB")
- public String testB()
- {
- return "------testB";
- }
- /**流控-链路演示demo
- * C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管
- */
- @Resource
- private FlowLimitService flowLimitService;
-
- @GetMapping("/testC")
- public String testC()
- {
- flowLimitService.common();
- return "------testC";
- }
- @GetMapping("/testD")
- public String testD()
- {
- flowLimitService.common();
- return "------testD";
- }
- }
说明:C和D两个请求都访问flowLimitService.common()方法,对C限流,对D不管
快速失败(默认的流控处理)
公式:阈值除以冷却因子coldFactor(默认值为3),经过预热时长后才会达到阈值
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
|
案例,单机阈值为10,预热时长设置5秒。
系统初始化的阈值为10 / 3 约等于3,即单机阈值刚开始为3(我们人工设定单机阈值是10,sentinel计算后QPS判定为3开始);
然后过了5秒后阀值才慢慢升高恢复到设置的单机阈值10,也就是说5秒钟内QPS为3,过了保护期5秒后QPS为10
|
应用
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值
接下来,点击查询商品的FeignClient对应的簇点资源后面的流控按钮:
在弹出的表单中填写下面内容:
注意,这里勾选的是并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝。
Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的工作状态切换有一个状态机来控制:
状态机包括三个状态:
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
请求成功:则切换到closed状态
请求失败:则切换到open状态
我们可以在控制台通过点击簇点后的熔断
按钮来配置熔断策略:
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,
让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。
2.最大RT:即最大的响应时间,指系统对请求作出响应的业务处理时间。
3.慢调用:处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用。
4.慢调用比例:在所以调用中,慢调用占有实际的比例=慢调用次数➗总调用次数
5.比例阈值:自己设定的 , 比例阈值=慢调用次数➗调用次数
6.统计时长:时间的判断依据
7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发
不配置Sentinel,对于int age=10/0,调一次错一次报错error,页面报【Whitelabel Error Page】或全局异常
配置Sentinel,对于int age=10/0,如符合如下异常比例启动熔断,页面报【Blocked by Sentinel (flow limiting)】
SentinelResource,是一个流量防卫防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。
通过访问的rest地址来限流,会返回Sentinell自带默认的限流处理信息
测试类
- @RestController
- @Slf4j
- public class RateLimitController
- {
- @GetMapping("/rateLimit/byUrl")
- public String byUrl()
- {
- return "按rest地址限流测试OK";
- }
- }
- @GetMapping("/rateLimit/byResource")
- @SentinelResource(value = "byResourceSentinelResource",blockHandler = "handleException")
- public String byResource()
- {
- return "按资源名称SentinelResource限流测试OK";
- }
- public String handleException(BlockException exception)
- {
- return "服务不可用@SentinelResource启动"+"\t"+"o(╥﹏╥)o";
- }
- @GetMapping("/rateLimit/doAction/{p1}")
- @SentinelResource(value = "doActionSentinelResource",
- blockHandler = "doActionBlockHandler", fallback = "doActionFallback")
- public String doAction(@PathVariable("p1") Integer p1) {
- if (p1 == 0){
- throw new RuntimeException("p1等于零直接异常");
- }
- return "doAction";
- }
-
- public String doActionBlockHandler(@PathVariable("p1") Integer p1,BlockException e){
- log.error("sentinel配置自定义限流了:{}", e);
- return "sentinel配置自定义限流了";
- }
-
- public String doActionFallback(@PathVariable("p1") Integer p1,Throwable e){
- log.error("程序逻辑异常了:{}", e);
- return "程序逻辑异常了"+"\t"+e.getMessage();
- }
- blockHandler,主要针对sentinel配置后出现的违规情况处理
- fallback,程序异常了JVM抛出的异常服务降级
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
测试
- @GetMapping("/testHotKey")
- @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
- public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
-
- @RequestParam(value = "p2",required = false) String p2){
- return "------testHotKey";
- }
- public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
- {
- return "-----dealHandler_testHotKey";
- }
-
热点参数的注意点,参数必须是基本类型或者String
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。白放行、黑禁止
- @RestController
- @Slf4j
- public class EmpowerController //Empower授权规则,用来处理请求的来源
- {
- @GetMapping(value = "/empower")
- public String requestSentinel4(){
- log.info("测试Sentinel授权规则empower");
- return "Sentinel授权规则";
- }
- }
- @Component
- public class MyRequestOriginParser implements RequestOriginParser
- {
- @Override
- public String parseOrigin(HttpServletRequest httpServletRequest) {
- return httpServletRequest.getParameter("serverName");
- }
- }
一但。我们重启微服务应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台
的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel_上的流控规则持续有效
改pom
- <!--SpringCloud ailibaba sentinel-datasource-nacos -->
- <dependency>
- <groupId>com.alibaba.csp</groupId>
- <artifactId>sentinel-datasource-nacos</artifactId>
- </dependency>
改yml
- server:
- port: 8401
-
- spring:
- application:
- name: cloudalibaba-sentinel-service
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848 #Nacos服务注册中心地址
- sentinel:
- transport:
- dashboard: localhost:8090 #配置Sentinel dashboard控制台服务地址
- # dashboard: 114.116.205.180:8718
- port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
- web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路
- datasource:
- ds1:
- nacos:
- server-addr: localhost:8848
- dataId: ${spring.application.name}
- groupId: DEFAULT_GROUP
- data-type: json
- rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType
问题背景
cloudalibaba-consumer-nacos-order83 通过OpenFeign调用 cloudalibaba-provider-payment9001
1 83 通过OpenFeign调用 9001微服务,正常访问OK
2 83 通过OpenFeign调用 9001微服务,异常访问error
访问者要有fallback服务降级的情况,不要持续访问9001加大微服务负担,但是通过feign接口调用的又方法各自不同,
如果每个不同方法都加一个fallback配对方法,会导致代码膨胀不好管理,工程埋雷....../(ㄒoㄒ)/~~
3 public @interface FeignClient
通过fallback属性进行统一配置,feign接口里面定义的全部方法都走统一的服务降级,一个搞定即可。
4 9001微服务自身还带着sentinel内部配置的流控规则,如果满足也会被触发,也即本例有2个Case
4.1 OpenFeign接口的统一fallback服务降级处理
4.2 Sentinel访问触发了自定义的限流配置,在注解@SentinelResource里面配置的blockHandler方法。
修改服务提供方cloudalibaba-provider-payment9001
pom添加
- <!--openfeign-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <!--alibaba-sentinel-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
- </dependency>
改yml
- server:
- port: 9001
-
- spring:
- application:
- name: nacos-payment-provider
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848 #配置Nacos地址
- sentinel:
- transport:
- dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
- port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
-
修改控制类
- package com.yanyu.cloud.controller;
-
- import cn.hutool.core.util.IdUtil;
- import com.alibaba.csp.sentinel.annotation.SentinelResource;
- import com.alibaba.csp.sentinel.slots.block.BlockException;
- import com.yanyu.cloud.entities.PayDTO;
- import com.yanyu.cloud.vo.Code;
- import com.yanyu.cloud.vo.ResultData;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.math.BigDecimal;
-
- @RestController
- public class PayAlibabaController
- {
- @Value("${server.port}")
- private String serverPort;
-
- @GetMapping(value = "/pay/nacos/{id}")
- public String getPayInfo(@PathVariable("id") Integer id)
- {
- return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
- }
- @GetMapping("/pay/nacos/get/{orderNo}")
- @SentinelResource(value = "getPayByOrderNo",blockHandler = "handlerBlockHandler")
- public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo)
- {
- //模拟从数据库查询出数据并赋值给DTO
- PayDTO payDTO = new PayDTO();
-
- payDTO.setId(1024);
- payDTO.setOrderNo(orderNo);
- payDTO.setAmount(BigDecimal.valueOf(9.9));
- payDTO.setPayNo("pay:"+ IdUtil.fastUUID());
- payDTO.setUserId(1);
-
- return ResultData.success("查询返回值:"+payDTO);
- }
- public ResultData handlerBlockHandler(@PathVariable("orderNo") String orderNo, BlockException exception)
- {
- return ResultData.fail(Code.RC500.getCode(),"getPayByOrderNo服务不可用," +
- "触发sentinel流控配置规则"+"\t"+"o(╥﹏╥)o");
- }
- /*
- fallback服务降级方法纳入到Feign接口统一处理,全局一个
- public ResultData myFallBack(@PathVariable("orderNo") String orderNo,Throwable throwable)
- {
- return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"异常情况:"+throwable.getMessage());
- }
- */
- }
修改c1oud-api-commons
添加pom
- <!--openfeign-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <!--alibaba-sentinel-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
- </dependency>
为远程调用新建全局统一服务降级类
- @Component
- public class PayFeignSentinelApiFallBack implements PayFeignApi.PayFeignSentinelApi
- {
- @Override
- public ResultData getPayByOrderNo(String orderNo)
- {
- return ResultData.fail(Code.RC500.getCode(),"对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o");
- }
- }
新增PayFeignSentinelApi接▣
- @FeignClient(value = "nacos-payment-provider",fallback = PayFeignSentinelApiFallBack.class)
- public interface PayFeignSentinelApi
- {
- @GetMapping("/pay/nacos/get/{orderNo}")
- public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo);
- }
修改c1ouda1 ibaba-consumer-nacos-order83
改pom
<!-- 引入自己定义的api通用包 --> <dependency> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--alibaba-sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
主启动类添加@EnableFeignClients)启动Feign的功能
cloudalibaba-sentinel-gateway9528 保护 cloudalibaba-provider-payment9001
pom
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> <scope>compile</scope> </dependency> </dependencies>
yml
- server:
- port: 9528
-
- spring:
- application:
- name: cloudalibaba-sentinel-gateway # sentinel+gataway整合Case
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- gateway:
- routes:
- - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
- uri: http://localhost:9001 #匹配后提供服务的路由地址
- predicates:
- - Path=/pay/** # 断言,路径相匹配的进行路由
网关配置
- @Configuration
- public class GatewayConfiguration {
-
- private final List<ViewResolver> viewResolvers;
- private final ServerCodecConfigurer serverCodecConfigurer;
-
- public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer)
- {
- this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
- this.serverCodecConfigurer = serverCodecConfigurer;
- }
-
- @Bean
- @Order(Ordered.HIGHEST_PRECEDENCE)
- public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
- // Register the block exception handler for Spring Cloud Gateway.
- return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
- }
-
- @Bean
- @Order(-1)
- public GlobalFilter sentinelGatewayFilter() {
- return new SentinelGatewayFilter();
- }
-
- @PostConstruct //javax.annotation.PostConstruct
- public void doInit() {
- initBlockHandler();
- }
-
-
- //处理/自定义返回的例外信息
- private void initBlockHandler() {
- Set<GatewayFlowRule> rules = new HashSet<>();
- rules.add(new GatewayFlowRule("pay_routh1").setCount(2).setIntervalSec(1));
-
- GatewayRuleManager.loadRules(rules);
- BlockRequestHandler handler = new BlockRequestHandler() {
- @Override
- public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
- Map<String,String> map = new HashMap<>();
-
- map.put("errorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
- map.put("errorMessage", "请求太过频繁,系统忙不过来,触发限流(sentinel+gataway整合Case)");
-
- return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
- .contentType(MediaType.APPLICATION_JSON)
- .body(BodyInserters.fromValue(map));
- }
- };
- GatewayCallbackManager.setBlockHandler(handler);
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。