dubbo启动流程

1.DubboBootstrap类启动

1.1 入口:start()方法

start方法中包含了服务暴露和服务引用,这里跟踪服务的暴露流程

public DubboBootstrap start() {
    if (started.compareAndSet(false, true)) {
        startup.set(false);
        initialized.set(false);
        shutdown.set(false);
        awaited.set(false);

        initialize();
        if (logger.isInfoEnabled()) {
            logger.info(NAME + " is starting...");
        }
        // 1. 暴露 dubbo服务(provider端)
        exportServices();
        if (isRegisterConsumerInstance() || hasExportedServices()) {
            // 2. export MetadataService
            exportMetadataService();
            // 3. Register the local ServiceInstance if required
            registerServiceInstance();
        }
		// 引用dubbo服务(consumer端)
        referServices();
        if (asyncExportingFutures.size() > 0 || asyncReferringFutures.size() > 0) {
            new Thread(() -> {
                try {
                    this.awaitFinish();
                } catch (Exception e) {
                    logger.warn(NAME + " asynchronous export / refer occurred an exception.");
                }
                startup.set(true);
                if (logger.isInfoEnabled()) {
                    logger.info(NAME + " is ready.");
                }
                onStart();
            }).start();
        } else {
            startup.set(true);
            if (logger.isInfoEnabled()) {
                logger.info(NAME + " is ready.");
            }
            onStart();
        }
        if (logger.isInfoEnabled()) {
            logger.info(NAME + " has started.");
        }
    }
    return this;
}

1.2 暴露dubbo服务,exportServices()方法

会调用到ServiceConfig的export方法

private void exportServices() {
    for (ServiceConfigBase sc : configManager.getServices()) {
        ServiceConfig<?> serviceConfig = (ServiceConfig<?>) sc;
        serviceConfig.setBootstrap(this);
        if (!serviceConfig.isRefreshed()) {
            serviceConfig.refresh();
        }
        // 异步起动
        if (sc.shouldExportAsync()) {
            ExecutorService executor = executorRepository.getExportReferExecutor();
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                try {
                    sc.export();
                    exportedServices.add(sc);
                } catch (Throwable t) {
                    logger.error("export async catch error : " + t.getMessage(), t);
                }
            }, executor);

            asyncExportingFutures.add(future);
        } 
        // 非异步暴露
        else {
        	// 调用暴露
            sc.export();
            exportedServices.add(sc);
        }
    }
}

1.3 调用ServiceConfig的export()方法

public synchronized void export() {
    if (this.shouldExport() && !this.exported) {
        this.init();
        // check bootstrap state
        if (!bootstrap.isInitialized()) {
            throw new IllegalStateException("DubboBootstrap is not initialized");
        }
        if (!this.isRefreshed()) {
            this.refresh();
        }
        if (!shouldExport()) {
            return;
        }
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
        	// 进一步调用doExport
            doExport();
        }
        if (this.bootstrap.getTakeoverMode() == BootstrapTakeoverMode.AUTO) {
            this.bootstrap.start();
        }
    }
}

1.4 调用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;
     }
     // 调用doExportUrls暴露urls
     doExportUrls();
     exported();
 }

1.5 调用doExportUrls()

如果有多个URL,则循环依次暴露出去

