作者:黄金
一、Dubbo简介
Dubbo是一款典型的高扩大、高性能、高可用的RPC微服务框架,用于解决微服务架构下的服务治理与通信问题。其外围模块蕴含 【RPC通信】 和 【服务治理】 ,其中服务治理又分为服务注册与发现、服务容错、负载平衡、流量调度等。明天将重点介绍Dubbo的服务注册与发现。
二、SPI机制
在介绍服务注册发现之前,先简略介绍一下贯通整个Dubbo源码,也是Dubbo实现自适应扩大的外围--SPI机制,下图为Dubbo SPI实现的简略类图。
•1、Dubbo SPI原理:通过读取相应的配置文件找到具体实现类,而后通过以下两种形式实例化对象:(1)通过自适应的动静字节码编译技术,生成相应的动静代理类,(2)利用反射机制实现实例化。相较于Java SPI,Dubbo SPI实现了外部的IoC和Aop
•2、Dubbo SPI 长处:(1)高扩大:用户能够依据理论业务需要扩大相应的实现模块,蕴含字节码编译技术、rpc协定、通信形式、注册形式等,(2)解耦: 通过封装SPI调用机制,架构上实现了下层利用与底层逻辑之间的解耦,为高扩大提供了撑持条件
•3、Dubbo SPI 罕用样例(以getExtension和getAdaptiveExtension为例)
配置文件内容test1=com.dubbo.demo.service.TestServiceimpltest2=com.dubbo.demo.service.TestServiceImpl2一、通过getExtension办法生成实例 ExtensionLoader<TestService> extensionLoader = ExtensionLoader.getExtensionLoader(TestService.class); TestService t1 = extensionLoader.getExtension("test1"); TestService t2 = extensionLoader.getExtension("test2"); 二、通过getAdaptiveExtension生成实例(办法中须要@Adaptive注解,参数会对URL校验) TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension(); URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"}); testService.sayHello("bbb", url);
调用getAdaptiveExtension办法最终会生成相应的代理类,最终生成的代理类会依据URL参数外面的protocol决定(以外部Protocol为例)
三、服务注册
1、服务注册流程
2、服务注册类图详解
3、服务注册步骤
(1)步骤一:初始化配置(类图:形象Config与初始化配置)
首先须要实例化ServiceConfig实例,申明“注册接口、接口实例、注册核心配置”,其中“ServiceBean”是实现Spring与Dubbo整合的桥梁。而后会由DubboBootstrap调用initialize办法实现configManager和Environment的初始化,其中就包含将ServiceConfig中的配置转换成外部封装的协定(ApplicationModel、ProviderModel等)
private static void startWithExport() throws InterruptedException { //初始化配置 ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>(); service.setInterface(DemoService.class); service.setRef(new DemoServiceImpl()); service.setApplication(new ApplicationConfig("dubbo-demo-api-provider")); service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); //服务注册入口 service.export();}
public synchronized void export() { if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); // compatible with api call. if (null != this.getRegistry()) { bootstrap.registries(this.getRegistries()); } //初始化配置() bootstrap.initialize(); } ...... if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); } else { //服务注册 doExport(); } exported(); }
(2)步骤二:组装URL
依据初始化配置组转注册接口服务的URL。其中URL也是Dubbo外部通过@Adaptive注解实现SPI的外围,通过批改URL的头部协定(如:register、dubbo、injvm等),在调用
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();PROTOCOL.export(wrapperInvoker)
该办法的时候,会依据不同的协定切换不通的实现类,实现了Dubbo技术架构与业务逻辑的解耦。
private void doExportUrls() { //组装后的URL格局样例 //registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212®istry=zookeeper×tamp=1663049763199 List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); int protocolConfigNum = protocols.size(); for (ProtocolConfig protocolConfig : protocols) { //组装pathKey : org.apache.dubbo.demo.DemoService String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); //保留接口服务 repository.registerService(pathKey, interfaceClass); //服务注册 doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum); }}
(3)步骤三:Invoker封装(类图:Ref -> Invoker)
通过内置的动静字节码编译(默认javassist)生成Invoker代理类,而后通过反射机制生成Wrapper实例。其中Invoker是Dubbo的外围模型,Invoker是Dubbo中的实体域,也就是实在存在的。其余模型都向它聚拢或转换成它
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) { ...... //组装新的URL //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562 URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); ...... //Invoker封装 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); //wrapper DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //服务注册(此时URL头部协定变成了register,理论会调用RegistryProtocol) Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); exporters.add(exporter);}# PROXY_FACTORYpublic <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // 动静代理类生成,反射生成实例 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } };}
(4)步骤四:Exporter封装(类图:Invoker-> Exporter)
此时会顺次调用RegistryProtocol 、DubboProtocol 将Invoker封装成Exporter,并将封装后的Exporter存储到本地map中(相似于spring bean)。而后会调用底层通信服务(默认netty)进行端口监听,此时会通过责任链模式封装Exchanger与Transporter,用于解决网络传输音讯的编码/解码。
# RegistryProtocol : exportpublic <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { ...... //此时URL头部协定已变成dubbo //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); // export invoker final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // 此时Registry实例默认是ZookeeperRegistry final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //底层调用ZK,创立node节点 registry.register(registeredProviderUrl); } ....}# RegistryProtocol : doLocalExportprivate <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); //此时会调用DubboProtocol进行exporter封装 return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); });}
# DubboProtocol : exportpublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { ...... // export service. String key = serviceKey(url); //exporter封装 DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); exporterMap.put(key, exporter); ...... //开启服务监听 openServer(url); optimizeSerialization(url); return exporter;}
(5)步骤五:注册服务节点
封装Exporter并开启服务端口监听后,会调用注册核心(默认Zookeeper)注册服务节点信息
# RegistryProtocol : exportpublic <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { ...... //此时URL头部协定已变成dubbo //dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098 providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); // export invoker final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // 此时Registry实例默认是ZookeeperRegistry final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //底层调用ZK,创立node节点 registry.register(registeredProviderUrl); } ....}
四、总结
至此,Dubbo服务注册的整体流程已大抵完结,文中如有不当或者错误观点,欢送大家评论区指出。感兴趣的同学,能够关注后续“Dubbo架构设计与源码解析”系列的文章。