官网描述
在有注册中心,需要注册提供者地址的情况下,ServiceConfig 解析出的 URL 的格式为: registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode(“dubbo://service-host/com.foo.FooService?version=1.0.0”),
基于扩展点自适应机制,通过 URL 的 registry:// 协议头识别,就会调用 RegistryProtocol 的 export() 方法,将 export 参数中的提供者 URL,先注册到注册中心。
再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocol 的 export() 方法,打开服务端口。
源码分析
Dubbo入口DubboNamespaceHandler.init() 解析xml配置
this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
ServiceBean
实现接口ApplicationContextAware,在setApplicationContext方法中通过反射将当前类addApplicationListener添加到Spring监听器中
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
...
Method method = applicationContext.getClass().getMethod("addApplicationListener", ApplicationListener.class);
method.invoke(applicationContext, this); // 将自己添加到spring监听器中
this.supportedApplicationListener = true;
...
}
ServiceBean实现了ApplicationListener接口,通过onApplicationEvent暴露服务
public void onApplicationEvent(ContextRefreshedEvent event) {
if (this.isDelay() && !this.isExported() && !this.isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + this.getInterface());
}
this.export();
}
}
export方法调用ServiceConfig的doExport方法,然后调用doExportUrls方法
private void doExportUrls() {
// 获取所有的注册中心地址
List<URL> registryURLs = this.loadRegistries(true);
Iterator i$ = this.protocols.iterator();
// 逐个暴露
while(i$.hasNext()) {
ProtocolConfig protocolConfig = (ProtocolConfig)i$.next();
this.doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
方法内会先调用this.exportLocal(var22); 进行本地服务暴露
private void exportLocal(URL url) {
if(!"injvm".equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString()).setProtocol("injvm").setHost("127.0.0.1").setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(this.getServiceClass(this.ref));
Exporter exporter = protocol.export(proxyFactory.getInvoker(this.ref, this.interfaceClass, local));
this.exporters.add(exporter);
logger.info("Export dubbo service " + this.interfaceClass.getName() + " to local registry");
}
}
方法内根据配置拼装url
url示例 dubbo://10.9.167.208:10089/com.zgjiuan.api.service.AuditDTOService?accepts=500&anyhost=true&application=dubbo-service&bind.ip=10.9.167.208&bind.port=10089&default.delay=-1&default.retries=0&default.timeout=9000&default.version=test&delay=-1&dubbo=2.0.2&generic=false&interface=com.zgjiuan.api.service.AuditDTOService&methods=add,listIds,audit,get,count,update,list,delete,alter&pid=56452&qos.enable=false&revision=1.0-SNAPSHOT&side=provider×tamp=1604654507865
// 通过url构建invoker
Invoker<?> invoker = proxyFactory.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
invoker本质就是对url转化成对象存储,可以执行调用的一个对象
断点追踪,来到了RegistryProtocol的export方法
public <T> Exporter<T> export(Invoker<T> originInvoker) throws RpcException {
// 执行暴露
RegistryProtocol.ExporterChangeableWrapper<T> exporter = this.doLocalExport(originInvoker);
URL registryUrl = this.getRegistryUrl(originInvoker);
Registry registry = this.getRegistry(originInvoker);
URL registeredProviderUrl = this.getRegisteredProviderUrl(originInvoker);
boolean register = registeredProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
if (register) {
this.register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
URL overrideSubscribeUrl = this.getSubscribedOverrideUrl(registeredProviderUrl);
RegistryProtocol.OverrideListener overrideSubscribeListener = new RegistryProtocol.OverrideListener(overrideSubscribeUrl, originInvoker);
this.overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 订阅服务变更 overrideSubscribeListener是变化通知类
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
return new RegistryProtocol.DestroyableExporter(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
private <T> RegistryProtocol.ExporterChangeableWrapper<T> doLocalExport(Invoker<T> originInvoker) {
String key = this.getCacheKey(originInvoker);
RegistryProtocol.ExporterChangeableWrapper<T> exporter = (RegistryProtocol.ExporterChangeableWrapper)this.bounds.get(key);
if (exporter == null) {
synchronized(this.bounds) {
exporter = (RegistryProtocol.ExporterChangeableWrapper)this.bounds.get(key);
if (exporter == null) {
Invoker<?> invokerDelegete = new RegistryProtocol.InvokerDelegete(originInvoker, this.getProviderUrl(originInvoker));
// 由于使用dubbo协议 所以会执行DubboProtocol.export方法
exporter = new RegistryProtocol.ExporterChangeableWrapper(this.protocol.export(invokerDelegete), originInvoker);
this.bounds.put(key, exporter);
}
}
}
// 返回一个封装的对象
return exporter;
}
exporter对象封装完成之后,调用 this.register(registryUrl, registeredProviderUrl);
使用zk客户端创建了临时节点
/dubbo/com.zgjiuan.api.service.AuditDTOService/providers/dubbo%3A%2F%2F10.9.167.208%3A10089%2Fcom.zgjiuan.api.service.AuditDTOService%3Faccepts%3D500%26anyhost%3Dtrue%26application%3Ddubbo-service%26default.delay%3D-1%26default.retries%3D0%26default.timeout%3D9000%26default.version%3Dtest%26delay%3D-1%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.zgjiuan.api.service.AuditDTOService%26methods%3Dadd%2ClistIds%2Caudit%2Cget%2Ccount%2Cupdate%2Clist%2Cdelete%2Calter%26pid%3D388%26revision%3D1.0-SNAPSHOT%26side%3Dprovider%26timestamp%3D1604658114263
protected void doRegister(URL url) {
try {
this.zkClient.create(this.toUrlPath(url), url.getParameter("dynamic", true));
} catch (Throwable var3) {
throw new RpcException("Failed to register " + url + " to zookeeper " + this.getUrl() + ", cause: " + var3.getMessage(), var3);
}
}
export完成后,开始订阅服务变化 registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
调用zkClient创建永久节点/dubbo/com.zgjiuan.api.service.AuditDTOService/configurators
export方法会根据不同的协议,调用对应的方法 示例dubbo协议,所以进入DubboProtocol.export方法
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
String key = serviceKey(url); // 接口全类名:版本:端口号
DubboExporter<T> exporter = new DubboExporter(invoker, key, this.exporterMap);
this.exporterMap.put(key, exporter);
Boolean isStubSupportEvent = url.getParameter("dubbo.stub.event", false);
Boolean isCallbackservice = url.getParameter("is_callback_service", false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter("dubbo.stub.event.methods");
if (stubServiceMethods != null && stubServiceMethods.length() != 0) {
this.stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
} else if (this.logger.isWarnEnabled()) {
this.logger.warn(new IllegalStateException("consumer [" + url.getParameter("interface") + "], has set stubproxy support event ,but no stub methods founded."));
}
}
// 开启Netty服务
this.openServer(url);
this.optimizeSerialization(url);
return exporter;
}
// 使用map存储server 如果不存在则创建服务
private void openServer(URL url) {
String key = url.getAddress();
boolean isServer = url.getParameter("isserver", true);
if (isServer) {
ExchangeServer server = (ExchangeServer)this.serverMap.get(key);
if (server == null) {
this.serverMap.put(key, this.createServer(url));
} else {
server.reset(url);
}
}
}
// 创建netty服务
private ExchangeServer createServer(URL url) {
url = url.addParameterIfAbsent("channel.readonly.sent", Boolean.TRUE.toString());
url = url.addParameterIfAbsent("heartbeat", String.valueOf(60000));
String str = url.getParameter("server", "netty");
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
} else {
url = url.addParameter("codec", "dubbo");
ExchangeServer server;
try {
// url对象包含了协议 地址 端口 path等信息
server = Exchangers.bind(url, this.requestHandler);
} catch (RemotingException var5) {
throw new RpcException("Fail to start server(url: " + url + ") " + var5.getMessage(), var5);
}
str = url.getParameter("client");
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
}
官网时序图
总结
服务暴露过程,就是先解析配置文件,生成URL,现在本地使用injvm协议暴露,然后生成invoker对象,在转化exporter对象,整个过程涉及了封装url,创建netty服务,通过zkClient在zkServer上创建节点,并订阅节点变化。