Dubbo服务发布原理

目录

ServiceBean中服务暴露过程

afterPropertiesSet

onApplicationEvent

export

Invoker分析


在spring.handlers文件中可以看到DubboNamespaceHandler的位置

因为要看服务发布,所以直接看service标签对应的类ServiceBean。

ServiceBean 这个类,分别实现了 InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener,BeanNameAware, ApplicationEventPublisherAware

  • InitializingBean   接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候会执行该方法。被重写的方法为 afterPropertiesSet
  • DisposableBean    被重写的方法为 destroy ,bean 被销毁的时候,spring 容器会自动执行 destory 方法,比如释放资源
  • ApplicationContextAware    实现了这个接口的 bean,当 spring 容器初始化的时候,会自动的将 ApplicationContext 注入进来
  • ApplicationListener    ApplicationEvent 事件监听,spring 容器启动后会发一个事件通知。被重写的方法为: onApplicationEvent ,onApplicationEvent方法传入的对象是 ContextRefreshedEvent。这个对象是当 Spring 的上下文被刷新或者加载完毕的时候触发的。因此服务就是在Spring 的上下文刷新后进行导出操作的
  • BeanNameAware    获得自身初始化时,本身的 bean 的 id 属性,被重写的方法为 setBeanName
  • ApplicationEventPublisherAware    这个是一个异步事件发送器。被重写的方法为 setApplicationEventPublisher ,简单来说,在 spring 里面提供了类似于消息队列的异步事件解耦功能。(典型的观察者模式的应用)

ServiceBean中服务暴露过程

服务的发布逻辑主要是通过ServiceBean中的onApplicationEventafterPropertiesSet实现的。

在初始化 bean 的时候会执行该方法 afterPropertiesSet ,spring 容器启动后会发一个事件通知 onApplicationEvent。

 

afterPropertiesSet

这里就是把 dubbo 中配置的 application 、 registry 、 service 、 protocol 等信息,加载到对应的 config实体中,便于后续的使用

onApplicationEvent

spring 容器启动之后触发,这里面做了两个事情:

  • 判断服务是否已经发布过
  • 如果没有发布,则调用调用 export 进行服务发布的流程,这里就是服务发布的入口。
//监听  spring上下文被刷新或者加载的时候触发
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export(); //导出、发布
        }
    }

export

@Override
    public void export() {
        super.export();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

在发布中,serviceBean重写了export 方法,实现了 一个事件的发布,并调用父类的export方法。

ServiceConfig中的export

public synchronized void export() {
        checkAndUpdateSubConfigs(); //检查或更新配置

        if (!shouldExport()) { //当前的服务是否需要发布 ,  通过配置实现: @Service(export = false)
            return;
        }

        if (shouldDelay()) {// 检查是否需要延时发布,通过配置 @Service(delay = 1000) 实现,单位毫秒
            delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();// 如果没有配置 delay ,则直接调用 export 进行发布
        }
    }

doExport

protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        if (exported) {// 服务是否已经发布过了
            return;
        }
        exported = true;// 设置发布状态

        if (StringUtils.isEmpty(path)) { //path 表示服务路径,默认使用 interfaceName
            path = interfaceName;
        }
        doExportUrls();
    }

doExportUrls

这里做了3件事

  • 记载所有配置的注册中心地址
  • 遍历所有配置的协议,protocols
  • 针对每种协议发布一个对应协议的服务
private void doExportUrls() {
        //(N)加载注册中心,并且声称URL地址
        //URL(来驱动流程的执行)->[  registry://192.168.13.106:2181/org.apache.dubbo.registry.RegsitryService/....]
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            //iterface , version ,group组成的key
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            //存储服务发布的元数据
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

doExportUrlsFor1Protocol

默认为dubbo协议

String name = protocolConfig.getName(); //name =dubbo  -><dubbo:protocol  name="dubbo"/>
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

组装URL

URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
        //TODO 动态配置修改
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

发布服务

String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // 如果是本地发布,则直接调用exportLocal
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);  //TODO
            }
            // export to remote if the config is not local (export to local only when config is local)
            // 发布远程服务
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            。。。。。。。。。。。。。。。。。
for (URL registryURL : registryURLs) { //registryURL: registry://ip:port...
                        //TODO  invoker -> 代理类
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        //MetaData元数据的委托
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //wrapperInvoker : registry:///
                        //Protocol$Adaptive(->做适配)
                        // RegistryProtocol-> getExtension("registry")
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
}

遍历多个注册中心,进行协议的发布:调用 protocol.export(invoker)来发布这个代理,添加到 exporters 集合.

protocol.export

找到protocol 的定义处:private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

发现它是一个自适应扩展点。在方法层面上的自适应扩展,意味着它实现了对于 export 这个方法的适配。意味着这个 Protocol 是一个动态代理类,Protocol$Adaptive

会根据 url 中配置的 protocol name 来实现对应协议的适配

目前发布的 invoker(URL),实际上是一个 registry://协议,所以Protocol$Adaptive,会通过 getExtension(extName)得到一个 RegistryProtoco

RegistryProtocol.export

 RegistryProtocol 是用来实现服务注册的,找到对应源码位置

 //实现服务的注册和发布
    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // registryUrl -> zookeeper://ip:port
        URL registryUrl = getRegistryUrl(originInvoker);
        // providerUrl -> dubbo:// ip:port
        URL providerUrl = getProviderUrl(originInvoker);

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
        //  the same service. Because the subscribed is cached key with the name of the service, it causes the
        //  subscription information to cover.
        // 订阅 override 数据。在 admin 控制台可以针对服务进行治理,比如修改权重,修改路由机制等,当注册中心有此服务的覆盖配置