private void doExportUrls() {
    ServiceRepository repository = ApplicationModel.getServiceRepository();
    ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
    repository.registerProvider(
            getUniqueServiceName(),
            ref,
            serviceDescriptor,
            this,
            serviceMetadata
    );
    List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
    // 如果有多个url,循环遍历
    for (ProtocolConfig protocolConfig : protocols) {
        String pathKey = URL.buildKey(getContextPath(protocolConfig)
                .map(p -> p + "/" + path)
                .orElse(path), group, version);
        // In case user specified path, register service one more time to map it to path.
        repository.registerService(pathKey, interfaceClass);
        // 暴露单个
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

1.6 暴露单个url, doExportUrlsFor1Protocol()

这里会创建动态代理,javassist或者jdk动态代理,如果使用javassist动态代理,创建 AbstractProxyInvoker(Wrapper),调用流程:invoker的doInvoke方法 -> wrapper.invokeMethod -> 用户的ServiceImpl方法,使用Wrapper, 会组装出Class, 然后使用组装出的类调用,从而避免了反射调用,比jdk动态代理的反射调用稍微好些。
包装器模式:new DelegateProviderMetaDataInvoker(new AbstractProxyInvoker())

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	.... 此处省略代码
	
	// 非local, 暴露远程暴露
    if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
        if (CollectionUtils.isNotEmpty(registryURLs)) {
            for (URL registryURL : registryURLs) {
                //if protocol is only injvm ,not register
                if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                    continue;
                }
                url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                if (monitorUrl != null) {
                    url = url.putAttribute(MONITOR_KEY, monitorUrl);
                }
                if (logger.isInfoEnabled()) {
                    if (url.getParameter(REGISTER_KEY, true)) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url.getServiceKey() + " to registry " + registryURL.getAddress());
                    } else {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url.getServiceKey());
                    }
                }

                // For providers, this is used to enable custom proxy to generate invoker
                String proxy = url.getParameter(PROXY_KEY);
                if (StringUtils.isNotEmpty(proxy)) {
                    registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                }

				// 这里创建动态代理,JavassistProxy或者JdkProxy
				// 如果使用javassist动态代理,创建 AbstractProxyInvoker(Wrapper),
				// 调用doInvoke -> wrapper.invokeMethod -> 用户的ServiceImpl方法
				// 使用Wrapper, 会组装出Class, 然后使用组装出的类调用,从而避免了反射调用,比jdk动态代理的反射调用稍微好些
                Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.putAttribute(EXPORT_KEY, url));
                // 包装了下,将(invoker  + ServiceConfig)包装进去
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
				// 使用protocol的export, 会调用到RegistryProtocol类的export
                Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                exporters.add(exporter);
            }
        } else {
            if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            if (MetadataService.class.getName().equals(url.getServiceInterface())) {
                MetadataUtils.saveMetadataURL(url);
            }
            Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
            DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

            Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
            exporters.add(exporter);
        }

        MetadataUtils.publishServiceDefinition(url);
    }
}
this.urls.add(url);
}

2. PROTOCOL暴露export()

2.1 RegistryProtocol.export()方法暴露dubbo服务

来到RegistryProtocol下也是经过dubbo spi aop包装的,流程:QosProtocolWrapper.export -> ProtocolFilterWrapper.export -> ProtocolListenerWrapper.export -> RegistryProtocol.export,但实际上QosProtocolWrapper、ProtocolFilterWrapper、ProtocolListenerWrapper根本没有做什么,这里强耦合了,属于多余的包装操作

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    URL registryUrl = getRegistryUrl(originInvoker);
    URL providerUrl = getProviderUrl(originInvoker);
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    // 暴露一个服务
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
    final Registry registry = getRegistry(registryUrl);
    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
        register(registry, registeredProviderUrl);
    }
    registerStatedUrl(registryUrl, registeredProviderUrl, register);
    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    notifyExport(exporter);
    return new DestroyableExporter<>(exporter);
}

2.2 doLocalExport方法

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
    	// 将providerUrl包装进去
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        // protocol.export调用链  
        // QosProtocolWrapper.export -> ProtocolFilterWrapper.export -> ProtocolListenerWrapper.export -> DubboProtocol.export
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

2.3 来到DubboProtocol的export方法

来到DubboProtocol的export,中间经过流程:QosProtocolWrapper.export -> ProtocolFilterWrapper.export -> ProtocolListenerWrapper.export -> DubboProtocol.export
其中ProtocolFilterWrapper会给invoker包装过滤器链

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();
    String key = serviceKey(url);
    // 将invoker封装进DubboExporter,实际上exporter没啥大的作用,exporter有销毁invoker功能,很鸡肋
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    //  exporter 存入exporterMap
    exporterMap.put(key, exporter);
    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."));
            }

        }
    }
    // 打开服务,暴露端口(20880)出去,即开启NettyServer,启动Nettty服务端
    openServer(url);
    optimizeSerialization(url);
    return exporter;
}

