minjay26
敬畏每一行代码

首先我们知道springcloud是一个微服务框架,按照官方文档的说法,springcloud提供了一些开箱即用的功能:

      1 分布式/版本化配置

      2 服务的注册与发现

      3 路由

      4 服务到服务之间调用

      5 负载均衡

      6 断路器

      7 分布式消息

 

本文我们讲解的组件注册中心用的是阿里的nacos(关于nacos的安装可自行百度),网关用的就是springcloudgateway,负载均衡ribbon,断路器hystrix,客户端调用目标是openfeign

 

一 先看服务的注册

 

      服务的注册核心的类就是spring-cloud-commons下的两个类

                      org.springframework.cloud.client.serviceregistry.ServiceRegistry

                      org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration

    ServiceRegistry是一个接口,接口中定义了如何向注册中心注册服务

package org.springframework.cloud.client.serviceregistry;

/**
 * Contract to register and deregister instances with a Service Registry.
 *
 * @author Spencer Gibb
 * @since 1.2.0
 */
public interface ServiceRegistry<R extends Registration> {

    /**
     * Registers the registration. A registration typically has information about
     * an instance, such as its hostname and port.
     * @param registration The registration.
     */
    void register(R registration);

    /**
     * Deregisters the registration.
     * @param registration
     */
    void deregister(R registration);

    /**
     * Closes the ServiceRegistry. This is a lifecycle method.
     */
    void close();

    /**
     * Sets the status of the registration. The status values are determined
     * by the individual implementations.
     *
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     * @param registration The registration to update.
     * @param status The status to set.
     */
    void setStatus(R registration, String status);

    /**
     * Gets the status of a particular registration.
     *
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     * @param registration The registration to query.
     * @param <T> The type of the status.
     * @return The status of the registration.
     */
    <T> T getStatus(R registration);
}

 

 AbstractAutoServiceRegistration则是springcloud向注册中心注册服务的启动逻辑

package org.springframework.cloud.client.serviceregistry;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PreDestroy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.cloud.client.discovery.ManagementServerPortUtils;
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;

/**
 * Lifecycle methods that may be useful and common to {@link ServiceRegistry}
 * implementations.
 *
 * TODO: Document the lifecycle.
 *
 * @param <R> Registration type passed to the {@link ServiceRegistry}.
 *
 * @author Spencer Gibb
 */