注册进来时,推送消息给提供者,重新暴露服务
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

        /***********************************/
        //doLocalExport 本质就是去启动一个netty服务
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        //  根据 invoker 中的 url 获取 Registry 实例 : zookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
        // 获取要注册到注册中心的 URL: dubbo://ip:port
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
        ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
                registryUrl, registeredProviderUrl);
        //to judge if we need to delay publish
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {// 是否配置了注册中心,如果是, 则需要注册
            // 注册到注册中心的 URL
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }

        // Deprecated! Subscribe to override rules in 2.6.x or before.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

doLocalExport

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
        String key = getCacheKey(originInvoker);
        //bounds -chm  ->computeIfAbsent  if(map.get(key)==null){map.put()}
        return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
            //orginInvoker->   InvokerDelegate(DelegateProviderMetaDataInvoker(invoker))
            Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
            //protocol.export -> DubboProtocol.export(本质上就是 暴露一个 20880的端口)
            //protocol- >Protocol$Apaptive ->QosProtocolWrapper(ProtocolListenerWrapper(ProtocolFilterWrapper(DubboProtocol(invoker))))
            //将 invoker 转换为 exporter 并启动 netty 服务
            return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
        });
    }

InvokerDelegete: 是 RegistryProtocol 的一个静态内部类,该类是一个 originInvoker 的委托类,该类存储了 originInvoker,其父类 IrWrapper 还会存储 providerUrl,InvokerWrapper 会调用 originInvoker 的 invoke 方法,也会销毁 invoker。可以管理 invoker 的生命周期

根据protocol.export(invokerDelegate)可知是一个方法级别的自适应扩展点。直接定位DubboProtocol.export

DubboProtocol.export

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        // 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如
        //${group}/copm.gupaoedu.practice.dubbo.ISayHelloService:${version}:20880
        String key = serviceKey(url);
        
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        //  将 <key, exporter>  键值对放入缓存中
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        openServer(url); //openServer(url) 开启一个服务 ,暴露20880端口
        optimizeSerialization(url);  //优化序列化

        return exporter;
    }

openServer

开启一个服务,并且放入到缓存中,同一个端口上仅允许启动一个服务器实例

 private void openServer(URL url) {
        //  获取 host:port ,并将其作为服务器实例的 key ,用于标识当前的服务器实例
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(IS_SERVER_KEY, true);
        if (isServer) {// 是否在 serverMap 中缓存了
            //缓存, 一个key只对应一个exchangeServer
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        //  创建服务器实例
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // server supports reset, use together with override
                //服务器已创建,则根据 url  中的配置重置服务器
                server.reset(url);
            }
        }
    }

createServer

private ExchangeServer createServer(URL url) {
        // 组装 url ,在 url 中添加心跳时间、编解码参数
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // enable heartbeat by default
                .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                .addParameter(CODEC_KEY, DubboCodec.NAME)
                .build();
        //获得当前应该采用什么样的方式来发布服务, netty3, netty4, mina , grizzy,
        String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        }
        // 创建 ExchangeServer.
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }

        str = url.getParameter(CLIENT_KEY);
        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;
    }

从Exchangers.bind一路找到NettyServer(url, listener)

Exchangers.bind-->getExchanger(url).bind(url, handler)-->>Transporters.bind-->getTransporter().bind-->NettyServer(url, listener)

NettyServer

public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();
        //  获取 ip  和端口
        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = ANYHOST_VALUE;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
        try {
            doOpen(); //启动服务器
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }

doOpen

这里就是开启一个netty服务,暴露端口号

nettyServerHandler用来处理客户端传递过来的请求。

@Override
    protected void doOpen() throws Throwable {
        bootstrap = new ServerBootstrap();

        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
        workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                new DefaultThreadFactory("NettyServerWorker", true));

        final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
        channels = nettyServerHandler.getChannels();

        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // FIXME: should we use getTimeout()?
                        int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                        NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                        ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                                .addLast("decoder", adapter.getDecoder())
                                .addLast("encoder", adapter.getEncoder())
                                .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                                .addLast("handler", nettyServerHandler);
                    }
                });
        // bind
        ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();

    }

Invoker分析

在之前的分析中,可以看到ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)中会创造一个invoker,并对其进行层层包装。

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

Invoker是Dubbo领域模型中非常重要的一个概念。

根据proxyFactory的定义可以看到这是个自适应扩展点,会生成一个动态适配器。

ProxyFactory proxyFactory =
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

ProxyFactory 它是一个spi扩展点,并且默认的扩展实现是javassit, 这个接口中有三个方法,并且都是加了@Adaptive的自适应扩展点。所以如果调用getInvoker方法,会返回一个ProxyFactory$Adaptive,

在这里通过ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName)获取了一个指定名称的扩展点。

在dubbo-rpc-api/resources/META-INF/com.alibaba.dubbo.rpc.ProxyFactory中,定义了javassis=JavassisProxyFactory,故调用JavassisProxyFactory的getInvoker方法。

@Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        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);
            }
        };
    }

Invoke本质上应该是一个代理,经过层层包装最终进行了发布。当消费者发起请求的时候,会获得这个invoker进行调用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值