一、前言
本系列为个人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"));
}
}
我们这里这里可以分为三种情况,
-
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)
-
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 指定了合并结果集的方法。需要注意的是:
- 如果以
.
开头则表示指定了合并结果集的方法,合并方法必须是返回类的方法,即这里的合并方法为方法返回类 MergerResult 的 merger 方法。 - 合并方法的入参必须为接口方法返回类型的方法。这里指定的即为MergerResult#merger方法,因为sayHello 方法的返回类型为 MergerResult。
- 合并方法的入参必须是结果类型,即必须是自身类型。如 MergerResult#merger 方法的入参必须是 MergerResult。
- 如果以
-
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);
}
上面的逻辑比较清晰,这里来简单总结:
- 首先获取merger 参数。如果 merger 参数没有配置,如果没有指定分组结果合并,则遍历执行到一个invoker 成功便返回结果。
- 如果代码执行到这一步,则说明消费之配置了 merger。则开始并发调用所有的 invoker获取到所有的结果集。
- 对结果集进行判断,如果只有一个结果集,或者方法没有返回值则不通过合并策略直接返回。
- 判断合并逻辑是通过合并方法还是合并策略。判断的依据即是 merger 是否以
.
开头。 - 如果是合并方法,则需要判断方法返回类型是否有合法的合并方法。如果有,则通过反射调用指定方法,将最终结果返回。
- 如果指定了合并策略,则根据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
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正