关闭

dubbo暴露服务过程

2492人阅读 评论(0) 收藏 举报
分类:

一般一个服务有可能即是服务端又是消费端。服务启动的时候会去像注册中心(一般是zk)暴露或者订阅自己的或者自己需要的服务。我们来看下dubbo是如何把本地服务注册到注册中心的。

我们来看下ServiceBean.java这个类。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean,
DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware

ServiceBean实现了Spring提供的接口InitializingBean,当在完成Bean初始化和注入的时候程序会自动执行InitializingBean接口中的afterPropertiesSet方法。

在这个方法中,会对dubbo的配置做初始化赋值,即我们在配置文件中配置的:

provider,application,moduleregistriesmonitorprotocolspathbeanName

在方法的最后会调用父类ServiceConfig中的export方法。

if (! isDelay()) { // 这里配置了delay会返回false,逻辑有点怪
    export();
}

export方法判断是否延迟注册,延迟注册使用新的守护线程去执行暴露服务,否则直接暴露服务

public synchronized void export() {
    if (provider != null) {
        if (export == null) {
            export = provider.getExport();
        }
        if (delay == null) {
            delay = provider.getDelay();
        }
    }
    if (export != null && ! export.booleanValue()) { // 判断服务是否暴露
        return;
    }

    // 判断是否延迟注册,如果延迟注册则启动一个新的线程去执行,不影响主线程的运行    
    if (delay != null && delay > 0) { 
        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(delay);
                } catch (Throwable e) {
                }
                doExport();
            }
        });
        thread.setDaemon(true);
        thread.setName("DelayExportServiceThread");
        thread.start();
    } else {
        doExport();
    }
}

在doExport方法中,对要暴露的服务进行了一系列的检查,检查provider,application,module,registries,monitor这些参数是否为空,是否是GenericService类型的服务,检查要注册的bean的引用和方法等。在方法的最后会调用doExportUrls方法。

private void doExportUrls() {
    List<URL> registryURLs = loadRegistries(true); // 加载所有的注册中心,服务有可能注册在多个注册中心
    for (ProtocolConfig protocolConfig : protocols) { // 不同协议的注册。dubbo/hessian...
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

doExportUrlsFor1Protocol方法比较长,有些地方简写代替。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }

        // 先从配置中取host,
        // 如果非法则通过InetAddress获取localHost,
        // 如果非法则继续通过Socket取当前localHost。
        String host = getHost();

        // 先从配置中取端口,取不到然后取默认端口,默认端口为空则取一可用端口。
        Integer port = getPort();

        // paramsMap,存放所有配置参数,下面生成url用。
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) {
            map.put(Constants.ANYHOST_KEY, "true");
        }
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);

        // method子标签配置规则解析,retry次数,参数等。没有使用过,不做解释
        if (methods != null && methods.size() > 0) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && arguments.size() > 0) {
                    ...... // 设置参数
                }
            } // end of methods for
        }

        // 获取所有的methods方法
        if (generic) { // 如果是泛化实现,generic=true,method=*表示任意方法
            map.put("generic", String.valueOf(true));
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

        // 有需要配置token,默认随机UUID,否则使用配置中的token,作令牌验证用
        if (! ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put("token", UUID.randomUUID().toString());
            } else {
                map.put("token", token);
            }
        }

        // injvm不需要暴露服务,标注notify=false
        if ("injvm".equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }

        // 导出服务
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }

        // 根据参数创建url对象
        URL url = new URL(name, host, port,
            (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

        // 如果url使用的协议存在扩展,调用对应的扩展来修改原url。目前扩展有override,absent
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置为none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                // 只有当url协议不是injvm的时候才会在本地暴露服务,如果是injvm则什么都不做
                exportLocal(url);
            }
            //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)
            if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        URL monitorUrl = loadMonitor(registryURL);
                        // 如果有monitor信息,则在url上增加monitor配置
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }

                        // 通过代理工厂将url转化成invoker对象,proxyFactory的实现是JavassistProxyFactory
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass,
                            registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        // 这里invoker对象协议是registry,protocol根据协议找到RegisterProtocol实现类,注1。
                        // 在调用RegisterProtocol类的export方法之前会先调用ProtocolListenerWrapper类的export方法
                        // protocol实例转化为ProtocolFilterWrapper,包了一层RegistryProtocol
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    }

注1:protocol对象是如何知道在执行的时候是使用的哪个实现类?如何根据url的协议来创建对应的Protocol。 在ServiceConfig初始化的时候,protocol初始化为

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

getAdaptiveExtension方法说明是在调用的时候再决定使用什么扩展。

下面为生成的protocol字节码Protocol$Adpative image

最终的注册还是在RegistryProtocol类中的export方法中完成的

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    //export invoker
    // 在本地暴露的时候会开启Netty服务
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
}

我们先来看下本地暴露服务。在本地暴露时,protocol.export(invokerDelegete)会转化为DubboProtocol

