jannal(无名小宝)

没有失败,只有缓慢的成功

导航

Dubbo之Stub(本地存根)

dubbo版本

  1. dubbo版本2.6.7

本地存根Stub

  1. 本地存根:在客户端执行部分逻辑。如果向下客户端执行部分逻辑,比如ThreadLocal缓存、提前校验参数、调用失败伪装容错数据等。此时就需要在API中带上Stub,客户端生成proxy实例,会把Proxy通过构造函数传递给Stub,然后把Stub暴露给用户,由Stub决定是否进行远程调用

  2. 配置

    <dubbo:service interface="com.foo.BarService" stub="true" />
    或者
    <dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
    
    在consumer配置(stub本身就在consumer,所以consumer端配置比较合适)
    <dubbo:service interface="com.foo.BarService" stub="true" />
    <dubbo:reference interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
    
    因为local已经过时,现在使用stub代替local
    <dubbo:service interface="com.foo.BarService" local="true" />
    <dubbo:reference interface="com.foo.BarService" local="com.foo.BarServiceLocal" />
    
  3. 编写存根实现规范:

    • Stub和Local 必须有可传入 Proxy 的构造函数和实现业务接口
    • 如果配置local=true,实现类的命名规范必须是接口名+Stub或者接口名+Local,而且要和接口在一个包下
    @Slf4j
    public class DemoServiceLocal implements DemoService {
    
        private final DemoService demoService;
    
        /**
         *构造函数传入真正的远程代理对象
         */
        public DemoServiceLocal(DemoService demoService) {
            this.demoService = demoService;
        }
    
    
        @Override
        public String sayHello(String name) {
            //可以前后实现AOP增强或者容错
    
            try {
                log.info("开始远程调用");
                String result = demoService.sayHello(name);
                log.info("结束远程调用,返回信息:{}", result);
                return result;
            } catch (Exception e) {
                log.error("容错数据");
            }
            return "容错数据";
        }
    
        @Override
        public SayResponseDTO sayHello(SayRequestDTO sayRequestDTO) {
            return null;
        }
    }
    
    

源码分析

  1. 检查配置AbstractInterfaceConfig#checkStub

    void checkStub(Class<?> interfaceClass) {
        //如果存在local=xxx配置,如果是local=true,则比如按照【接口名称+Local】的命名方式。如果是直接指定类名,则没有要求
        if (ConfigUtils.isNotEmpty(local)) {
            Class<?> localClass = ConfigUtils.isDefault(local) ? ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local);
            //local实现类必须实现业务接口
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
            }
            //local实现类必须有一个接口构造函数
            try {
                ReflectUtils.findConstructor(localClass, interfaceClass);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
            }
        }
        //与上面local逻辑一样
        if (ConfigUtils.isNotEmpty(stub)) {
            Class<?> localClass = ConfigUtils.isDefault(stub) ? ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub);
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
            }
            try {
                ReflectUtils.findConstructor(localClass, interfaceClass);
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
            }
        }
    }
    
  2. 导出时检查ServiceConfig#doExport

    //local属性已被弃用,由stub属性替代
    if (local != null) {
        if ("true".equals(local)) {
            local = interfaceName + "Local";
        }
        Class<?> localClass;
        try {
            localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(localClass)) {
            throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
        }
    }
    // stub设为true,表示使用缺省代理类名,即:接口名 +Stub后缀,
    // 服务接口客户端本地代理类名,用于在客户端执行本地逻辑,如本地缓存等,
    // 该本地代理类的构造函数必须允许传入远程代理对象,
    // 构造函数如:public XxxServiceStub(XxxService xxxService)
    if (stub != null) {
        if ("true".equals(stub)) {
            stub = interfaceName + "Stub";
        }
        Class<?> stubClass;
        try {
            stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(stubClass)) {
            throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
        }
    }
    
  3. StubProxyFactoryWrapper是对ProxyFactory进行增强处理

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        T proxy = proxyFactory.getProxy(invoker);
        //非泛化调用
        if (GenericService.class != invoker.getInterface()) {
            //local已经废弃,这里是为了兼容
            String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
            if (ConfigUtils.isNotEmpty(stub)) {
                Class<?> serviceType = invoker.getInterface();
                //如果配置stub=true,而非直接配置Stub实现类名
                if (ConfigUtils.isDefault(stub)) {
                    //XXXServiceStub或者XXXServiceLocal
                    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 implementation class " + stubClass.getName() + " not implement interface " + serviceType.getName());
                    }
                    try {
                        //将代理对象传入Stub类的构造函数
                        Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                        //替换proxy成为新的proxy
                        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 = 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 implementation class " + stubClass.getName(), e);
                    }
                } catch (Throwable t) {
                    LOGGER.error("Failed to create stub implementation class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t);
                    // ignore
                }
            }
        }
        return proxy;
    }
    

