Dubbo原理学习(一)

本文详细探讨了Dubbo服务提供者如何通过ServiceBean暴露服务的过程,包括从@Service注解到服务注册、远程服务暴露、Netty服务器启动、Zookeeper连接和注册等步骤。分析了ServiceBean的生命周期方法及其实现,揭示了Dubbo服务从初始化到发布的内部机制。
摘要由CSDN通过智能技术生成

Dubbo简介

项目中使用dubbo作为rpc框架,而且以前一直也是有过使用经验,但是没有深入学习过,近期项目进度不紧,就简单学习一下深入原理。
dubbo官方文档:http://dubbo.apache.org/zh-cn/docs/user/quick-start.html
开头开始一张众所周知的dubbo原理图:在这里插入图片描述
节点角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

Provider

我们在写dubbo服务的时候,通常在实现类上面加上com.alibaba.dubbo.config.annotation的@Service注解,这就意味着是一个服务提供者,但是这个过程是怎样实现的呢?
早期的dubbo是通过spring的配置xml文件来实现,现在主要是使用注解,不过都是一个意思,标记为spring管理的bean。在这里插入图片描述
标签的处理类dubbo中的定义写在META-INF/spring.handlers

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

打开DubboNamespaceHandler可以看到:
在这里插入图片描述
可以看到dubbo自定义了一个DubboBeanDefinitionParser类去解析上面的标签,并且自定义了AnnotationBean,ServiceBean和ReferenceBean。

在服务提供方这节先研究一下ServiceBean,通过dubbo的启动日志,可以大概了解到服务发现的原理:

大概有如下操作:
在这里插入图片描述

第一个发布的动作:暴露本地服务

Export dubbo service com.alibaba.dubbo.demo.DemoService to local registry, dubbo version: 2.0.0, current host: 127.0.0.1

第二个发布动作:暴露远程服务

Export dubbo service com.alibaba.dubbo.demo.DemoService to url dubbo://192.168.100.38:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=8484&side=provider&timestamp=1473908495465, dubbo version: 2.0.0, current host: 127.0.0.1
Register dubbo service com.alibaba.dubbo.demo.DemoService url dubbo://192.168.100.38:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&monitor=dubbo%3A%2F%2F192.168.48.117%3A2181%2Fcom.alibaba.dubbo.registry.RegistryService%3Fapplication%3Ddemo-provider%26backup%3D192.168.48.120%3A2181%2C192.168.48.123%3A2181%26dubbo%3D2.0.0%26owner%3Dwilliam%26pid%3D8484%26protocol%3Dregistry%26refer%3Ddubbo%253D2.0.0%2526interface%253Dcom.alibaba.dubbo.monitor.MonitorService%2526pid%253D8484%2526timestamp%253D1473908495729%26registry%3Dzookeeper%26timestamp%3D1473908495398&owner=william&pid=8484&side=provider&timestamp=1473908495465 to registry registry://192.168.48.117:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&backup=192.168.48.120:2181,192.168.48.123:2181&dubbo=2.0.0&owner=william&pid=8484&registry=zookeeper&timestamp=1473908495398, dubbo version: 2.0.0, current host: 127.0.0.1

第三个发布动作:启动netty

Start NettyServer bind /0.0.0.0:20880, export /192.168.100.38:20880, dubbo version: 2.0.0, current host: 127.0.0.1

第四个发布动作:打开连接zk

INFO zookeeper.ClientCnxn: Opening socket connection to server /192.168.48.117:2181

第五个发布动作:到zk注册

Register: dubbo://192.168.100.38:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=8484&side=provider&timestamp=1473908495465, dubbo version: 2.0.0, current host: 127.0.0.1

第六个发布动作;监听zk

Subscribe: provider://192.168.100.38:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=8484&side=provider&timestamp=1473908495465, dubbo version: 2.0.0, current host: 127.0.0.1
Notify urls for subscribe url provider://192.168.100.38:20880/com.alibaba.dubbo.d

那么,ServiceBean是何时暴露服务的呢?
查看ServiceBean源码可以发现:

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
    private static final long serialVersionUID = 213195494150089726L;
    private static transient ApplicationContext SPRING_CONTEXT;
    private transient ApplicationContext applicationContext;
    private transient String beanName;
    private transient boolean supportedApplicationListener;

    public ServiceBean() {
    }

    public ServiceBean(Service service) {
        super(service);
    }
    //......
  }

ServiceBean里面实现了ApplicationListener,ApplicationContextAware,InitializingBean接口,这3个接口分别会在spring启动的不同时机依次调用:
自己写了一个简单的方法验证一下顺序:
在这里插入图片描述
在这里插入图片描述
所以依次去看一下里面都进行了哪些操作:

