与dubbo服务发布相对的,是引用服务进行调用的过程,这个很多步骤都是与服务发布相对的,但是也有特有的地方,比如,负载均衡 ,集群容错等。这篇博客,我们主要关注dubbo服务调用的一个核心过程。
dubbo服务调用的主要过程:将调用信息注册到zk上-> 通知RegistryDirectory刷新可用服务列表->刷新过程中,新服务会与netty服务端建立连接,并封装到DubboInvoker中。-> 选择失败策略通过负载均衡算法,选择服务端具体哪个服务去执行 -> 通过netty返回执行结果。
Dubbo服务调用过程图如下:(看不清,请点击新的页签进行查看)
在ReferenceConfig.init的方法里,会把配置信息封闭成一个map,然后去构建一个代理类。在构建这个代理类,会用DubboProtocol获取DubboInvoker。
private T createProxy(Map<String, String> map) {
// 省略一些代码。。。
// 通过注册中心配置拼装URL
List<URL> us = loadRegistries(false);
if (us != null && us.size() > 0) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.size() == 0) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
if (urls.size() == 1) {
//会调用RegistryProtocol.refer
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
//省略很多代码
// 创建服务代理,使用JavassistProxyFactory去创建
return (T) proxyFactory.getProxy(invoker);
}
在RegistryProtocol里,会创建一个RegistryDirectory,这个对象保存的调用服务的信息,并且也是一个监听。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
//调用zk去真实的注册
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
//最终也是调用zk去订阅监听,监听器是RegistryDirectory
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
return cluster.join(directory);
}
在真正注册到zk上,注册监听器到zk相应路径上,会调用RegistryDirectory的notify通知方法,去获取可用的服务列表。
public synchronized void notify(List<URL> urls) {
List<URL> invokerUrls = new ArrayList<URL>();
List<URL> routerUrls = new ArrayList<URL>();
List<URL> configuratorUrls = new ArrayList<URL>();
//初始化值
//省略代码
// configurators
if (configuratorUrls != null && configuratorUrls.size() > 0) {
this.configurators = toConfigurators(configuratorUrls);
}
// routers
if (routerUrls != null && routerUrls.size() > 0) {
List<Router> routers = toRouters(routerUrls);
if (routers != null) { // null - do nothing
setRouters(routers);
}
}
List<Configurator> localConfigurators = this.configurators; // local reference
// 合并override参数
this.overrideDirectoryUrl = directoryUrl;
if (localConfigurators != null && localConfigurators.size() > 0) {
for (Configurator configurator : localConfigurators) {
this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
}
}
// providers 刷新服务列表
refreshInvoker(invokerUrls);
}
其中调用refershInvoker方法时,会去调用toInvokers把URl列表转换为Invoker列表。
private void refreshInvoker(List<URL> invokerUrls) {
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
this.forbidden = true; // 禁止访问
this.methodInvokerMap = null; // 置空列表
destroyAllInvokers(); // 关闭所有Invoker
} else {
this.forbidden = false; // 允许访问
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<URL>();
this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比
}
if (invokerUrls.size() == 0) {
return;
}
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// 将URL列表转成Invoker列表
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
//省略一些代码
。。。。。。。
}
}
在将这些url转换为invoker时,会使用DubboProtocol去创建与netty服务端的连接。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if (urls == null || urls.size() == 0) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
for (URL providerUrl : urls) {
//封装url信息
//省略一些代码
// 缓存key为没有合并消费端参数的URL,不管消费端如何合并参数,如果服务端URL发生变化,则重新refer
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) { // 缓存中没有,重新refer
try {
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
//服务可以就使用DubboProtocol去创建连接,封装DubboInvoker
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
}
在DubboProtocol.refer里,会去创建netty客户端连接。
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
最后是返回一个DubboInvoker对象,在这之前会调用getClients先去获取或创建客户端连接。是共享连接就获取之前的加接,不是的话就创建新的连接。
private ExchangeClient initClient(URL url) {
//设置一些参数,比如心跳机制等
// client type setting.
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
boolean compatible = (version != null && version.startsWith("1.0."));
url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
//默认开启heartbeat
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
//省略一些代码
ExchangeClient client;
try {
//设置连接应该是lazy的
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
//跟服务发布相对,具体调用HeaderExchanger.connect
client = Exchangers.connect(url, requestHandler);
}
在HeaderExchanger里,也是通过具体的NettyTransports连接去创建一个NettyClient,在doOpen方法里,创建netty连接,熟悉netty的应该对这段代码不陌生
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
bootstrap = new ClientBootstrap(channelFactory);
// config
// @see org.jboss.netty.channel.socket.SocketChannelConfig
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("connectTimeoutMillis", getTimeout());
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
//使用适配,用DubboCodec去编码解码
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
//工作线程的处理handler
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
}
在RegistryDirectory中,缓存了可用的服务列表Invoker,之后具体使用哪个服务去调用,就看选择的负载均衡策略了。在
RegistryProtocol.doRefer里,会去执行“cluster.join(directory)”,这里的cluster看是使用哪种失败策略。默认会调用FailoverCluster.join。在FailoverClusterInvoker.doInvoke里,会去选择具体要调用哪参机器上的哪个服务。
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//重试时,进行重新选择,避免重试时invoker列表已发生变化.
//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
if (i > 0) {
checkWhetherDestroyed();
copyinvokers = list(invocation);
//重新检查一下
checkInvokers(copyinvokers, invocation);
}
//使用loadbalance负载均衡选择哪个服务invoker去调用
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
//调用执行调用链
Result result = invoker.invoke(invocation);
//省略一些代码。。。。
最后真正调用服务端的DubboProtocol.requestHandler.reply去处理,
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
//从DubboExporter里获取Invoker
Invoker<?> invoker = getInvoker(channel, inv);
//如果是callback 需要处理高版本调用低版本的问题
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
//callback的处理,省略。。。。
}
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
//反射调用,真正客户端方法调用的地方
return invoker.invoke(inv);
}
处理结果再通过netty返回到客户端resutlt中。
到此,一个服务调用过程就基本完成了。与之前的dubbo 源码学习笔记 (二) —— dubbo发布服务的过程相对应,形成dubbo服务发布调用的整个过程。这里总结一下整体过程:
dubbo工作原理分为服务发布和服务引用两个核心过程。
1、服务发布的时候,DubboProtocol将调用链封装为DubboExporter,放入到netty服务端工作线程池中
2、URL配置信息注册到zk注册中心,并注册override监听,触发订阅。
3、服务引用时,也将服务引用的信息封装成URL并注册到zk注册中心,同时监听category、providers、configurators、routers这四个目录节点(才能感知服务上下线的变化)。将这些信息包装成DubboInvoker客户端的调用链,返回代理。
4、客户端使用代理进行调用时,经过负载均衡,选择其中一个服务的URL,根据URl信息与netty建立连接,发送Invocation到netty服务端;服务端在工作线程池中找一个线程,处理Invocation,并把RpcResult结果返回给客户端;客户端接收解析RpcResult,获取处理结果。
这样整个过程就基本结束。理解这一整个过程,也就相当于了解了dubbo整体的原理。也就能从大的方向把握这一框架,当然,dubbo框架还有很多值得学习研究的地方。在之后的博客中会继续分析。