@EnableDiscoveryClient 注解如何實現服務注冊與發現


@EnableDiscoveryClient 是如何實現服務注冊的?
我們首先需要了解 Spring-Cloud-Commons 這個模塊,Spring-Cloud-Commons 是 Spring-Cloud 官方提供的一套抽象層,類似於 JDBC 一樣,提供了一套規范,具體的實現有實現廠商去根據標准實現,在Finchley版中, Spring-Cloud-Commons 共提供了6個模塊標准規范。

actuator
circuitbreaker
discovery
hypermedia
loadbalancer
serviceregistry
在今天的文章中,我們一起來探討學習一下 discovery、serviceregistry這兩個模塊,我們使用 alibaba 的 nacos-discovery 實現來進行學習。
@EnableDiscoveryClient 注解做了什么事?

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     */
    boolean autoRegister() default true;
}

從EnableDiscoveryClient源碼可以看出該接口有一個autoRegister()方法默認返回值是true,它還做了一件非常重要的事,引用了EnableDiscoveryClientImportSelector類。為什么說這個類非常重要呢?我們來看看就知道了

EnableDiscoveryClientImportSelector 類做了什么事?

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector<EnableDiscoveryClient> {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        String[] imports = super.selectImports(metadata);

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

        boolean autoRegister = attributes.getBoolean("autoRegister");

        if (autoRegister) {
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = importsList.toArray(new String[0]);
        } else {
            Environment env = getEnvironment();
            if(ConfigurableEnvironment.class.isInstance(env)) {
                ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
                LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                map.put("spring.cloud.service-registry.auto-registration.enabled", false);
                MapPropertySource propertySource = new MapPropertySource(
                        "springCloudDiscoveryClient", map);
                configEnv.getPropertySources().addLast(propertySource);
            }

        }

        return imports;
    }

    @Override
    protected boolean isEnabled() {
        return getEnvironment().getProperty(
                "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
    }

    @Override
    protected boolean hasDefaultFactory() {
        return true;
    }

}

將焦點聚集到selectImports()方法上,該類獲取了autoRegister 的值。

當autoRegister=true 時,將AutoServiceRegistrationConfiguration類添加到自動裝配中,系統就會去自動裝配AutoServiceRegistrationConfiguration類,在具體的實現中自動裝配類都是在這個AutoServiceRegistrationConfiguration類自動裝配完成后才裝配的,也就是說autoRegister=true就更夠實現服務注冊

當autoRegister=false時,將spring.cloud.service-registry.auto-registration.enabled 設置成了 false,這樣跟注冊相關的類將不會自動裝配,因為自動注冊相關的類都有一個條件裝配@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true),換句話說如果我們不想該服務注冊到注冊中心,只是想從注冊中心拉取服務,我們只需要引導類上的注解改成@EnableDiscoveryClient(autoRegister = false)

nacos 是如何根據標准去實現服務注冊的?
我們先看看在org.springframework.cloud.alibaba.nacos包下的NacosDiscoveryAutoConfiguration類

NacosDiscoveryAutoConfiguration類做了些什么?

@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class })
public class NacosDiscoveryAutoConfiguration {

    @Bean
    public NacosServiceRegistry nacosServiceRegistry(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosDiscoveryProperties);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosRegistration nacosRegistration(
            NacosDiscoveryProperties nacosDiscoveryProperties,
            ApplicationContext context) {
        return new NacosRegistration(nacosDiscoveryProperties, context);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        return new NacosAutoServiceRegistration(registry,
                autoServiceRegistrationProperties, registration);
    }
}

該類的自動裝配是在AutoServiceRegistrationConfiguration之后完成,當autoRegister設置為false時,NacosDiscoveryAutoConfiguration就不會裝配,也就意味着服務不會像注冊中心進行注冊。好了我們還是來看看NacosDiscoveryAutoConfiguration干了些啥吧,主要是裝配了NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration三個bean,來看看三個bean干了那些騷操作。

NacosServiceRegistry類做了些什么?

public class NacosServiceRegistry implements ServiceRegistry<Registration> {

    private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    private final NamingService namingService;

