SpringCloud War 包部署致使服務未正常註冊到 Nacos 問題

最近把一個 SpringBoot 項目接入了微服務,使用 SpringBoot 2.1.9.RELASE 和 SpringCloud Greewich.SR3 。本地測試都正常,可是上線後,出現了一個這樣一個狀況:java

日誌未出現 Nacos Registery 相似的語句,也沒有任何異常信息,就像是壓根就沒有配置微服務同樣。git

檢查了配置文件,正常github

本地測試,正常註冊spring

後來找組長幫忙看了一下,發現是 war 包部署的問題:segmentfault

SpringCloud 項目打 war 包部署時,也就是使用外部 Tomcat 部署,其啓動命令、端口等是由外部容器 Tomcat 配置的,而 Nacos 或者其餘服務註冊方式須要當前項目的端口號用於註冊微服務。tomcat

以 Nacos 爲例,其自動註冊微服務的類是 NacosAutoServiceRegistration,咱們看一下它的源碼:ide

public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<Registration> {

    private NacosRegistration registration;

    @Deprecated
    public void setPort(int port) {
        this.getPort().set(port);
    }

    protected NacosRegistration getRegistration() {
        if (this.registration.getPort() < 0 && this.getPort().get() > 0) {
            this.registration.setPort(this.getPort().get());
        }

        Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
        return this.registration;
    }

咱們看到 NacosAutoServiceRegistration 使用了 this.registration.setPort(this.getPort().get()); 來設置端口號。微服務

而端口號是從其父類 AbstractAutoServiceRegistration 中的方法獲取的:學習

public abstract class AbstractAutoServiceRegistration<R extends Registration>
        implements AutoServiceRegistration, ApplicationContextAware,
        ApplicationListener<WebServerInitializedEvent> {

    private AtomicInteger port = new AtomicInteger(0);


    @Deprecated
    public void bind(WebServerInitializedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        if (context instanceof ConfigurableWebServerApplicationContext) {
            if ("management".equals(((ConfigurableWebServerApplicationContext) context)
                    .getServerNamespace())) {
                return;
            }
        }
        this.port.compareAndSet(0, event.getWebServer().getPort());
        this.start();
    }

這段代碼監聽了內置容器啓動完成事件,監聽獲取到容器端口後,向註冊中心註冊服務。測試

所以,當使用外部容器時,如此處的 Tomcat 來部署項目,AbstractAutoServiceRegistration 就不能監聽到容器啓動事件了,也就不會嘗試向服務註冊中心註冊當前這個微服務,那麼註冊就失敗了,而且也就沒有異常信息了。

解決辦法是自定義獲取獲取外部容器端口的方法, 而後監聽應用啓動事件,當應用被啓動時,獲取外部容器啓動的端口號,而後將這個 port 設置到 NacosAutoServiceReigistration 中。

下面是完整的解決方法:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.alibaba.nacos.registry.NacosAutoServiceRegistration;
import org.springframework.stereotype.Component;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.lang.management.ManagementFactory;
import java.util.Set;

@Component
public class NacosConfig implements ApplicationRunner {

    @Autowired(required = false)
    private NacosAutoServiceRegistration registration;

    @Value("${server.port}")
    Integer port;

    @Override
    public void run(ApplicationArguments args) {
        if (registration != null && port != null) {
            Integer tomcatPort = port;
            try {
                tomcatPort = new Integer(getTomcatPort());
            } catch (Exception e) {
                e.printStackTrace();
            }

            registration.setPort(tomcatPort);
            registration.start();
        }
    }

    /**
     * 獲取外部tomcat端口
     */
    public String getTomcatPort() throws Exception {
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
        String port = objectNames.iterator().next().getKeyProperty("port");
        return port;
    }
}

交流學習

我的網站:http://www.eknown.cn

GitHub:https://github.com/laolunsi

公衆號:猿生物語,"分享技術,也感悟人生",歡迎關注!

相關文章
相關標籤/搜索