AlibabaCloud-sentinel-getway熔断篇(2)

AlibabaCloud-sentinel熔断篇(2)

在上一篇文章中,我们介绍了使用sentinel对接口进行限流,但是Sentinel的功能当然不局限于接口拉,这个时候@SentinelResource注解就出来了,

下面就看下他的使用方法吧

第一步:首先启动sentinel控制台

java -Dserver.port=8889  -jar sentinel-dashboard-1.8.0.jar 

第二步: 增加注解支持的配置:

    public static void main(String[] args) {
    
    
        new SpringApplicationBuilder(FebsServerTestApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }

    // 注解支持的配置Bean
    @Bean
    public SentinelResourceAspect sentinelResourceAspect(){
    
    
        return new SentinelResourceAspect();
    }

第二步: 在需要通过Sentinel来控制流量的地方使用@SentinelResource注解,比如下面以控制Service逻辑层的某个方法为

package cc.mrbird.febs.server.test.service.impl;

import cc.mrbird.febs.server.test.service.ITestService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @author: craywen
 * @date: 2020-09-07 10:22
 * @desc:
 */
@Service
@Slf4j
public class TestServerImpl implements ITestService {
    
    

    @Override
    @SentinelResource(value = "sentinelTest")
    public void sentinelTest() {
    
    
     log.info("----------------限流方法测试------------");
    }
}

到这里就是给这个方法(简称资源点)限流了,定义了资源点之后可以实现不同的保护策略,包括:限流、降级等。

那我们先看下效果吧:首先控制台设置限流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j818rsBf-1599451264306)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200907105529843.png)]

为了演示效果,设置一秒钟2次。

postman测试效果图:

1. 单次点一次的效果图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rHDbxSmb-1599451264309)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200907105730789.png)]

2. 快速点击:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F41cp2ip-1599451264310)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images)]

这就很明显了,直接返回的500异常,对于这种我们肯定是要坐处理的。

实现限流的异常处理

默认情况下,Sentinel对控制资源的限流处理是直接抛出异常,但是正常情况为了更好的用户业务,都会实现一些被限流之后的特殊处理,我们不希望展示一个生硬的报错。那么只需要基于上面的例子做一些加工,比如:


    @Override
    @SentinelResource(value = "sentinelTest",blockHandler = "exceptionHandler")
    public void sentinelTest() {
     log.info("----------------限流方法测试------------");
    }

    // 阻塞处理
    private  void exceptionHandler(BlockException ex){
        log.error( "blockHandler:" , ex);
    }
  • 通过@SentinelResource注解的blockHandler属性制定具体的处理函数(实现自定义异常)
  • 实现处理函数,该函数的传参必须与资源点的传参一样,并且最后加上BlockException异常参数;同时,返回类型也必须一样。

实现熔断降级

@SentinelResource注解除了可以用来做限流控制之外,还能实现与Hystrix类似的熔断降级策略。

  1. 首先改造方法sentinelTest2

     // 熔断与降级处理
        @Override
        @SentinelResource(value = "sentinelTest2",fallback = "fallback")
        public void sentinelTest2() {
          
          
            log.info("sentinelTest2-------",new Date());
            throw new RuntimeException("发生异常");
        }
    
        public void fallback(){
          
          
            log.error("fallbackHandler:" +new Date());
        }
    

2.配置熔断策略[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UHhSQdmt-1599451264312)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images)]
为了让效果更明显,设置熔断时间为10S,这意思就是当请求这个方法的异常比例超过50%,会触发熔断机制,最小请求可以理解成以五次请求为一个周期。

完成上面的改造之后,重启应用,并设置sentinelTest2资源的熔断降级策略(使用异常百分比),然后频繁的请求/sentinelTest2接口。在QPS>=5之后,由于这个接口一直在抛出异常,所以一定会满足熔断降级条件,这时候就会执行fallbackHandler`方法,不断的打印如下日志:

3。为了效果我们用Apache jmeter测试:准备20个请求

先看不设置熔断的效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLbsqybk-1599451264313)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images)]

熔断效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahWDJfvd-1599451264314)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images)]

话不多说,很明显吧,直接调用fallback方法。

更多注解属性说明

关于@SentinelResource注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT

  • blockHandler / blockHandlerClass: blockHandler对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback
    

    :fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了

    exceptionsToIgnore
    

    里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback
    

    (since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了

    exceptionsToIgnore
    

    里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出

还有啥问题呢:

因为sentinel控制台的,限流或者是熔断规则都是没有 持久性化的,所有也可以持久化到数据库中。

可以参考如下配置:

package cc.mrbird.febs.gateway.common.configure;

import cc.mrbird.febs.common.core.entity.FebsResponse;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
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 org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @author: craywen
 * @date: 2020-09-04 11:10
 * @desc:  Sentinel 配置
 * 使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
 */
@Configuration
public class FebsGatewaySentinelConfiguration {
    
    

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public FebsGatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                            ServerCodecConfigurer serverCodecConfigurer) {
    
    
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 异常处理模块
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
    
    
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 过滤器
     * @return
     */
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
    
    
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
    
    
        //初始化规则
        initGatewayLimitRules();
        //熔断规则
        initFuseRules();
        //自定义限流异常处理
        initBlockHandler();
    }

    /**
     * 熔断规则 DegradeRule
     * resource	资源名,即规则的作用对象
     * grade	熔断策略,支持慢调用比例/异常比例/异常数策略	慢调用比例
     * count	慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
     * timeWindow	熔断时长,单位为 s
     * minRequestAmount	熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)	5
     * statIntervalMs	统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)	1000 ms
     * slowRatioThreshold	慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
     */
    private void initFuseRules(){
    
    
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("FEBS-Server-System");
        // set threshold RT, 10 ms
        rule.setCount(10);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

    /**
     * 初始化规则
     */
    private void initGatewayLimitRules() {
    
    
        /**  Set<FlowRule> rules api 限流
         * resource	资源名,资源名是限流规则的作用对象
         * count	限流阈值
         * grade	限流阈值类型,QPS 模式(1)或并发线程数模式(0)	QPS 模式
         * limitApp	流控针对的调用来源	default,代表不区分调用来源
         * strategy	调用关系限流策略:直接、链路、关联	根据资源本身(直接)
         * controlBehavior	流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流	直接拒绝
         * clusterMode	是否集群限流
         */
        /*GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,
        可以针对不同 route 或自定义的 API 分组进行限流,
        支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
        */

        Set<GatewayFlowRule> rules = new HashSet<>();
        /*设置限流规则
        resource: 资源名称,这里为路由router的ID
        resourceMode: 路由模式
        count: QPS即每秒钟允许的调用次数
        intervalSec: 每隔多少时间统计一次汇总数据,统计时间窗口,单位是秒,默认是 1 秒。
        */
        rules.add(new GatewayFlowRule("FEBS-Server-test")
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
                .setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 自定义限流异常处理
     * ResultSupport 为自定义的消息封装类,代码略
     */
    private void initBlockHandler() {
    
    
        GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
    
    
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange,
                                                      Throwable throwable) {
    
    
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .body(BodyInserters.fromObject(new FebsResponse().put("data", "系统访问量过大,限流啦!")));
            }
        });
    }
}

系统就会自动加载配置。

参考资料Sentinel官方文档

参考资料 http://blog.didispace.com/spring-cloud-alibaba-sentinel-2-5/

猜你喜欢

转载自blog.csdn.net/qq_38893133/article/details/108445493