public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
        if (applicationContext != null) {
            SPRING_CONTEXT = applicationContext;

            try {
                Method method = applicationContext.getClass().getMethod("addApplicationListener", ApplicationListener.class);
                method.invoke(applicationContext, this);
                this.supportedApplicationListener = true;
            } catch (Throwable var5) {
                if (applicationContext instanceof AbstractApplicationContext) {
                    try {
                        Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", ApplicationListener.class);
                        if (!method.isAccessible()) {
                            method.setAccessible(true);
                        }

                        method.invoke(applicationContext, this);
                        this.supportedApplicationListener = true;
                    } catch (Throwable var4) {
                    }
                }
            }
        }

    }
//......
 public void afterPropertiesSet() throws Exception {
        this.setPosition(PriorityGroupUtil.getInnerPosition());
        Map protocolConfigMap;
        if (this.getProvider() == null) {
            protocolConfigMap = this.applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, ProviderConfig.class, false, false);
            if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
                Map<String, ProtocolConfig> protocolConfigMap = this.applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, ProtocolConfig.class, false, false);
                Iterator i$;
                ProviderConfig config;
                if ((protocolConfigMap == null || protocolConfigMap.size() == 0) && protocolConfigMap.size() > 1) {
                    List<ProviderConfig> providerConfigs = new ArrayList();
                    i$ = protocolConfigMap.values().iterator();

                    while(i$.hasNext()) {
                        config = (ProviderConfig)i$.next();
                        if (config.isDefault() != null && config.isDefault()) {
                            providerConfigs.add(config);
                        }
                    }

                    if (providerConfigs.size() > 0) {
                        this.setProviders(providerConfigs);
                    }
                } else {
                    ProviderConfig providerConfig = null;
                    i$ = protocolConfigMap.values().iterator();

                    label318:
                    while(true) {
                        do {
                            if (!i$.hasNext()) {
                                if (providerConfig != null) {
                                    this.setProvider(providerConfig);
                                }
                                break label318;
                            }
                        }
                            
                            //.....
    public void onApplicationEvent(ApplicationEvent event) {
        if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName()) && this.isDelay() && !this.isExported() && !this.isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + this.getInterface());
            }

            this.export();
        }

    }

前面基本上都是一些初始化操作,只有在最后的地方,调用了一个export方法。
跟进查看:
在这里插入图片描述
在这里插入图片描述
还是在不断的初始化一些配置,还有一些校验操作。在后面可以看到一个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);
        }

    }

loadRegistries():这里主要是注册url的一个拼接
URL格式类似于:registry://192.123.1.101:2181/com.alibaba.dubbo.registry.。。。

protected List<URL> loadRegistries(boolean provider) {
        this.checkRegistry();
        List<URL> registryList = new ArrayList();
        if (this.registries != null && this.registries.size() > 0) {
            Iterator i$ = this.registries.iterator();

            label75:
            while(true) {
                RegistryConfig config;
                String address;
                do {
                    do {
                        do {
                            if (!i$.hasNext()) {
                                return registryList;
                            }

                            config = (RegistryConfig)i$.next();
                            address = config.getAddress();
                            if (address == null || address.length() == 0) {
                                address = "0.0.0.0";
                            }

                            String sysaddress = System.getProperty("dubbo.registry.address");
                            if (sysaddress != null && sysaddress.length() > 0) {
                                address = sysaddress;
                            }
                        } while(address == null);
                    } while(address.length() <= 0);
                } while("N/A".equalsIgnoreCase(address));

                Map<String, String> map = new HashMap();
                appendParameters(map, this.application);
                appendParameters(map, config);
                map.put("path", RegistryService.class.getName());
                map.put("dubbo", Version.getVersion());
                map.put("timestamp", String.valueOf(System.currentTimeMillis()));
                if (ConfigUtils.getPid() > 0) {
                    map.put("pid", String.valueOf(ConfigUtils.getPid()));
                }

                if (!map.containsKey("protocol")) {
                    if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                        map.put("protocol", "remote");
                    } else {
                        map.put("protocol", "dubbo");
                    }
                }

                List<URL> urls = UrlUtils.parseURLs(address, map);
                Iterator i$ = urls.iterator();

                while(true) {
                    URL url;
                    do {
                        if (!i$.hasNext()) {
                            continue label75;
                        }

                        url = (URL)i$.next();
                        url = url.addParameter("registry", url.getProtocol());
                        url = url.setProtocol("registry");
                    } while((!provider || !url.getParameter("register", true)) && (provider || !url.getParameter("subscribe", true)));

                    registryList.add(url);
                }
            }
        } else {
            return registryList;
        }
    }

然后再遍历protocols变量,将protocols列表中的每个protocol根据url暴露出去,主要是doExportUrlsFor1Protocol方法。
在这里插入图片描述