private <T> ExporterChangeableWrapper<T>  doLocalExport(final Invoker<T> originInvoker){
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    if (exporter == null) {
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
            if (exporter == null) {
                final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker,
                    getProviderUrl(originInvoker));
                exporter = new ExporterChangeableWrapper<T>((Exporter<T>)
                    protocol.export(invokerDelegete), originInvoker);
                bounds.put(key, exporter);
            }
        }
    }
    return (ExporterChangeableWrapper<T>) exporter;
}

因为不是Registry协议,在ProtocolFilterWrapper执行的时候就会走下面的逻辑,在这里有一个方法buildInvokerChain,会创建Dubbo调用过程中的过滤器链,具体类似tomcat中的过滤器链规则。

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

DubboProtocol中的export方法会缓存生成的exporter对象,然后方法最后开启Netty服务

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();
    
    // export service.
    String key = serviceKey(url);
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    exporterMap.put(key, exporter);
    
    // 这里会判断是否是stub服务(不是太懂,默认是false的)
    //export an stub service for dispaching event
    Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);

    // 判断是否是callback服务
    Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
    if (isStubSupportEvent && !isCallbackservice){
        String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
        if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
            // log
        } else {
            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
        }
    }

    // 开启Netty服务
    openServer(url);
    
    return exporter;
}

openServer方法会根据key(ip+port)判断server是否存在,不存在则调用createServer(URL url)方法创建一个链接。

private ExchangeServer createServer(URL url) {
    //默认开启heartbeat
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
    ...
    ExchangeServer server = Exchangers.bind(url, requestHandler);
    ...
    return server;
}

Exchangers的bind方法getExchanger(url)得到的默认为HeaderExchanger,HeaderExchanger的bind方法又调用了Transporters中的bind方法,看getTransporter方法又碰到了熟悉的适配加载机制,Transporter类的注解为@SPI("netty"),默认会调用NettyTransporter的bind方法,NettyTransporter的bind方法直接new了一个NettyServer,NettyServer初始化的时候调用父类的构造方法,父类的构造方法中调用了NettyServer的doOpen方法,NettyServer的链接创建在doOpen方法中完成。

Exchangers
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    // 断点调到这里codec为dubbo,应该是序列化用。
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).bind(url, handler);
}
HeaderExchanger
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url,
        new DecodeHandler(new HeaderExchangeHandler(handler))));
    // 这里应当是适配者模式
}
Transporters
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    return getTransporter().bind(url, handler);
}
public static Transporter getTransporter() {
    // 适配扩展点加载
    return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}

在NettyServer的doOpen方法中可以看到这里绑定了netty的端口和链接。方法到这里本地的Server端口都已经暴露完毕。

protected void doOpen() throws Throwable {
    NettyHelper.setNettyLoggerFactory();
    ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
    ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
    ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker,
        getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
    bootstrap = new ServerBootstrap(channelFactory);
    
    final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    channels = nettyHandler.getChannels();
    // https://issues.jboss.org/browse/NETTY-365
    // https://issues.jboss.org/browse/NETTY-379
    // final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() {
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
            ChannelPipeline pipeline = Channels.pipeline();
            /*int idleTimeout = getIdleTimeout();
            if (idleTimeout > 10000) {
                pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
            }*/
            pipeline.addLast("decoder", adapter.getDecoder());
            pipeline.addLast("encoder", adapter.getEncoder());
            pipeline.addLast("handler", nettyHandler);
            return pipeline;
        }
    });
    // bind
    channel = bootstrap.bind(getBindAddress());
}

回到RegistryProtocol中,来看下在zk中暴露服务。首先先获取zk的配置信息,然后获取需要暴露的url,然后调用registry.register方法将url注册到zookeeper上去。

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    //export invoker
    // 在本地暴露服务
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    //registry provider
    // 拿到zookeeper的注册信息
    final Registry registry = getRegistry(originInvoker);
    // 获取需要暴露provider的url对象,dubbo的注册订阅通信都是以url作为参数传递的
    final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
    registry.register(registedProviderUrl); // 注册服务
    // 订阅override数据
    // FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,
    // 因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    // 暴露的同时订阅服务,另外会在zk上创建configurators节点信息
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    //保证每次export都返回一个新的exporter实例
    return new Exporter<T>() {
        ...
    };
}

调试代码可以发现,这里的registry对象为ZookeeperRegistry,ZookeeperRegistry继承了FailbackRegistry,默认调用父类的register方法,然后调用ZookeeperRegistry的doRegister方法,这里就比较简单了。直接调用zkClient在zookeeper上创建一个节点信息,这样就把服务丢到zk上去了

protected void doRegister(URL url) {
	zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
}

暴露完服务之后,还会调用registry的subscribe方法,这里主要是加了注册订阅Listener,在创建出其他节点之后会调用notify方法。notify方法会做两件事,1. 会将url改动更新到缓存的配置文件中。2. 会通知listener变动,此通知为全量通知。

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:13257次
    • 积分:323
    • 等级:
    • 排名:千里之外
    • 原创:17篇
    • 转载:7篇
    • 译文:1篇
    • 评论:0条
    文章分类