Dubbo优雅服务降级之Stub和回声服务


title: Dubbo优雅服务降级之Stub和回声服务 tags:

  • dubbo
  • 服务降级
  • stub
  • echo
  • proxy categories: dubbo date: 2017-06-25 18:18:52

上篇Dubbo优雅服务降级之mock描述了关于mock的细节。此篇就详述一下关于Stub的实现。

在dubbo的官方文档中写道

Mock是Stub的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现RpcException(比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用Stub,可能就需要捕获并依赖RpcException类,而用Mock就可以不依赖RpcException,因为它的约定就是只有出现RpcException时才执行。

Mock通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。

从上述中的表述可知,stub的功能更为全面,可以说可以操作的范围更加广阔。

首先我们来看一下在Dubbo中核心生成代理类的方法。

com.alibaba.dubbo.rpc.ProxyFactory

我们找到熟悉的spi接口文件

    stub=com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
    jdk=com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory
    javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
复制代码

可以看到熟悉的jdk代理和javassist代理(我们常用的包括cglib,jdk代理,javassit代理)

除此之外就是StubProxyFactoryWrapper

如前几篇说描述,wrapper一定会出现在extensionLoader获取扩展点的包装上。

因此可以认为此wrapper将会包装所有从proxy扩展点获取到的代理对象上。

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        T proxy = proxyFactory.getProxy(invoker);
        if (GenericService.class != invoker.getInterface()) {
            String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
            if (ConfigUtils.isNotEmpty(stub)) {
                Class<?> serviceType = invoker.getInterface();
                if (ConfigUtils.isDefault(stub)) {
                    if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) {
                        stub = serviceType.getName() + "Stub";
                    } else {
                        stub = serviceType.getName() + "Local";
                    }
                }
                try {
                    Class<?> stubClass = ReflectUtils.forName(stub);
                    if (! serviceType.isAssignableFrom(stubClass)) {
                        throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + serviceType.getName());
                    }
                    try {
                        Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                        proxy = (T) constructor.newInstance(new Object[] {proxy});
                        //export stub service
                        URL url = invoker.getUrl();
                        if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)){
                            url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
                            url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
                            try{
                                export(proxy, (Class)invoker.getInterface(), url);
                            }catch (Exception e) {
                                LOGGER.error("export a stub service error.", e);
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        throw new IllegalStateException("No such constructor \"public " + stubClass.getSimpleName() + "(" + serviceType.getName() + ")\" in stub implemention class " + stubClass.getName(), e);
                    }
                } catch (Throwable t) {
                    LOGGER.error("Failed to create stub implemention class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t);
                    // ignore
                }
            }
        }
        return proxy;
    }
复制代码

从代码中可知,绝大部分和mock的情况一致,通过获取指定的Stub对象(默认以Local结尾)。

我们在回顾一下ReferenceBean引用到远程服务的过程中的最后一步

    // 创建服务代理
    return (T) proxyFactory.getProxy(invoker);
复制代码

由此我们可以认为创建的对象是由Stubwrapper包装过的。

那么此时Stub就拥有了比Mock更早开始或者更晚收尾的时机(基本可以认为是在Spring Aop针对invoke的环绕增强)。

这样服务端开发者也可以定义好Stub和接口jar一起下发给对应的服务使用者(比如参数校验等等,决定是否调用真实的服务端等等)

谈到了代理的创建,顺便也要说一下关于EchoService的创建。

我们在拿到Dubbo服务时,可以将任意服务强制转换为EchoService。

  1. 服务消费方,通过将服务强制转型为EchoService,并调用$echo()测试该服务的提供者是可用

如 assertEqauls(“OK”, ((EchoService)memberService).$echo(“OK”));

我们知道了能够强转说明了该服务必然是实现了此接口。那么自然可以猜测是在动态代理时做的手脚。

观察到JavassistProxyFactory和JdkProxyFactory均继承了

    public abstract class AbstractProxyFactory implements ProxyFactory {
     
        public <T> T getProxy(Invoker<T> invoker) throws RpcException {
            Class<?>[] interfaces = null;
            String config = invoker.getUrl().getParameter("interfaces");
            if (config != null && config.length() > 0) {
                String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
                if (types != null && types.length > 0) {
                    interfaces = new Class<?>[types.length + 2];
                    interfaces[0] = invoker.getInterface();
                    interfaces[1] = EchoService.class;
                    for (int i = 0; i < types.length; i ++) {
                        interfaces[i + 1] = ReflectUtils.forName(types[i]);
                    }
                }
            }
            if (interfaces == null) {
                interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
            }
            return getProxy(invoker, interfaces);
        }
         
        public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);
     
    }
复制代码

可以明显看出该接口会在动态代理时生成,那么具体的实现在哪里呢?

这个需要回到FIlter的机制上来了解dubbo源码系列之filter的今世

在对应的Filter中存在

    @Activate(group = Constants.PROVIDER, order = -110000)
    public class EchoFilter implements Filter {
     
       public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
          if(inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1 )
             return new RpcResult(inv.getArguments()[0]);
          return invoker.invoke(inv);
       }
     
    }
复制代码

该方法返回了inv的默认第一个参数。该FIlter作用在服务端,并且排序相对较小,可以认为基本上该过滤器的顺序靠后,尽量避免误拦截指定的方法(方法名称需要规范,框架采用$echo )

因此在客户端可以任意调用某个服务端的$echo方法完成校验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值