public abstract class AbstractAutoServiceRegistration<R extends Registration>
        implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
    private static final Log logger = LogFactory
            .getLog(AbstractAutoServiceRegistration.class);

    private boolean autoStartup = true;

    private AtomicBoolean running = new AtomicBoolean(false);

    private int order = 0;

    private ApplicationContext context;

    private Environment environment;

    private AtomicInteger port = new AtomicInteger(0);

    private final ServiceRegistry<R> serviceRegistry;
    private AutoServiceRegistrationProperties properties;

    @Deprecated
    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
        this.serviceRegistry = serviceRegistry;
        this.properties = properties;
    }

    protected ApplicationContext getContext() {
        return context;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void onApplicationEvent(WebServerInitializedEvent event) {
        bind(event);
    }

    @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();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
        this.environment = this.context.getEnvironment();
    }

    @Deprecated
    protected Environment getEnvironment() {
        return environment;
    }

    @Deprecated
    protected AtomicInteger getPort() {
        return port;
    }

    public boolean isAutoStartup() {
        return this.autoStartup;
    }

    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()) {
            this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }

    /**
     * @return Whether the management service should be registered with the
     * {@link ServiceRegistry}.
     */
    protected boolean shouldRegisterManagement() {
        if (this.properties == null || this.properties.isRegisterManagement()) {
            return getManagementPort() != null
                    && ManagementServerPortUtils.isDifferent(this.context);
        }
        return false;
    }

    /**
     * @return The object used to configure the registration.
     */
    @Deprecated
    protected abstract Object getConfiguration();

    /**
     * @return True, if this is enabled.
     */
    protected abstract boolean isEnabled();

    /**
     * @return The serviceId of the Management Service.
     */
    @Deprecated
    protected String getManagementServiceId() {
        // TODO: configurable management suffix
        return this.context.getId() + ":management";
    }

    /**
     * @return The service name of the Management Service.
     */
    @Deprecated
    protected String getManagementServiceName() {
        // TODO: configurable management suffix
        return getAppName() + ":management";
    }

    /**
     * @return The management server port.
     */
    @Deprecated
    protected Integer getManagementPort() {
        return ManagementServerPortUtils.getPort(this.context);
    }

    /**
     * @return The app name (currently the spring.application.name property).
     */
    @Deprecated
    protected String getAppName() {
        return this.environment.getProperty("spring.application.name", "application");
    }

    @PreDestroy
    public void destroy() {
        stop();
    }

    public boolean isRunning() {
        return this.running.get();
    }

    protected AtomicBoolean getRunning() {
        return running;
    }

    public int getOrder() {
        return this.order;
    }

    public int getPhase() {
        return 0;
    }

    protected ServiceRegistry<R> getServiceRegistry() {
        return this.serviceRegistry;
    }

    protected abstract R getRegistration();

    protected abstract R getManagementRegistration();

    /**
     * Register the local service with the {@link ServiceRegistry}.
     */
    protected void register() {
        this.serviceRegistry.register(getRegistration());
    }

    /**
     * Register the local management service with the {@link ServiceRegistry}.
     */
    protected void registerManagement() {
        R registration = getManagementRegistration();
        if (registration != null) {
            this.serviceRegistry.register(registration);
        }
    }

    /**
     * De-register the local service with the {@link ServiceRegistry}.
     */
    protected void deregister() {
        this.serviceRegistry.deregister(getRegistration());
    }

    /**
     * De-register the local management service with the {@link ServiceRegistry}.
     */
    protected void deregisterManagement() {
        R registration = getManagementRegistration();
        if (registration != null) {
            this.serviceRegistry.deregister(registration);
        }
    }

    public void stop() {
        if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
            deregister();
            if (shouldRegisterManagement()) {
                deregisterManagement();
            }
            this.serviceRegistry.close();
        }
    }
}
View Code

从代码可以看出该类实现了ApplicationListener来监听WebServerInitializedEvent,最终调用register方法来注册服务,register方法就是通过ServiceRegistry接口的实现,比如阿里nacos的实现类com.alibaba.cloud.nacos.registry.NacosServiceRegistry