2.4 openServer开启服务

// 如果要暴露多个URL,只会创建一次NettyServer,因为开启netty服务端只需要暴露一个端口(20880)就行了

private void openServer(URL url) {
    // 获取暴露地址. 例如: 192.168.56.1:20880
    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) {
    	// 根据key看是否有server,有则表明有其他接口暴露过了,
    	// 不再暴露(暴露的NettyServer是共享端口192.168.56.1:20880)
        ProtocolServer server = serverMap.get(key);
        if (server == null) {
            synchronized (this) {
                server = serverMap.get(key);
                if (server == null) {
                	// createServer 创建netty服务
                    serverMap.put(key, createServer(url));
                }
            }
        } else {
            server.reset(url);
        }
    }
}

2.5 createServer创建服务

传入requestHandler,将其绑定到NettyServer

private ProtocolServer createServer(URL url) {
    url = URLBuilder.from(url)
            .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
            .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
            .addParameter(CODEC_KEY, DubboCodec.NAME)
            .build();
    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 server;
    try {
    	// 开始bind url和requestHandler,
    	// 这个requestHandler很重要,dubbo服务处理重要Handler, 通过此handler的reply方法
    	// 获取invoker,来到动态代理,最终调用到用户ServiceImpl中去
        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 new DubboProtocolServer(server);
}

2.6 Exchangers.bind

获取HeaderExchanger,再bind

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // getExchanger获取到的是HeaderExchanger
    return getExchanger(url).bind(url, handler);
}

2.7 HeaderExchanger.bind

包装handler, 组装调用链的过程,DecodeHandler用来做数据反序列化,默认hession2

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
	// 这里又是使用包装器模式,层层包装handler,其中重要的是
	// new NettyServer(new DecodeHandler(new HeaderExchangeHandler(handler))
	// DecodeHandler是用反序列化的,如hession(在业务线程中处理)
	// 区别于dubbo协议的拆包粘包数据格式处理decode、incode(这个是在io线程中处理的)
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

2.8 Transporters.bind

public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
   if (url == null) {
       throw new IllegalArgumentException("url == null");
   }
   if (handlers == null || handlers.length == 0) {
       throw new IllegalArgumentException("handlers == null");
   }
   ChannelHandler handler;
   if (handlers.length == 1) {
       handler = handlers[0];
   } else {
       handler = new ChannelHandlerDispatcher(handlers);
   }
  	// getTransporter获取的是 org.apache.dubbo.remoting.transport.netty4.NettyTransporter
   return getTransporter().bind(url, handler);
}

3 开始创建NettyServer

public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
   return new NettyServer(url, handler);
}

3.1 调用NettyServer的构造方法

public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
   // ChannelHandlers.wrap(handler, url) 包装handler
   super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url));
}

3.1.1 包装hander过程

形成了hander包装链,NettyServerHandler -> NettyServer -> MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExangeHandler -> DubboProtocol类下的ExchangeHandlerAdapter。其中到AllChannelHandler时,会跳转到业务线程处理:executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message)); 调用run方法,再调用handler.received(channel, message),继续后续流程,最后来到了DubboProtocol类下的ExchangeHandlerAdapter.reply方法下。

   protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
   	// 通过dubbo spi获取到AllChannelHandler
   	// 最终结果:NettyServerHandler(NettyServer(MultiMessageHandler(HeartbeatHandler(AllChannelHandler(DecodeHandler(HeaderExangeHandler(DubboProtocol类下的ExchangeHandlerAdapter))))) ))
   	// 此包装链即为dubbo服务handler 从NettyServerHandler开始到ExchangeHandlerAdapter的reply方法的调用链
       return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
               .getAdaptiveExtension().dispatch(handler, url)));
   }

3.1.2 分析DubboProtocol类下的ExchangeHandlerAdapter.reply方法

