Sentenel对Dubbo支持

(1) Dubbo中使用Sentinel

Sentinel与dubbo结合的基本思路是sentinel利用了Dubbo Filter和spi机制进行拓展,这样做的好处可以做到无需改动业务代码就能支持限流、熔断等功能。

SPI对应的配置文件 /resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter

sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter

(2) 源码解析

源码 https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java

package com.alibaba.csp.sentinel.adapter.dubbo;

/**
 * 支持与Sentinel集成的Apache Dubbo服务提供商筛选器。默认情况下自动激活。
 * 注意:这只适用于Apache Dubbo 2.7.x或更高版本。
 *  
 * 如果要禁用提供程序筛选器,可以配置: dubbo:provider filter="-sentinel.dubbo.provider.filter" 
 */
@Activate(group = PROVIDER)
public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter {

    public SentinelDubboProviderFilter() {
        RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
    }

    @Override
    String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
        return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
    }

    @Override
    String getInterfaceName(Invoker invoker, String prefix) {
        return DubboUtils.getInterfaceName(invoker, prefix);
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // Get origin caller.
        String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation);
        if (null == origin) {
            origin = "";
        }
        Entry interfaceEntry = null;
        Entry methodEntry = null;
        String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey();
        // 接口名
        String interfaceResourceName = getInterfaceName(invoker, prefix);
        // 方法名 
        String methodResourceName = getMethodName(invoker, invocation, prefix);
        try {
            // Only need to create entrance context at provider side, as context will take effect
            // at entrance of invocation chain only (for inbound traffic).
            // 使用方法名作为上下文名称
            ContextUtil.enter(methodResourceName, origin);
            // 使用接口名作为interfaceEntry资源名
            interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
            // 使用方法名作为methodEntry资源名
            methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN,
                invocation.getArguments());

            // 调用方法    
            Result result = invoker.invoke(invocation);
            if (result.hasException()) {
                Tracer.traceEntry(result.getException(), interfaceEntry);
                Tracer.traceEntry(result.getException(), methodEntry);
            }
            return result;
        } catch (BlockException e) {
            // 触发限流或熔断 
            return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e);
        } catch (RpcException e) {
            Tracer.traceEntry(e, interfaceEntry);
            Tracer.traceEntry(e, methodEntry);
            throw e;
        } finally {
            if (methodEntry != null) {
                // 退出
                methodEntry.exit(1, invocation.getArguments());
            }
            if (interfaceEntry != null) {
                // 退出
                interfaceEntry.exit();
            }
            ContextUtil.exit();
        }
    }

}

限流或熔断后调用的方法 DefaultDubboFallback::handle()

package com.alibaba.csp.sentinel.adapter.dubbo.fallback;

/**
 * 
 */
public class DefaultDubboFallback implements DubboFallback {

    @Override
    public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
        // Just wrap the exception.
        return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation);
    }
}