/*
 * Copyright (C) 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.nacos.registry;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.util.StringUtils;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;

/**
 * @author xiaojing
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
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 = getNacosInstanceFromRegistration(registration);

        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) {

        if (!status.equalsIgnoreCase("UP") && !status.equalsIgnoreCase("DOWN")) {
            log.warn("can't support status {},please choose UP or DOWN", status);
            return;
        }

        String serviceId = registration.getServiceId();

        Instance instance = getNacosInstanceFromRegistration(registration);

        if (status.equalsIgnoreCase("DOWN")) {
            instance.setEnabled(false);
        }
        else {
            instance.setEnabled(true);
        }

        try {
            nacosDiscoveryProperties.namingMaintainServiceInstance()
                    .updateInstance(serviceId, instance);
        }
        catch (Exception e) {
            throw new RuntimeException("update nacos instance status fail", e);
        }

    }

    @Override
    public Object getStatus(Registration registration) {

        String serviceName = registration.getServiceId();
        try {
            List<Instance> instances = nacosDiscoveryProperties.namingServiceInstance()
                    .getAllInstances(serviceName);
            for (Instance instance : instances) {
                if (instance.getIp().equalsIgnoreCase(nacosDiscoveryProperties.getIp())
                        && instance.getPort() == nacosDiscoveryProperties.getPort())
                    return instance.isEnabled() ? "UP" : "DOWN";
            }
        }
        catch (Exception e) {
            log.error("get all instance of {} error,", serviceName, e);
        }
        return null;
    }

    private Instance getNacosInstanceFromRegistration(Registration registration) {
        Instance instance = new Instance();
        instance.setIp(registration.getHost());
        instance.setPort(registration.getPort());
        instance.setWeight(nacosDiscoveryProperties.getWeight());
        instance.setClusterName(nacosDiscoveryProperties.getClusterName());
        instance.setMetadata(registration.getMetadata());
        return instance;
    }

}
View Code

我们可以看到NacosServiceRegistry.register方法最终会调用NamingService.registerInstance

 @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {

        if (instance.isEphemeral()) {
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);
            long instanceInterval = instance.getInstanceHeartBeatInterval();
            beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);

            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }

        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }
View Code

该方法判断isEphemeral,是true的话会向beatReactor新增一个当前service的心跳任务,作用就是通过定时任务向注册中心发一个心跳请求,表示服务仍然存活,这个定时任务的间隔时间默认为5秒,默认值定义在com.alibaba.nacos.api.common.Constants里面,我们可以自定义该配置,spring.cloud.nacos.discovery.heart-beat-interval=1000,这个时间如果太长可能服务已经出问题了,但是注册中心无法感知,导致请求还会打到该节点,太短的话可能会对注册中心造成压力

 

最终向注册中心注册服务就是发起一个简单的http请求,具体细节大家可以看源代码

 

二 再看springcloud的feign配置加载

     feign的启用通过注解EnableFeignClients,该注解会通过import FeignClientsRegistrar来加载feign的配置,其中FeignClientsRegistrar是对接口ImportBeanDefinitionRegistrar的一个实现,该接口相当于自定义对某一包下bean的自定义装配,比如这里的FeignClientsRegistrar就是对注解中basePackages下的feign clientbean的自定义装配。

      其它关于对feign的配置加载,可参考我的另一篇文章 https://www.cnblogs.com/minjay/p/15746223.html 

 

三 ribbon的配置加载 

      ribbon是负载均衡工具,在ribbon之前spring首先会先配置LoadBalancerAutoConfiguration,如下代码

/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.ribbon;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

import com.netflix.client.IClient;
import com.netflix.client.http.HttpRequest;
import com.netflix.ribbon.Ribbon;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {

    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }

    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    @Bean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }

    @Bean
    @ConditionalOnProperty(value = "ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }

    @Configuration
    @ConditionalOnClass(HttpRequest.class)
    @ConditionalOnRibbonRestClient
    protected static class RibbonClientHttpRequestFactoryConfiguration {

        @Autowired
        private SpringClientFactory springClientFactory;

        @Bean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
            return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
        }

        @Bean
        public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
            return new RibbonClientHttpRequestFactory(this.springClientFactory);
        }
    }

    //TODO: support for autoconfiguring restemplate to use apache http client or okhttp

    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnRibbonRestClientCondition.class)
    @interface ConditionalOnRibbonRestClient { }

    private static class OnRibbonRestClientCondition extends AnyNestedCondition {
        public OnRibbonRestClientCondition() {
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        @Deprecated //remove in Edgware"
        @ConditionalOnProperty("ribbon.http.client.enabled")
        static class ZuulProperty {}

        @ConditionalOnProperty("ribbon.restclient.enabled")
        static class RibbonProperty {}
    }

    /**
     * {@link AllNestedConditions} that checks that either multiple classes are present
     */
    static class RibbonClassesConditions extends AllNestedConditions {

        RibbonClassesConditions() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }

        @ConditionalOnClass(IClient.class)
        static class IClientPresent {

        }

        @ConditionalOnClass(RestTemplate.class)
        static class RestTemplatePresent {

        }

        @ConditionalOnClass(AsyncRestTemplate.class)
        static class AsyncRestTemplatePresent {

        }

        @ConditionalOnClass(Ribbon.class)
        static class RibbonPresent {

        }

    }
}
View Code

   LoadBalancerAutoConfiguration会自动帮我们注入支持负载均衡的RestTemplate以及配置重试retry相关。

  加载完LoadBalancerAutoConfiguration后会自动配置RibbonAutoConfiguration,其中很核心的类就是装载了bean LoadBalancerClient为RibbonLoadBalancerClient,LoadBalancerClient接口是spring自己定义的一个接口,但是我发现这个接口的实现在整个调用过程中都用不到,还望哪位大佬能指导下这个实现机制在什么时候会用的到

   还有一个核心配置就是SpringClientFactory,这个类的作用就是在请求时根据你的serviceId去加载相关配置,比如连接超时和读取超时

   

 

 

     

 

 

 

 

 

 

 

posted on 2022-02-16 16:12  minjay26  阅读(623)  评论(0编辑  收藏  举报