    public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.namingService = nacosDiscoveryProperties.namingServiceInstance();
    }

    @Override
    public void register(Registration registration) {

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
            return;
        }

        String serviceId = registration.getServiceId();

        Instance instance = new Instance();
        instance.setIp(registration.getHost());
        instance.setPort(registration.getPort());
        instance.setWeight(nacosDiscoveryProperties.getWeight());
        instance.setClusterName(nacosDiscoveryProperties.getClusterName());
        instance.setMetadata(registration.getMetadata());

        try {
            namingService.registerInstance(serviceId, instance);
            log.info("nacos registry, {} {}:{} register finished", serviceId,
                    instance.getIp(), instance.getPort());
        }
        catch (Exception e) {
            log.error("nacos registry, {} register failed...{},", serviceId,
                    registration.toString(), e);
        }
    }

    @Override
    public void deregister(Registration registration) {

        log.info("De-registering from Nacos Server now...");

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No dom to de-register for nacos client...");
            return;
        }

        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        String serviceId = registration.getServiceId();

        try {
            namingService.deregisterInstance(serviceId, registration.getHost(),
                    registration.getPort(), nacosDiscoveryProperties.getClusterName());
        }
        catch (Exception e) {
            log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
                    registration.toString(), e);
        }

        log.info("De-registration finished.");
    }

    @Override
    public void close() {

    }

    @Override
    public void setStatus(Registration registration, String status) {
        // nacos doesn't support set status of a particular registration.
    }

    @Override
    public <T> T getStatus(Registration registration) {
        // nacos doesn't support query status of a particular registration.
        return null;
    }

}

該類實現了 spring-cloud-commons 提供的 ServiceRegistry 接口,重寫了register、deregister兩個方法,在register
方法中主要是將配置文件裝換成Instance實例,調用了namingService.registerInstance(serviceId, instance);方法,這個方法應該是服務端提供的服務注冊方法。這一頓操作之后,服務就注冊好了。

NacosRegistration類做了些什么?

public class NacosRegistration implements Registration, ServiceInstance {

    public static final String MANAGEMENT_PORT = "management.port";
    public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";
    public static final String MANAGEMENT_ADDRESS = "management.address";
    public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";

    private NacosDiscoveryProperties nacosDiscoveryProperties;

    private ApplicationContext context;

    public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,
            ApplicationContext context) {
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.context = context;
    }

    @PostConstruct
    public void init() {

        Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
        Environment env = context.getEnvironment();

        String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH);
        if (!StringUtils.isEmpty(endpointBasePath)) {
            metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath);
        }

        Integer managementPort = ManagementServerPortUtils.getPort(context);
        if (null != managementPort) {
            metadata.put(MANAGEMENT_PORT, managementPort.toString());
            String contextPath = env
                    .getProperty("management.server.servlet.context-path");
            String address = env.getProperty("management.server.address");
            if (!StringUtils.isEmpty(contextPath)) {
                metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
            }
            if (!StringUtils.isEmpty(address)) {
                metadata.put(MANAGEMENT_ADDRESS, address);
            }
        }
    }

    @Override
    public String getServiceId() {
        return nacosDiscoveryProperties.getService();
    }

    @Override
    public String getHost() {
        return nacosDiscoveryProperties.getIp();
    }

    @Override
    public int getPort() {
        return nacosDiscoveryProperties.getPort();
    }

    public void setPort(int port) {
        this.nacosDiscoveryProperties.setPort(port);
    }

    @Override
    public boolean isSecure() {
        return nacosDiscoveryProperties.isSecure();
    }

    @Override
    public URI getUri() {
        return DefaultServiceInstance.getUri(this);
    }

    @Override
    public Map<String, String> getMetadata() {
        return nacosDiscoveryProperties.getMetadata();
    }

    public boolean isRegisterEnabled() {
        return nacosDiscoveryProperties.isRegisterEnabled();
    }

    public String getCluster() {
        return nacosDiscoveryProperties.getClusterName();
    }

    public float getRegisterWeight() {
        return nacosDiscoveryProperties.getWeight();
    }

    public NacosDiscoveryProperties getNacosDiscoveryProperties() {
        return nacosDiscoveryProperties;
    }

    public NamingService getNacosNamingService() {
        return nacosDiscoveryProperties.namingServiceInstance();
    }

    @Override
    public String toString() {
        return "NacosRegistration{" + "nacosDiscoveryProperties="
                + nacosDiscoveryProperties + '}';
    }
}

該類主要是裝配了一些management管理類的配置信息

NacosAutoServiceRegistration類做了些什么事情?

public class NacosAutoServiceRegistration
        extends AbstractAutoServiceRegistration<Registration> {
    private static final Logger log = LoggerFactory
            .getLogger(NacosAutoServiceRegistration.class);

    private NacosRegistration registration;

    public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        super(serviceRegistry, autoServiceRegistrationProperties);
        this.registration = registration;
    }

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

    @Override
    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;
    }

    @Override
    protected NacosRegistration getManagementRegistration() {
        return null;
    }

    @Override
    protected void register() {
        if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
            log.debug("Registration disabled.");
            return;
        }
        if (this.registration.getPort() < 0) {
            this.registration.setPort(getPort().get());
        }
        super.register();
    }

    @Override
    protected void registerManagement() {
        if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
            return;
        }
        super.registerManagement();

    }

    @Override
    protected Object getConfiguration() {
        return this.registration.getNacosDiscoveryProperties();
    }

    @Override
    protected boolean isEnabled() {
        return this.registration.getNacosDiscoveryProperties().isRegisterEnabled();
    }

    @Override
    @SuppressWarnings("deprecation")
    protected String getAppName() {
        String appName = registration.getNacosDiscoveryProperties().getService();
        return StringUtils.isEmpty(appName) ? super.getAppName() : appName;
    }
}

