本文基于dubbo 2.7.5版本代码
dubbo的回声测试功能作用是,当客户端正式访问服务端前通过回声测试,以了解服务是否可用或者网络是否畅通,而且还可以使用该功能对服务监控。
dubbo的每个服务都自动实现了EchoService接口,使用方式也非常简单。
public class DubboClient {
@Reference
private RemoteService remoteService;
public void process(){
...
EchoService echoService = (EchoService)remoteService;
//方法的入参是什么,返回值就是什么
String status = (String)echoService.$echo("OK");
...
}
}
下面介绍一下该功能是如何实现的。
客户端实现
当看到下面代码时,我立刻产生了一个疑问,为什么RemoteService的对象可以强转为EchoService,下面先看一下这个问题。
EchoService echoService = (EchoService)remoteService;
dubbo客户端通过代理对象访问远程服务,所以remoteService其实是一个代理对象。
代理对象是在ReferenceBean的createProxy方法中创建的。
public synchronized void init() {
....
ref = createProxy(map);
....
}
private T createProxy(Map<String, String> map) {
...
return (T) PROXY_FACTORY.getProxy(invoker);
}
//PROXY_FACTORY.getProxy(invoker)最终会调用到下面的方法上
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
Set<Class<?>> interfaces = new HashSet<>();
String config = invoker.getUrl().getParameter(INTERFACES);
if (config != null && config.length() > 0) {
String[] types = COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
for (int i = 0; i < types.length; i++) {
interfaces.add(ReflectUtils.forName(types[i]));
}
}
}
if (!GenericService.class.isAssignableFrom(invoker.getInterface()) && generic) {
interfaces.add(com.alibaba.dubbo.rpc.service.GenericService.class);
}
interfaces.add(invoker.getInterface());
//INTERNAL_INTERFACES是一个Class数组,包含两个Class对象:
//EchoService.class和Destroyable.class
interfaces.addAll(Arrays.asList(INTERNAL_INTERFACES));
return getProxy(invoker, interfaces.toArray(new Class<?>[0]));
}
从INTERNAL_INTERFACES可以看出代理对象是实现了EchoService接口的,这样可以通过强制转换访问$echo方法。
服务端实现
客户端通过代理对象实现EchoService接口,这样客户端可以像调用其他方法一样访问远程服务。
服务端收到$echo调用时,首先解析请求参数,请求参数显示调用的是RemoteService接口的$echo方法,因此服务端根据请求参数找到的Invoker对象其实是RemoteService接口的Invoker对象。接着调用Invoker对象里面的Filter过滤器,其中调用的第一个过滤器便是EchoFilter。
@Activate(group = CommonConstants.PROVIDER, order = -110000)
public class EchoFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
if (inv.getMethodName().equals($ECHO) && inv.getArguments() != null && inv.getArguments().length == 1) {
//如果发现调用的方法是$echo,且入参只有一个,那么便直接生成Result对象,不在往后调用
return AsyncRpcResult.newDefaultAsyncResult(inv.getArguments()[0], inv);
}
return invoker.invoke(inv);
}
}
public static AsyncRpcResult newDefaultAsyncResult(Object value, Invocation invocation) {
return newDefaultAsyncResult(value, null, invocation);
}
public static AsyncRpcResult newDefaultAsyncResult(Object value, Throwable t, Invocation invocation) {
CompletableFuture<AppResponse> future = new CompletableFuture<>();
AppResponse result = new AppResponse();
if (t != null) {
result.setException(t);
} else {
//直接根据inv.getArguments()[0]生成result对象,并将其返回到客户端
result.setValue(value);
}
future.complete(result);
return new AsyncRpcResult(future, invocation);
}
从上面的代码可以看出,EchoFilter只在服务端生效(group = CommonConstants.PROVIDER)。
当调用的方法是$echo,且入参只有一个,那么该请求便会被EchoFilter拦截,并将入参设置到result对象中,然后将result对象返回至客户端。
自动检测
dubbo没有提供自动回声测试的功能,不过我们可以通过编码自行实现。
- 如果类中引用了远程服务,那么在该类的构造方法中调用回声测试,该方式可以在客户端启动时使用,如果测试失败,可以终止客户端的启动;
- 将每个引用的服务注册到一个服务集合中,启动定时任务进行回声测试,以监控服务是否正常;
不过我觉得回声测试的功能用在监控场景下比较有意义,因为客户端启动时,可以设置自动检测是否有可用的服务。