这个方法是把上面的URL转成dubbo协议的URL:
url地址包含了版本号,接口名,方法列表,序列化方法,过期时间等这个接口bean所有需要用到的上下文信息,并且地址头也由registry改成了dubbo。

接下来就可以暴露本地接口:

    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");
        }

    }

此时可以看到:

Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(this.ref, this.interfaceClass, local));

在这里插入图片描述
默认使用的是javassistProxyFactory,所以跟进去看一下:

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(36) < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

目前自己学习只有到这里,后续还有待深入学习。

参考:
https://blog.csdn.net/jyxmust/article/details/82669722

ServiceBean.onApplicationEvent
-->export()
  -->ServiceConfig.export()
    -->doExport()
      -->doExportUrls()//里面有一个for循环,代表了一个服务可以有多个通信协议,例如 tcp协议 http协议,默认是tcp协议
        -->loadRegistries(true)//从dubbo.properties里面组装registry的url信息
        -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) 
          //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
          -->exportLocal(URL url)
            -->proxyFactory.getInvoker(ref, (Class) interfaceClass, local)
              -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
              -->extension.getInvoker(arg0, arg1, arg2)
                -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url) 
                  -->proxyFactory.getInvoker(proxy, type, url)
                    -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
                      -->Wrapper.getWrapper(com.alibaba.dubbo.demo.provider.DemoServiceImpl)
                        -->makeWrapper(Class<?> c)
                      -->return new AbstractProxyInvoker<T>(proxy, type, url)
            -->protocol.export
              -->Protocol$Adpative.export
                -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("injvm");
                -->extension.export(arg0)
                  -->ProtocolFilterWrapper.export
                    -->buildInvokerChain //创建8个filter
                    -->ProtocolListenerWrapper.export
                      -->InjvmProtocol.export
                        -->return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap)
                        -->目的:exporterMap.put(key, this)//key=com.alibaba.dubbo.demo.DemoService, this=InjvmExporter
          //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)
          -->proxyFactory.getInvoker//原理和本地暴露一样都是为了获取一个Invoker对象
          -->protocol.export(invoker)
            -->Protocol$Adpative.export
              -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("registry");
	            -->extension.export(arg0)
	              -->ProtocolFilterWrapper.export
	                -->ProtocolListenerWrapper.export
	                  -->RegistryProtocol.export
	                    -->doLocalExport(originInvoker)
	                      -->getCacheKey(originInvoker);//读取 dubbo://192.168.100.51:20880/
	                      -->rotocol.export
	                        -->Protocol$Adpative.export
	                          -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("dubbo");
	                          -->extension.export(arg0)
	                            -->ProtocolFilterWrapper.export
	                              -->buildInvokerChain//创建8个filter
	                              -->ProtocolListenerWrapper.export
---------1.netty服务暴露的开始-------    -->DubboProtocol.export
	                                  -->serviceKey(url)//组装key=com.alibaba.dubbo.demo.DemoService:20880
	                                  -->目的:exporterMap.put(key, this)//key=com.alibaba.dubbo.demo.DemoService:20880, this=DubboExporter
	                                  -->openServer(url)
	                                    -->createServer(url)
--------2.信息交换层 exchanger 开始-------------->Exchangers.bind(url, requestHandler)//exchaanger是一个信息交换层
	                                        -->getExchanger(url)
	                                          -->getExchanger(type)
	                                            -->ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension("header")
	                                        -->HeaderExchanger.bind
	                                          -->Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
	                                            -->new HeaderExchangeHandler(handler)//this.handler = handler
	                                            -->new DecodeHandler
	                                            	-->new AbstractChannelHandlerDelegate//this.handler = handler;
---------3.网络传输层 transporter--------------------->Transporters.bind
	                                              -->getTransporter()
	                                                -->ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension()
	                                              -->Transporter$Adpative.bind
	                                                -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension("netty");
	                                                -->extension.bind(arg0, arg1)
	                                                  -->NettyTransporter.bind
	                                                    --new NettyServer(url, listener)
	                                                      -->AbstractPeer //this.url = url;    this.handler = handler;
	                                                      -->AbstractEndpoint//codec  timeout=1000  connectTimeout=3000
	                                                      -->AbstractServer //bindAddress accepts=0 idleTimeout=600000