getInvoker,获取invoker,其实就是从exporterMap获取exporter,再从exporter中获取invoker, 这个invoker也是包装器模式层层包装过的invoker,主要是:各种FilterInvoker(FilterChainNode过滤器链) -> DelegateProviderMetaDataInvoker ->JavassistProxyFactory下的AbstractProxyInvoker -> 用户业务代码

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

   @Override
   public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {

       if (!(message instanceof Invocation)) {
           throw new RemotingException(channel, "Unsupported request: "
                   + (message == null ? null : (message.getClass().getName() + ": " + message))
                   + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
       }

       Invocation inv = (Invocation) message;
       Invoker<?> invoker = getInvoker(channel, inv);
       // need to consider backward-compatibility if it's a callback
       if (Boolean.TRUE.toString().equals(inv.getObjectAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
           String methodsStr = invoker.getUrl().getParameters().get("methods");
           boolean hasMethod = false;
           if (methodsStr == null || !methodsStr.contains(",")) {
               hasMethod = inv.getMethodName().equals(methodsStr);
           } else {
               String[] methods = methodsStr.split(",");
               for (String method : methods) {
                   if (inv.getMethodName().equals(method)) {
                       hasMethod = true;
                       break;
                   }
               }
           }
           if (!hasMethod) {
               logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                       + " not found in callback service interface ,invoke will be ignored."
                       + " please update the api interface. url is:"
                       + invoker.getUrl()) + " ,invocation is :" + inv);
               return null;
           }
       }
       RpcContext.getServiceContext().setRemoteAddress(channel.getRemoteAddress());
       Result result = invoker.invoke(inv);
       return result.thenApply(Function.identity());
   }

3.2 NettyServer构造器中会调用doOpen()方法,开启Netty服务

这里就是使用Netty api操作,开启服务暴露端口,主要的是在pipeline中加入了3个重要的bound处理器

  1. dubbo协议编码器,InternalEncoder (OutboundHandler)
  2. dubbo协议解码器,InternalDecoder (InboundHandler)
  3. dubbo服务调用流程处理器,NettyServerHandler (InboundHandler和OutboundHandler)
   protected void doOpen() throws Throwable {
       bootstrap = new ServerBootstrap();

       bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");
       workerGroup = NettyEventLoopFactory.eventLoopGroup(
               getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
               "NettyServerWorker");

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

       boolean keepalive = getUrl().getParameter(KEEP_ALIVE_KEY, Boolean.FALSE);

       bootstrap.group(bossGroup, workerGroup)
               .channel(NettyEventLoopFactory.serverSocketChannelClass())
               .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
               .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
               .childOption(ChannelOption.SO_KEEPALIVE, keepalive)
               .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
               .childHandler(new ChannelInitializer<SocketChannel>() {
                   @Override
                   protected void initChannel(SocketChannel ch) throws Exception {
                       // FIXME: should we use getTimeout()?
                       int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                       NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                       if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
                           ch.pipeline().addLast("negotiation",
                                   SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
                       }
                       ch.pipeline()
                       			 // dubbo协议解码器,InternalDecoder
                               .addLast("decoder", adapter.getDecoder())
                               // dubbo协议编码器,InternalEncoder
                               .addLast("encoder", adapter.getEncoder())
                               .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                               // dubbo服务调用流程处理器,NettyServerHandler
                               .addLast("handler", nettyServerHandler);
                   }
               });
       // bind
       ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
       channelFuture.syncUninterruptibly();
       channel = channelFuture.channel();
   }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
重新启动Dubbo服务的方法有多种。首先,你可以检查服务端和客户端是否都报错了。如果是这样,可能是服务断线导致的问题。你可以尝试重新连接服务以解决此问题。另外,如果你在使用jdk1.8下启动dubbo-admin时遇到了报错问题,你可以尝试下载dubbo源码并进行修改重新编译打包发布解决这个问题。此外,你还可以考虑升级Daojia自有dubbo版本到3.0.2-SNAPSHOT或以上版本,这可能会解决一些问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Dubbo服务重启后不停报错[DUBBO] disconected from 问题原因解析](https://blog.csdn.net/qq_38975553/article/details/104494713)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [解决dubbo-admin在jdk1.8下启动报错](https://download.csdn.net/download/u010781073/10121031)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [dubbo如何平滑重启](https://blog.csdn.net/m0_67401228/article/details/126328123)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值