首先我们知道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(); } } }
从代码可以看出该类实现了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; } }
我们可以看到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); }
该方法判断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 { } } }
LoadBalancerAutoConfiguration会自动帮我们注入支持负载均衡的RestTemplate以及配置重试retry相关。
加载完LoadBalancerAutoConfiguration后会自动配置RibbonAutoConfiguration,其中很核心的类就是装载了bean LoadBalancerClient为RibbonLoadBalancerClient,LoadBalancerClient接口是spring自己定义的一个接口,但是我发现这个接口的实现在整个调用过程中都用不到,还望哪位大佬能指导下这个实现机制在什么时候会用的到
还有一个核心配置就是SpringClientFactory,这个类的作用就是在请求时根据你的serviceId去加载相关配置,比如连接超时和读取超时