---------4.打开断开,暴露netty服务-------------------------------->doOpen()
	                                                        -->设置 NioServerSocketChannelFactory boss worker的线程池 线程个数为3
	                                                        -->设置编解码 hander
	                                                        -->bootstrap.bind(getBindAddress())
	                                            -->new HeaderExchangeServer
	                                              -->this.server=NettyServer
	                                              -->heartbeat=60000
	                                              -->heartbeatTimeout=180000
	                                              -->startHeatbeatTimer()//这是一个心跳定时器,采用了线程池,如果断开就心跳重连。

	                    -->getRegistry(originInvoker)//zk 连接
	                      -->registryFactory.getRegistry(registryUrl)
	                        -->ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension("zookeeper");
	                        -->extension.getRegistry(arg0)
	                          -->AbstractRegistryFactory.getRegistry//创建一个注册中心,存储在REGISTRIES
	                            -->createRegistry(url)
	                              -->new ZookeeperRegistry(url, zookeeperTransporter)
	                                -->AbstractRegistry
	                                  -->loadProperties()//目的:把C:\Users\bobo\.dubbo\dubbo-registry-192.168.48.117.cache
	                                                                                                                                                                    文件中的内容加载为properties
	                                  -->notify(url.getBackupUrls())//不做任何事             
	                                -->FailbackRegistry   
	                                  -->retryExecutor.scheduleWithFixedDelay(new Runnable()//建立线程池,检测并连接注册中心,如果失败了就重连
	                                -->ZookeeperRegistry
	                                  -->zookeeperTransporter.connect(url)
	                                    -->ZookeeperTransporter$Adpative.connect(url)
	                                      -->ExtensionLoader.getExtensionLoader(ZookeeperTransporter.class).getExtension("zkclient");
	                                      -->extension.connect(arg0)
	                                        -->ZkclientZookeeperTransporter.connect
	                                          -->new ZkclientZookeeperClient(url)
	                                            -->AbstractZookeeperClient
	                                            -->ZkclientZookeeperClient
	                                              -->new ZkClient(url.getBackupAddress());//连接ZK
	                                              -->client.subscribeStateChanges(new IZkStateListener()//订阅的目标:连接断开,重连
	                                    -->zkClient.addStateListener(new StateListener() 
	                                      -->recover //连接失败 重连
	                                      
	                    -->registry.register(registedProviderUrl)//创建节点
	                      -->AbstractRegistry.register
	                      -->FailbackRegistry.register
	                        -->doRegister(url)//向zk服务器端发送注册请求
	                          -->ZookeeperRegistry.doRegister
	                            -->zkClient.create
	                              -->AbstractZookeeperClient.create//dubbo/com.alibaba.dubbo.demo.DemoService/providers/
										                              dubbo%3A%2F%2F192.168.100.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26
										                              application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3D
										                              com.alibaba.dubbo.demo.DemoService%26loadbalance%3Droundrobin%26methods%3DsayHello%26owner%3
										                              Dwilliam%26pid%3D2416%26side%3Dprovider%26timestamp%3D1474276306353
	                                -->createEphemeral(path);//临时节点  dubbo%3A%2F%2F192.168.100.52%3A20880%2F.............
	                                -->createPersistent(path);//持久化节点 dubbo/com.alibaba.dubbo.demo.DemoService/providers
	                                    
	                                    
	                    -->registry.subscribe//订阅ZK
	                      -->AbstractRegistry.subscribe
	                      -->FailbackRegistry.subscribe
	                        -->doSubscribe(url, listener)// 向服务器端发送订阅请求
	                          -->ZookeeperRegistry.doSubscribe
	                            -->new ChildListener()
	                              -->实现了 childChanged
	                                -->实现并执行 ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
	                              //A
	                            -->zkClient.create(path, false);//第一步:先创建持久化节点/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
	                            -->zkClient.addChildListener(path, zkListener)
	                              -->AbstractZookeeperClient.addChildListener
	                                //C
	                                -->createTargetChildListener(path, listener)//第三步:收到订阅后的处理,交给FailbackRegistry.notify处理
	                                  -->ZkclientZookeeperClient.createTargetChildListener
	                                    -->new IZkChildListener() 
	                                      -->实现了 handleChildChange //收到订阅后的处理
	                                      	-->listener.childChanged(parentPath, currentChilds);
	                                      	-->实现并执行ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
	                                      	-->收到订阅后处理 FailbackRegistry.notify
	                                //B      	
	                                -->addTargetChildListener(path, targetListener)第二步
	                                  -->ZkclientZookeeperClient.addTargetChildListener
	                                    -->client.subscribeChildChanges(path, listener)//第二步:启动加入订阅/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
	                    
	                    -->notify(url, listener, urls)
	                      -->FailbackRegistry.notify
	                        -->doNotify(url, listener, urls);
	                          -->AbstractRegistry.notify
	                            -->saveProperties(url);//把服务端的注册url信息更新到C:\Users\bobo\.dubbo\dubbo-registry-192.168.48.117.cache
	                              -->registryCacheExecutor.execute(new SaveProperties(version));//采用线程池来处理
	                            -->listener.notify(categoryList)
	                              -->RegistryProtocol.notify
	                                -->RegistryProtocol.this.getProviderUrl(originInvoker)//通过invoker的url 获取 providerUrl的地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值