這個類主要是調用NacosServiceRegistryregister()方法,我們來關注一下他的父類AbstractAutoServiceRegistrationstart(),這個才是啟動方法。

    public void start() {
        if (!isEnabled()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Discovery Lifecycle disabled. Not starting");
            }
            return;
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
        if (!this.running.get()) {
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }
    ......省略
    protected void register() {
        this.serviceRegistry.register(getRegistration());
    }

在start()方法里調用了NacosAutoServiceRegistration.register方法,NacosAutoServiceRegistration.register的方法里又調用了父類AbstractAutoServiceRegistration.register方法,在父類AbstractAutoServiceRegistration.register方法里調用了NacosServiceRegistry.register方法,實現了服務注冊。

服務注冊大概經歷了這么多,有興趣的可以自己出看看源碼,相信你肯定比我理解的更好。下面是個人學習 Nacos 服務注冊的源碼閱讀流程圖,其他的實現也差不多,主要是要理解 Spring-Cloud-Commons 的規范。

@EnableDiscoveryClient執行流程.png

@EnableDiscoveryClient 是如何實現服務發現?

通過上面我們知道了 Spring-Cloud-Commons 模塊實現了一套規范,我們直接去看在服務發現的規范是什么?我們能夠找到DiscoveryClient接口。

public interface DiscoveryClient {

    /**
     * A human readable description of the implementation, used in HealthIndicator
     * @return the description
     */
    String description();

    /**
     * Get all ServiceInstances associated with a particular serviceId
     * @param serviceId the serviceId to query
     * @return a List of ServiceInstance
     */
    List<ServiceInstance> getInstances(String serviceId);

    /**
     * @return all known service ids
     */
    List<String> getServices();

}

里面就提供了三個接口,我們接下來看看nacos是如何實現的?

public class NacosDiscoveryClient implements DiscoveryClient {

    private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class);
    public static final String DESCRIPTION = "Spring Cloud Nacos Discovery Client";

    private NacosDiscoveryProperties discoveryProperties;

    public NacosDiscoveryClient(NacosDiscoveryProperties discoveryProperties) {
        this.discoveryProperties = discoveryProperties;
    }

    @Override
    public String description() {
        return DESCRIPTION;
    }

    @Override
    public List<ServiceInstance> getInstances(String serviceId) {
        try {
            List<Instance> instances = discoveryProperties.namingServiceInstance()
                    .selectInstances(serviceId, true);
            return hostToServiceInstanceList(instances, serviceId);
        }
        catch (Exception e) {
            throw new RuntimeException(
                    "Can not get hosts from nacos server. serviceId: " + serviceId, e);
        }
    }

    private static ServiceInstance hostToServiceInstance(Instance instance,
            String serviceId) {
        NacosServiceInstance nacosServiceInstance = new NacosServiceInstance();
        nacosServiceInstance.setHost(instance.getIp());
        nacosServiceInstance.setPort(instance.getPort());
        nacosServiceInstance.setServiceId(serviceId);

        Map<String, String> metadata = new HashMap<>();
        metadata.put("nacos.instanceId", instance.getInstanceId());
        metadata.put("nacos.weight", instance.getWeight() + "");
        metadata.put("nacos.healthy", instance.isHealthy() + "");
        metadata.put("nacos.cluster", instance.getClusterName() + "");
        metadata.putAll(instance.getMetadata());
        nacosServiceInstance.setMetadata(metadata);

        if (metadata.containsKey("secure")) {
            boolean secure = Boolean.parseBoolean(metadata.get("secure"));
            nacosServiceInstance.setSecure(secure);
        }
        return nacosServiceInstance;
    }

    private static List<ServiceInstance> hostToServiceInstanceList(
            List<Instance> instances, String serviceId) {
        List<ServiceInstance> result = new ArrayList<>(instances.size());
        for (Instance instance : instances) {
            result.add(hostToServiceInstance(instance, serviceId));
        }
        return result;
    }

    @Override
    public List<String> getServices() {

        try {
            ListView<String> services = discoveryProperties.namingServiceInstance()
                    .getServicesOfServer(1, Integer.MAX_VALUE);
            return services.getData();
        }
        catch (Exception e) {
            log.error("get service name from nacos server fail,", e);
            return Collections.emptyList();
        }
    }
}

這里面的邏輯非常簡單,就不過多贅述了,有興趣的小伙伴,可以自行去研究喔。

整個服務注冊與發現差不多就是這樣子,因為涉及的內容非常多,在很多地方小弟我確實看不懂,在上述中肯定有非常多的錯誤還請大神們多多指教。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM