Dubbo笔记衍生篇⑤:MergeableClusterInvoker

一、前言

本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章。仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


本文为 Dubbo笔记⑬ :Dubbo 集群组件 之 Cluster & ClusterInvoker衍生篇,主要介绍 MergeableClusterInvoker 策略在集群容错的实现已经作用。

二、多分组调用

首先需要明确在Dubbo中是通过 服务接口 + 服务分组 + 服务版本号确定唯一服务。当一个接口有多种实现时,可以用group区分,所以一个服务可能存在多个不同分组,而一个消费者可以同时引用一个接口的不同分组实现。

	// 引用服务分组为 aaa, bbb 的 demeService 服务
    @Reference(version = "1.0.0", group = "aaa,bbb")
    private DemoService demoService;

默认情况下,Dubbo会挑选其中一个分组的接口调用并返回接口。但是某些情况下,我们可能需要合并多个分组的返回接口。比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。此时可以通过 merger 参数指定合并策略。

下面我们看一下demo 实现

1. 分组结果合并 Demo

	// dubbo 服务接口
	public interface SimpleDemoService {
		// 用于测试 分组方法合并
	    MergerResult sayHello(String msg);
	    // 用于测试 分组策略合并,之所以区分两个接口是因为,策略合并限制了某些只有某些返回类型才能使用。
	    List<String> sayHello2(String msg);
	
	}
	
	// spring 接口分组实现
	@Service(group = "spring", version = "1.0.0")
	public class SpringSimpleDemoServiceImpl implements SimpleDemoService {
		
	    @Override
	    public MergerResult sayHello(String msg) {
	        return new MergerResult("StringSimpleDemoServiceImpl : " + msg);
	    }
		
	    @Override
	    public List<String> sayHello2(String msg) {
	        return Lists.newArrayList("StringSimpleDemoServiceImpl : " + msg);
	    }
	}
	
	// main 分组接口实现
	@Service(group = "main", version = "1.0.0")
	public class MainSimpleDemoServiceImpl implements SimpleDemoService {
	    @Override
	    public MergerResult sayHello(String msg) {
	        return new MergerResult("MainSimpleDemoServiceImpl : " + msg);
	    }
	
	    @Override
	    public List<String> sayHello2(String msg) {
	        return Lists.newArrayList("MainSimpleDemoServiceImpl : " + msg);
	    }
	}
	
	// 返回结果,用于合并结果
	@Data
	public class MergerResult implements Serializable {
	    private String result;
	
	    public MergerResult(String result) {
	        this.result = result;
	    }
		// 合并结果时调用的方法
	    public MergerResult merger(MergerResult mergerResult) {
	        return new MergerResult(this.result + " | " + mergerResult.getResult());
	    }
	}

这里为了方便 直接使用Main 方法调用

public class SimpleConsumer {
    public static void main(String[] args) {
    	// 自定义的简易生成ReferenceConfig工具类
        ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
//        Map map = Maps.newHashMap();
//        map.put("merger", "list");
//        map.put("merger", ".merger");
//        referenceConfig.setParameters(map);
		// 设置调用服务分组为 spring 和 main
        referenceConfig.setGroup("spring,main");
        SimpleDemoService demoService = referenceConfig.get();
        System.out.println(demoService.sayHello("SimpleConsumer"));
    }
}

我们这里这里可以分为三种情况,

  1. merger 参数默认,即不指定 merger 方式:
    这种情况即默认情况,Dubbo 会选择一个 invoker 调用并返回

            ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
            referenceConfig.setGroup("spring,main");
            SimpleDemoService demoService = referenceConfig.get();
            System.out.println(demoService.sayHello("SimpleConsumer"));
    

    返回结果如下,可以看到调用了 group=spring 的服务。

    MergerResult(result=StringSimpleDemoServiceImpl : SimpleConsumer)
    
  2. merger 指定 合并方法:
    当调用时会将多个分组的结果通过指定方法来进行合并。

            ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
            Map map = Maps.newHashMap();
            map.put("merger", ".merger");
            referenceConfig.setParameters(map);
            referenceConfig.setGroup("spring,main");
            SimpleDemoService demoService = referenceConfig.get();
            System.out.println(demoService.sayHello("SimpleConsumer"));
    

    返回结果如下,可以看到调用了 group=spring 和 main 的服务,并将其结果合并了,合并调用是的MergerResult#merger 方法。

    MergerResult(result=StringSimpleDemoServiceImpl : SimpleConsumer | MainSimpleDemoServiceImpl : SimpleConsumer)
    

    这里通过 merger 指定了合并结果集的方法。需要注意的是:

    1. 如果以 . 开头则表示指定了合并结果集的方法,合并方法必须是返回类的方法,即这里的合并方法为方法返回类 MergerResult 的 merger 方法。
    2. 合并方法的入参必须为接口方法返回类型的方法。这里指定的即为MergerResult#merger方法,因为sayHello 方法的返回类型为 MergerResult。
    3. 合并方法的入参必须是结果类型,即必须是自身类型。如 MergerResult#merger 方法的入参必须是 MergerResult。
  3. merger 指定合并策略:
    这里我们调用sayHello2 方法,因为合并策略限定了返回类型。

            ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
            Map map = Maps.newHashMap();
            // 指定合并策略为 list。即结果类型为 list 的才能合并
            map.put("merger", "list");
            referenceConfig.setParameters(map);
            referenceConfig.setGroup("spring,main");
            SimpleDemoService demoService = referenceConfig.get();
            System.out.println(demoService.sayHello2("SimpleConsumer"));
    

    返回结果如下为一个 List集合,可以看到 group=spring 和 main 的服务的返回结果被合并了。合并过程在 ListMerger 中实现。

    [StringSimpleDemoServiceImpl : SimpleConsumer, MainSimpleDemoServiceImpl : SimpleConsumer]
    

上面区分了三种情况的demo 实现,下面我们来对代码进行分析。

三、源码分析

1. MergeableClusterInvoker 调用时机

在<Dubbo笔记⑬ :Dubbo 集群 之 Cluster & ClusterInvoker 中,我们介绍过,Cluster Invoker 功能的实现是通过doInvoker 方法完成。但是对于
与其他 Cluster Invoker 不同的是,MergeableClusterInvoker不仅仅可以通过指定策略的方式加载执行。当消费者调用多个分组的服务时,Dubbo会自动指定MergeableClusterInvoker 为集群容错策略 ,如下 :

在 RegistryProtocol#refer 中如果当前消费了多个分组的同一个服务则会通过getMergeableCluster() 方法 来指定mergeable 策略,使用 MergeableClusterInvoker 来进行集群容错。(关于 RegistryProtocol#refer 的内容不是本文重点,如果需要详参Dubbo笔记⑨ : 消费者启动流程 - RegistryProtocol#refer) :

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    	//取 registry 参数值,并将其设置为协议头
        url = url.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY)).removeParameter(REGISTRY_KEY);
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }
        // group="a,b" or group="*"
        // 将 url 查询字符串转为 Map
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        // 获取 group 配置
        String group = qs.get(Constants.GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            	// 1. 如果是多分组的情况下,通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑。
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        // 调用 doRefer 继续执行服务引用逻辑
        return doRefer(cluster, registry, type, url);
    }

    private Cluster getMergeableCluster() {
        return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable");
    }

2. MergeableClusterInvoker#doInvoker

上述Demo 中的结果处理是在 MergeableClusterInvoker#doInvoker 中完成。下面我们来看 MergeableClusterInvoker 是如何处理合并策略 ::

   @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        checkInvokers(invokers, invocation);
        // 1. 获取 merger 属性
        String merger = getUrl().getMethodParameter(invocation.getMethodName(), Constants.MERGER_KEY);
        // 2. 如果没有指定分组结果合并,则遍历执行到一个invoker 成功便返回结果
        if (ConfigUtils.isEmpty(merger)) { // If a method doesn't have a merger, only invoke one Group
            for (final Invoker<T> invoker : invokers) {
                if (invoker.isAvailable()) {
                    try {
                        return invoker.invoke(invocation);
                    } catch (RpcException e) {
                        if (e.isNoInvokerAvailableAfterFilter()) {
                            log.debug("No available provider for service" + directory.getUrl().getServiceKey() + " on group " + invoker.getUrl().getParameter(Constants.GROUP_KEY) + ", will continue to try another group.");
                        } else {
                            throw e;
                        }
                    }
                }
            }
            return invokers.iterator().next().invoke(invocation);
        }
		// 3. 到这里说明指定了分组合并,
        Class<?> returnType;
        try {
        	// 4 获取调用方法的返回类型
            returnType = getInterface().getMethod(
                    invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
        } catch (NoSuchMethodException e) {
            returnType = null;
        }
		// 5. 通过线程池进行异步调用
        Map<String, Future<Result>> results = new HashMap<String, Future<Result>>();
        for (final Invoker<T> invoker : invokers) {
            Future<Result> future = executor.submit(new Callable<Result>() {
                @Override
                public Result call() throws Exception {
                    return invoker.invoke(new RpcInvocation(invocation, invoker));
                }
            });
            results.put(invoker.getUrl().getServiceKey(), future);
        }

        Object result = null;

        List<Result> resultList = new ArrayList<Result>(results.size());
		// 获取超时时间
        int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        for (Map.Entry<String, Future<Result>> entry : results.entrySet()) {
            Future<Result> future = entry.getValue();
            try {
            	// 6. 获取异步调用的结果
                Result r = future.get(timeout, TimeUnit.MILLISECONDS);
                if (r.hasException()) {
                    log.error("Invoke " + getGroupDescFromServiceKey(entry.getKey()) +
                                    " failed: " + r.getException().getMessage(),
                            r.getException());
                } else {
                    resultList.add(r);
                }
            } catch (Exception e) {
                throw new RpcException("Failed to invoke service " + entry.getKey() + ": " + e.getMessage(), e);
            }
        }
		// 7. 结果集的预处理
		// 结果集为空则返回空的RpcResult
        if (resultList.isEmpty()) {
            return new RpcResult((Object) null);
        } else if (resultList.size() == 1) {
        // 结果集只有一个,则不需要合并直接返回
            return resultList.iterator().next();
        }
		// 如果调用方法返回类型是 void。则返回 空RpcResult
        if (returnType == void.class) {
            return new RpcResult((Object) null);
        }
        // 8. 对结果集进行合并
		// 8.1 对merger 参数的处理,如果以. 开头则说明指定了合并方法,对合并方法的处理
        if (merger.startsWith(".")) {
            merger = merger.substring(1);
            Method method;
            try {
                method = returnType.getMethod(merger, returnType);
            } catch (NoSuchMethodException e) {
                throw new RpcException("Can not merge result because missing method [ " + merger + " ] in class [ " +
                        returnType.getClass().getName() + " ]");
            }
            if (!Modifier.isPublic(method.getModifiers())) {
                method.setAccessible(true);
            }
            result = resultList.remove(0).getValue();
            // 8.2 调用指定的方法进行结果合并
            try {
                if (method.getReturnType() != void.class
                        && method.getReturnType().isAssignableFrom(result.getClass())) {
                    for (Result r : resultList) {
                        result = method.invoke(result, r.getValue());
                    }
                } else {
                    for (Result r : resultList) {
                        method.invoke(result, r.getValue());
                    }
                }
            } catch (Exception e) {
                throw new RpcException("Can not merge result: " + e.getMessage(), e);
            }
        } else {
        	// 8.3 否则认为是指定了合并策略. 开始获取合并策略。
            Merger resultMerger;
            if (ConfigUtils.isDefault(merger)) {
                resultMerger = MergerFactory.getMerger(returnType);
            } else {
                resultMerger = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(merger);
            }
            if (resultMerger != null) {
                List<Object> rets = new ArrayList<Object>(resultList.size());
                for (Result r : resultList) {
                    rets.add(r.getValue());
                }
                // 8.4 通过合并策略进行合并
                result = resultMerger.merge(
                        rets.toArray((Object[]) Array.newInstance(returnType, 0)));
            } else {
                throw new RpcException("There is no merger to merge result.");
            }
        }
        // 9. 返回合并后的结果集
        return new RpcResult(result);
    }

上面的逻辑比较清晰,这里来简单总结:

  1. 首先获取merger 参数。如果 merger 参数没有配置,如果没有指定分组结果合并,则遍历执行到一个invoker 成功便返回结果。
  2. 如果代码执行到这一步,则说明消费之配置了 merger。则开始并发调用所有的 invoker获取到所有的结果集。
  3. 对结果集进行判断,如果只有一个结果集,或者方法没有返回值则不通过合并策略直接返回。
  4. 判断合并逻辑是通过合并方法还是合并策略。判断的依据即是 merger 是否以 . 开头。
  5. 如果是合并方法,则需要判断方法返回类型是否有合法的合并方法。如果有,则通过反射调用指定方法,将最终结果返回。
  6. 如果指定了合并策略,则根据merger 参数获取到Merger 实现类,调用Merger#merge来将结果集合并并返回。

3. Merger 接口

Merger 如其名,用来合并多个分组的结果集。其中关于 Merger 接口的类型, org.apache.dubbo.rpc.cluster.Merger 中提供了如下一些实现类。

map=org.apache.dubbo.rpc.cluster.merger.MapMerger
set=org.apache.dubbo.rpc.cluster.merger.SetMerger
list=org.apache.dubbo.rpc.cluster.merger.ListMerger
byte=org.apache.dubbo.rpc.cluster.merger.ByteArrayMerger
char=org.apache.dubbo.rpc.cluster.merger.CharArrayMerger
short=org.apache.dubbo.rpc.cluster.merger.ShortArrayMerger
int=org.apache.dubbo.rpc.cluster.merger.IntArrayMerger
long=org.apache.dubbo.rpc.cluster.merger.LongArrayMerger
float=org.apache.dubbo.rpc.cluster.merger.FloatArrayMerger
double=org.apache.dubbo.rpc.cluster.merger.DoubleArrayMerger
boolean=org.apache.dubbo.rpc.cluster.merger.BooleanArrayMerger

不同类型的 Merger 用于合并不同的结果集。比如MapMerger 会合并多个Map,并返回合并后的Map。

public class MapMerger implements Merger<Map<?, ?>> {

    @Override
    public Map<?, ?> merge(Map<?, ?>... items) {
        if (ArrayUtils.isEmpty(items)) {
            return Collections.emptyMap();
        }
        Map<Object, Object> result = new HashMap<Object, Object>();
        for (Map<?, ?> item : items) {
            if (item != null) {
                result.putAll(item);
            }
        }
        return result;
    }

}

消费者根据返回类型的不同指定不同的合并策略,通过这种方式将不同分组的结果集进行合并。


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
https://blog.csdn.net/abcde474524573/article/details/53026110
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值