本地Mock

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

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

  3. 配置

    <dubbo:reference interface="com.foo.BarService" mock="true" />
    
    <dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
    
  4. Mock实现:

    • mock=true方式,命名规范必须是【接口名+Mock】
    • 必须实现业务接口,并且有一个无参数的构造函数
    public class BarServiceMock implements BarService {
        public String sayHello(String name) {
            // 你可以伪造容错数据,此方法只在出现RpcException时被执行
            return "容错数据";
        }
    }
    
    以下异常处理可以用mock代替
    Offer offer = null;
    try {
        offer = offerService.findOffer(offerId);
    } catch (RpcException e) {
       logger.error(e);
    }
    或者直接配置
    <dubbo:reference interface="com.foo.BarService" mock="return null" />
    
  5. mock语法

    1. 使用 return 来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是:
        empty: 代表空,基本类型的默认值,或者集合类的空值
        null: null
        true: true
        false: false
        JSON 格式: 反序列化 JSON 所得到的对象
    
    2. 使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。
       2.1 当调用出错时,抛出一个默认的 RPCException    
       <dubbo:reference interface="com.foo.BarService" mock="throw" />
       2.2 当调用出错时,抛出指定的 Exception: 
       <dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" />
         
    3.在 2.6.6 以上的版本,可以开始在 Spring XML 配置文件中使用 fail: 和 force:。
      3.1 force: 代表强制使用 Mock 行为,在这种情况下不会走远程调用。
      <dubbo:reference interface="com.foo.BarService" mock="force:return fake" />
      <dubbo:reference interface="com.foo.BarService" mock="force:com.foo.BarServiceMock" />
      3.2 fail: 与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。force: 和 fail: 都支持与 throw 或者 return 组合使用 
      <dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />  
           
    
  6. 方法级别配置Mock

    <dubbo:reference id="demoService" check="false" interface="com.foo.BarService">
    	为sayHello()方法指定mock
      <dubbo:parameter key="sayHello.mock" value="force:return fake"/>
    </dubbo:reference>
    

源码分析

  1. 检查配置AbstractInterfaceConfig#checkMock

    void checkMock(Class<?> interfaceClass) {
        if (ConfigUtils.isEmpty(mock)) {
            return;
        }
        //标准化mock配置字符串
        String normalizedMock = MockInvoker.normalizeMock(mock);
        //如果是以return开头
        if (normalizedMock.startsWith(Constants.RETURN_PREFIX)) {
            normalizedMock = normalizedMock.substring(Constants.RETURN_PREFIX.length()).trim();
            try {
                //解析return 字符串
                MockInvoker.parseMockValue(normalizedMock);
            } catch (Exception e) {
                throw new IllegalStateException("Illegal mock return in <dubbo:service/reference ... " +
                        "mock=\"" + mock + "\" />");
            }
        } else if (normalizedMock.startsWith(Constants.THROW_PREFIX)) {
            normalizedMock = normalizedMock.substring(Constants.THROW_PREFIX.length()).trim();
            if (ConfigUtils.isNotEmpty(normalizedMock)) {
                try {
                    //解析throw 字符串
                    MockInvoker.getThrowable(normalizedMock);
                } catch (Exception e) {
                    throw new IllegalStateException("Illegal mock throw in <dubbo:service/reference ... " +
                            "mock=\"" + mock + "\" />");
                }
            }
        } else {
            MockInvoker.getMockObject(normalizedMock, interfaceClass);
        }
    }
    
  2. 获取Mock对象MockInvoker#getMockObject

    public static Object getMockObject(String mockService, Class serviceType) {
        if (ConfigUtils.isDefault(mockService)) {
            mockService = serviceType.getName() + "Mock";
        }
    
        Class<?> mockClass = ReflectUtils.forName(mockService);
        if (!serviceType.isAssignableFrom(mockClass)) {
            throw new IllegalStateException("The mock class " + mockClass.getName() +
                    " not implement interface " + serviceType.getName());
        }
    
        try {
            return mockClass.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalStateException("No default constructor from mock class " + mockClass.getName(), e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }
    

posted on 2022-02-08 10:36  jannal  阅读(197)  评论(0编辑  收藏  举报