alibaba / Sentinel

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
https://sentinelguard.io/
Apache License 2.0
22.32k stars 8k forks source link

sentinel+dubbo如何实现dubbo远程调用异常时全局的降级或熔断 #2803

Open ranLee1 opened 2 years ago

ranLee1 commented 2 years ago

sentinel+dubbo如何实现dubbo远程调用异常时全局的降级或熔断,尝试过sentinel-demo-dubbo无法实现全局fallback,测试代码如下

测试环境

消费者pom.xml

  <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-apache-dubbo-adapter</artifactId>
        </dependency>
        <!--Snetinel 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        </dependency>
  </dependencies>

消费者ConsumerImpl

@Service
public class ConsumerServiceImpl implements IConsumerService {
    @DubboReference
    private IProductService productService;

    @Override
    @SentinelResource(value = "testHello")
    public String hello(Long id) {
        Random random = new Random();
        productService.hello(id.toString());
        String serialNum = String.valueOf(random.nextInt(1000));
        return Thread.currentThread().getName() + "\t" + "调用成功,流水号为:" + serialNum;
    }

}

消费者DubboAdapterConfig 全局fallback配置参考Sentinel+dubbo配置,文档中说明用户只需要实现自定义的 DubboFallback 接口,并通过 DubboFallbackRegistry 注册即可,我按如下代码注册结果不生效,注册方式有误还请大佬指正🙈

@Configuration
public class  DubboAdapterConfig {
    private static final Logger logger = LoggerFactory.getLogger(DubboAdapterConfig.class);

    @Bean
    private static void registerFallback() {
        DubboFallbackRegistry.setProviderFallback(new DubboFallback() {
            @Override
            public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
                return new AppResponse("错误: " + ex.getClass().getTypeName());
            }
        });
    }
}

服务者pom.xml

 <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-apache-dubbo-adapter</artifactId>
        </dependency>
        <!--Snetinel 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
  </dependencies>

服务者ProductServiceImpl

@DubboService
public class ProductServiceImpl implements IProductService {
    @Override
    public String hello(String str) {
        return "结果:" + str;
    }
}

分别启动消费者服务和服务者服务时,可以正常远程调用。当停掉服务者时,再次请求消费者时,控制台抛异常如下:

org.apache.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:8858 for service com.ran.member.api.service.IProductService on consumer 172.17.192.1 use dubbo version 2.7.13, please check status of providers(disabled, not registered or in blacklist).
    at org.apache.dubbo.registry.integration.DynamicDirectory.doList(DynamicDirectory.java:168) ~[dubbo-2.7.13.jar:2.7.13]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ HTTP GET "/index?id=-1" [ExceptionHandlingWebHandler]
Stack trace:

请问要如何实现当服务者停机时,消费者再次请求Sentinel可以做全局降级/限流🤔

liufeiyu1002 commented 2 years ago

image 消费者 setProviderFallback你不觉得奇怪么~~ image 消费者调用失败 抛出 BlockException的时候 会调用 DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e);(所以在消费端调用需要注册的是 consumerFallack) rpc 异常的时候 没做处理
所以你需要在 调用的时候 rpc 异常了也行调用回调 或者 返回结果中有异常时按需也可调用 可以参考框架的 filter自己实现 在 rpcexception的时候也调用ConsumerFallback 。 全局降级 这个可以配置内置的断路器配合使用 添加对应的DegradeRule即可

ranLee1 commented 2 years ago

@liufeiyu1002 感谢大佬指点😊。实现方式:全局异常捕获BlockException,其中ConsumerFallack中异常继承于RuntimeException,发生限流时抛出异常为:RuntimeException:SentinelBlockException: FlowException,所以还需对RuntimeException做判断:

消费者DubboAdapterConfig调整

@Configuration
public class  DubboAdapterConfig {

    @Bean
    private static void registerFallback() {
        DubboFallbackRegistry.setConsumerFallback(new DubboFallback() {
                @Override
                public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
                   //AsyncRpcResult 
                    CompletableFuture<AppResponse> cf = new CompletableFuture<>();
                    AsyncRpcResult asyncRpcResult = new AsyncRpcResult(cf, invocation);
                    asyncRpcResult.setException(ex);
                    return asyncRpcResult;
                }
        });
    }
}

消费者全局异常捕获 GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 限流器异常捕获
     *
     * @param e BlockException
     * @return
     */
    @ExceptionHandler(value = {BlockException.class})
    public JSONResult handler(BlockException e) {
        return getBlockExceptionInfo(e);
    }

    /**
     * dubbo远程调用
     *
     * @param e RpcException
     * @return
     */
    @ExceptionHandler(value = {RpcException.class})
    public JSONResult handler(RpcException e) {
        logger.error("远程调用失败,{}", e.getMessage());
        return JSONResult.failure(e.getCode(), e.getMessage());
    }

    /**
     * 运行时异常
     *
     * @param e RuntimeException
     * @return
     */
    @ExceptionHandler(value = RuntimeException.class)
    public JSONResult handler(RuntimeException e) {
        if (e.getCause() instanceof BlockException) {
            BlockException bk = (BlockException) e.getCause();
            return getBlockExceptionInfo(bk);
        }
        return JSONResult.failure(e.getMessage());
    }

    private JSONResult getBlockExceptionInfo(BlockException e) {
        String msg = "";
        if (e instanceof FlowException) {
            msg = "请求限流";
        } else if (e instanceof ParamFlowException) {
            msg = "请求热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求降级";
        } else if (e instanceof AuthorityException) {
            msg = "无访问权限";
        } else {
            msg = "捕获异常失败";
        }
        logger.warn(e.getRuleLimitApp()+ "," + msg);
        return JSONResult.failure(msg);
    }
}