Spring Cloud 集成 nacos 原理详解
# 一 Nacos Naming集成Spring Cloud
# 1、引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
此依赖引入:com.alibaba.nacos:nacos-client:1.3.3
spring-cloud-commons-2.2.1.RELEASE
spring-cloud-context-2.2.1.RELEASE
spring-cloud-starter-netflix-ribbon-2.2.1.RELEASE
# 2、参数配置
spring:
cloud:
service-registry:
auto-registration:
enabled: true
register-management: true
fail-fast: false
nacos:
discovery:
namespace: 3a7086e3-3c61-4553-b719-5921b34266c0 # 默认 public
server-addr: 192.168.8.92:8848
endpoint: # 不能和server-addr同时存在,优先选择此配置
username:
password:
watch-delay: 30000 # 定时发送事件: HeartbeatEvent(暂时未发现有处理此事件)
log-name: # 日志文件名称 默认为 naming.log (暂时没发现有啥用)
service: ${spring.application.name} # 服务名称 spring.cloud.nacos.discovery.service | spring.application.name
weight: 1 # 服务权重
cluster-name: DEFAULT # 集群名称
group: DEFAULT_GROUP # 集群组
register-enabled: true # 服务是否注册(是否作为服务提供者)
instance-enabled: true # 服务是否可用(注册实例后,实例状态)
network-interface:
ip: # 注册服务时,优先使用此ip,没有则自动生成
port: -1
secure: false # 是否为https
access-key:
secret-key:
heart-beat-interval: 5000 # 心跳间隔事件
heart-beat-timeout: 15000 # 心跳超时时间
ip-delete-timeout: 30000 # ip移除超时时间
ephemeral: true # 服务是否时临时的(临时服务需要心跳机制探活)
metadata:
app: demo
zone: shanghai
# 是否使用云环境解析 System.getProperty
# ans.namespace
# ALIBABA_ALIWARE_NAMESPACE
isUseCloudNamespaceParsing: false
# 是否使用云环境解析 System.getenv
# ALIBABA_ALIWARE_ENDPOINT_URL
isUseEndpointParsingRule: false
# 缓存文件基础目录: System.getProperty(com.alibaba.nacos.naming.cache.dir) System.getProperty(user.home) + "/nacos/naming/" + namespace
# NamingProxy
# 客户端每30s: 从endpoint对应的服务拿取nacos server列表并更新serversFromEndpoint "http://" + endpoint + "/nacos/serverlist"
# 客户端每5s: 遍历所有nacos server列表并依次登录获取accessToken "http://" + server + "/nacos/v1/auth/users/login"
# BeatReactor
namingClientBeatThreadCount: 6
# HostReactor
naming-load-cache-at-start: true # 启动是是否加载本地缓存的naming配置,用于nacos server不可用时提供高可用
namingPollingThreadCount: 7
# FailoverReactor
# 故障本地快速恢复(将不再从服务器端获取服务列表):每5s执行一次是否开启此功能 cachePath + "/failover/00-00---000-VIPSRV_FAILOVER_SWITCH-000---00-00"
# 服务列表刷盘: 每30d执行一次,第一次延时30m执行一次
# 服务列表刷盘: 每10s执行一次判断 cachePath + "/failover"是否为空,为空执行一次刷盘操作
# 3、自动配置原理
# 3.1 NacosServiceAutoConfiguration(Spring Cloud Alibaba)
创建对象:NacosServiceManager
- 提供创建并缓存NamingService、NamingMaintainService方法
- 提供InstancePreRegisteredEvent监听处理,给缓存配置赋值
# 3.2 NacosDiscoveryAutoConfiguration(Spring Cloud Alibaba)
创建对象:NacosDiscoveryProperties、NacosServiceDiscovery
- NacosDiscoveryProperties初始化后,发布事件:NacosDiscoveryInfoChangedEvent
- NacosDiscoveryProperties提供获取Properties方法
- NacosServiceDiscovery提供通过serviceId获取List<ServiceInstance>
- NacosServiceDiscovery提供获取所有服务名称List<String>
# 3.3 AutoServiceRegistrationConfiguration(Spring Cloud)
创建对象:AutoServiceRegistrationProperties,自动注册参数
# 3.4 AutoServiceRegistrationAutoConfiguration(Spring Cloud)
提供判断开启快速失败情况下AutoServiceRegistration对象不存在抛出异常,使项目启动失败
# 3.5 NacosServiceRegistryAutoConfiguration(Spring Cloud Alibaba)(自动注册、注销)
引入AutoServiceRegistrationConfiguration、AutoServiceRegistrationAutoConfiguration
创建对象:NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration
NacosServiceRegistry
- 实现Spring Cloud Common定义的服务注册接口:ServiceRegistry<Registration>
- 提供注册实例、注销实例、关闭、修改实例状态、获取实例状态
NacosRegistration
- 实现Spring Cloud定义的注册接口和服务实例接口:Registration、ServiceInstance
- 设置元信息参数:
management.endpoints.web.base-path: Environment.getProperty("management.endpoints.web.base-path")
management.port: Environment.getProperty("management.server.port")
management.context-path: Environment.getProperty("management.server.servlet.context-path")
management.address: Environment.getProperty("management.server.address")
preserved.heart.beat.interval
preserved.heart.beat.timeout
preserved.ip.delete.timeout
NacosAutoServiceRegistration
- 继承Spring Cloud定义的自动注册抽象类:AbstractAutoServiceRegistration
- 间接实现Spring Cloud定义的自动注册接口:AutoServiceRegistration
- AbstractAutoServiceRegistration实现接口:ApplicationListener<WebServerInitializedEvent>,当监听到WebServerInitializedEvent事件时,触发自动注册流程
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
- AbstractAutoServiceRegistration方法destroy()有注解:@PreDestroy,当对象消亡前,触发自动注销流程
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
this.serviceRegistry.close();
- NacosAutoServiceRegistration实现方法:Registration getRegistration(),创建NacosRegistration对象
- NacosAutoServiceRegistration实现方法:Registration getManagementRegistration(),不处理
- NacosAutoServiceRegistration实现方法:boolean isEnabled(),根据配置register-enabled判断是否注册
- NacosAutoServiceRegistration监听事件:NacosDiscoveryInfoChangedEvent,触发重新注册
Spring Cloud Common | Spring Cloud Alibaba |
---|---|
Registration | NacosRegistration |
ServiceRegistry | NacosServiceRegistry |
AutoServiceRegistration|AbstractAutoServiceRegistration | NacosAutoServiceRegistration |
# 4、服务发现原理
# 4.1 NacosDiscoveryAutoConfiguration(Spring Cloud Alibaba)
创建对象:NacosDiscoveryProperties、NacosServiceDiscovery
- NacosDiscoveryProperties初始化后,发布事件:NacosDiscoveryInfoChangedEvent
- NacosDiscoveryProperties提供获取Properties方法
- NacosServiceDiscovery提供通过serviceId获取List<ServiceInstance>
- NacosServiceDiscovery提供获取所有服务名称List<String>
# 4.2 NacosDiscoveryClientConfiguration(Spring Cloud Alibaba)
创建对象:NacosDiscoveryClient、NacosWatch
NacosDiscoveryClient
- 实现Spring Cloud定义的自动注册抽象类:DiscoveryClient
- 提供根据serviceId获取服务列表List<\ServiceInstance>、获取所有serviceId,底层调用
NacosServiceDiscovery实现
NacosWatch
- 实现Spring Context定义的生命周期接口:SmartLifecycle
- 项目启动后自动触发开启方法:start()
- 监听自己注册到注册中心服务实例元信息,若有变更,则更新本地缓存服务实例元信息
- 每隔30s发送心跳事件:
HeartbeatEvent(未发现有处理该事件的地方)
# 4.3 SimpleDiscoveryClientAutoConfiguration(Spring Cloud Common)
- 创建对象:SimpleDiscoveryProperties、SimpleDiscoveryClient
- 实现了一个简单的服务发现,配置文件中配置服务信息:spring.cloud.discovery.client.simple
# 4.4 CompositeDiscoveryClientAutoConfiguration(Spring Cloud Common)
- 创建对象:CompositeDiscoveryClient
- 作用聚合所有的DiscoveryClient实例,统一提供服务
模块 | ||
---|---|---|
Spring Cloud Common | ServiceInstance | DiscoveryClient |
SimpleServiceInstance | SimpleDiscoveryClient | |
CompositeDiscoveryClient | ||
Spring Cloud Alibaba | NacosServiceInstance | NacosDiscoveryClient |
# 5、Ribbon服务发现原理
# 5.1 RibbonAutoConfiguration(Spring Cloud Netflix)
RibbonClientSpecification
由注解自动创建@RibbonClients
name: default.org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration configuration: {}
RibbonEagerLoadProperties
ribbon:
eager-load:
enabled: false # 是否提前创建服务提供者上下文,避免第一次访问时才创建
clients: # 需要提前创建服务提供者上下文的服务提供者名称列表
ServerIntrospectorProperties
ribbon:
securePorts:
- 443
- 8443
SpringClientFactory ‼️
设置默认配置类:RibbonClientConfiguration
- IClientConfig:DefaultClientConfigImpl,配置类
- IRule:ZoneAvoidanceRule,路由规则
- IPing:DummyPing,服务探活机制
- ServerList<Server>:ConfigurationBasedServerList,存储服务列表
- ServerListUpdater:PollingServerListUpdater,更新服务列表机制
- ILoadBalancer:ZoneAwareLoadBalancer,负载均衡机制
- ServerListFilter<Server>:ZonePreferenceServerListFilter,服务列表过滤机制
- RibbonLoadBalancerContext:负载均衡上下文,重构url
- RetryHandler:DefaultLoadBalancerRetryHandler,重试处理机制
- ServerIntrospector:DefaultServerIntrospector,服务内省器
设置属性源名称:ribbon;设置属性名称:ribbon.client.name,用于获取上下文
StandardEnvironment: MutablePropertySources: CopyOnWriteArrayList: MapPropertySource: name: ribbon source: Collection$SingletonMap ribbon.client.name: 服务提供者名称
设置配置类集合:List<RibbonClientSpecification>
NamedContextFactory.Specification:带名称和配置的规格
@RibbonClients:引入的规则为全局规格,所有服务上下文均有效
@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
@RibbonClient:引入局部规格,仅单个服务有效
@RibbonClient(name="服务提供者名称", configuration={配置类.class})
LoadBalancerClient‼️
- 实现Spring Cloud定义的负载均衡接口:LoadBalancerClient
- 执行方法:execute(String serviceId, LoadBalancerRequest<T> request),执行请求
- 执行方法:URI reconstructURI(ServiceInstance instance, URI original),重构URI
- 执行方法:ServiceInstance choose(String serviceId),获取服务提供者实例
PropertiesFactory
- 支持对指定的集中类型做定制化,默认配置类
- xx.ribbon.NFLoadBalancerClassName: ILoadBalancer
- xx.ribbon.NFLoadBalancerPingClassName: IPing
- xx.ribbon.NFLoadBalancerRuleClassName: IRule
- xx.ribbon.NIWSServerListClassName: ServerList
- xx.ribbon.NIWSServerListFilterClassName: ServerListFilter
# 5.2 RibbonNacosAutoConfiguration(Spring Cloud Alibaba)
RibbonClientSpecification
由注解自动创建@RibbonClients
name: default.com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration configuration: NacosRibbonClientConfiguration
覆盖默认配置:
- ServerList<?>:NacosServerList
- ServerIntrospector:NacosServerIntrospector
# 5.3 LoadBalancerAutoConfiguration(Spring Cloud Commons)
LoadBalancerRequestFactory‼️
- 将HttpRequest对象封装为LoadBalancerRequest<ClientHttpResponse>
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
return execution.execute(serviceRequest, body);
};
}
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
}
- LoadBalancerRequest:封装HttpRequest为ServiceRequestWrapper(重构 URI)
- LoadBalancerRequest:使用LoadBalancerRequestTransformer转化HttpRequest
LoadBalancerInterceptor‼️
- 实现Spring Cloud定义的http拦截器:ClientHttpRequestInterceptor
- 调用LoadBalancerRequestFactory创建对象LoadBalancerRequest<ClientHttpResponse>
- 调用RibbonLoadBalancerClient执行http请求
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
RestTemplateCustomizer
- 将LoadBalancerInterceptor添加到带有@LoadBalanced注解的RestTemplate中
Spring Cloud | Ribbon | Feign |
---|---|---|
NamedContextFactory.Specification | RibbonClientSpecification | FeignClientSpecification |
NamedContextFactory | SpringClientFactory | FeignContext |
propertySourceName | ribbon | feign |
propertyName | ribbon.client.name | feign.client.name |
defaultConfigType | RibbonClientConfiguration | FeignClientsConfiguration |
FeignContext
Interface | Function |
---|---|
FeignLoggerFactory | DefaultFeignLoggerFactory |
Feign.Builder | HystrixFeign.Builder |
Encoder | SpringEncoder|PageableSpringEncoder |
Decoder | OptionalDecoder|ResponseEntityDecoder|SpringDecoder |
Contract | SpringMvcContract |
Logger.Level | Logger.Level.NONE |
Retryer | Retryer.NEVER_RETRY|Retryer.Default |
ErrorDecoder | ErrorDecoder.Default-封装异常为RetryableException,交给Retryer处理 |
Request.Options | 封装连接超时、读超时、是否允许重定向 |
RequestInterceptor | |
QueryMapEncoder | QueryMapEncoder.Default-解析对象的属性为Map |
Client | LoadBalancerFeignClient|Client.Default |
Targeter | HystrixTargeter |
Feign配置加载优先级
Config Type | Priority(low -> higt) |
---|---|
Feign.Builder(默认值) | 1 |
RibbonClientConfiguration | 2 |
feign.client.config.default | 3 |
feign.client.config.{providerName} | 4 |
1、Feign.Builder.target(Target)
3、Feign.newInstance(Target) -> ReflectiveFeign
4、ReflectiveFeign.ParseHandlersByName.apply(Target)
4、Contract.parseAndValidatateMetadata(Class<?>) -> SpringMvcContract
5、SynchronousMethodHandler.Factory.create(...)
6、InvocationHandlerFactory.Default.create(Target, Map<Method, MethodHandler>)
7、ReflectiveFeign.FeignInvocationHandler(...) -- InvocationHandler
1、ReflectiveFeign.FeignInvocationHandler.invoke(...) -- JDK动态代理(InvocationHandler)
2、SynchronousMethodHandler.invoke(Object[]) -- feign-core封装接口(MethodHandler)
3、Param.Expander.expand(Object) -- 解析参数,默认使用(ConversionService)
4、UriTemplate.expand(variables) -- 解析请求pathVariable参数(SimpleExpression)
5、QueryTemplate.expand(variables) -- 解析请求requestParam参数(SimpleExpression)
7、HeaderTemplate.expandvariables) -- 解析请求header参数(SimpleExpression)
6、BodyTemplate.expand(variables) -- 解析请求body参数(SimpleExpression)
7、Options -- 解析参数中,没有使用默认配置(feign.Request.Options)
8、Retryer -- 发生异常时,重试机制,处理特定异常(RetryableException)
9、RequestInterceptor -- 请求拦截器处理
10、HardCodedTarget -- 编码处理,转化为(****Request****)
--- ribbon相关
11、LoadBalancerFeignClient.execute(Request, Request.Options) -- 解析clientName
12、Request -> FeignLoadBalancer.RibbonRequest -- feign转ribbon请求
13、IClientConfig -- ribbon配置(DefaultClientConfigImpl|FeignOptionsClientConfig)
14、ILoadBalancer -- ribbon负载均衡(ZoneAwareLoadBalancer)
15、ServerIntrospector -- ribbon服务内审器(NacosServerIntrospector)
16、FeignLoadBalancer -- ribbon客户端重写,执行请求(AbstractLoadBalancerAwareClient)
17、RequestSpecificRetryHandler -- ribbon重试处理机制
18、LoadBalancerCommand -- 构建rxjava异步程序
19、Observable -- 观察对象,当负载均衡后获得Server实例后将其传递给订阅者
(FeignLoadBalancer -> ZoneAwareLoadBalancer)
20、Observable -- 观察对象,开启服务调用统计
21、Observable -- 观察对象,通过Server解析URI获取真实地址
22、Observable -- 观察对象,执行请求(Client.Default.execute(Request, Options))
23、Observable -- 观察对象,请求后处理信息统计
24、Observable -- 观察对象,重试处理
25、FeignLoadBalancer.RibbonResponse.toResponse() -- ribbon-feign响应对象转换
Server
- String host
- int port = 80
- String scheme
- volatile String id
- volatile boolean isAliveFlag
- String zone = "UNKNOWN"
- volatile boolean readyToServe = true
- MetaInfo simpleMetaInfo
MetaInfo(interface)
- String getAppName()
- String getServerGroup()
- String getServiceIdForDiscovery()
- String getInstanceId()
ILoadBalancer(interface)
- void addServers(List<Server> newServers)
- Server chooseServer(Object key)
- void markServerDown(Server server)
- List<Server> getServerList(boolean availableOnly) @Deprecated
- List<Server> getReachableServers()
- List<Server> getAllServers()
AbstractLoadBalancer(abstract)
- Server chooseServer() => ILoadBalancer.chooseServer(null)
- abstract List<Server> getServerList(ServerGroup serverGroup)
- abstract LoadBalancerStats getLoadBalancerStats()
AbstractLoadBalancer.ServerGroup(enum)
- ALL|STATUS_UP|STATUS_NOT_UP
BaseLoadBalancer
- IRule rule = new RoundRobinRule()
- IPingStrategy pingStrategy = new SerialPingStrategy()
- IPing ping
- List<Server> allServerList
- List<Server> upServerList
- String name = "default"
- Timer lbTimer
- int pingIntervalSeconds = 10
- int maxTotalPingTimeSeconds = 5
- Comparator<Server> serverComparator = new ServerComparator()
- AtomicBoolean pingInProgress = new AtomicBoolean(false)
- LoadBalancerStats lbStats
- PrimeConnections primeConnections
- volatile boolean enablePrimingConnections = false
- IClientConfig config
- List<ServerListChangeListener> changeListeners
- List<ServerStatusChangeListener> serverStatusListeners
- List<Server> getReachableServers(): upServerList
- 实现了服务ping探活机制:IPingStrategy通过IPing探活所有服务,有变更通知ServerStatusChangeListener
- ping:时间间隔 NFLoadBalancerPingInterval 30s
- 更新服务列表
- 初始化延时:1s
- 更新时间间隔:ServerListRefreshInterval 30s
- 现成数量:DynamicServerListLoadBalancer.ThreadPoolSize 2
- 服务列表区域亲和性过滤器 ZoneAffinityServerListFilter
- 区域:@zone null
- 启动区域亲和性:EnableZoneAffinity false
- 启动区域专区性:EnableZoneExclusivity false
---- zone != null && (zoneAffinity || zoneExclusive) && servers > 0 ====> 过滤出zone区域的服务
---- 根据结果判断是否启用区域亲和性过滤
- 赋值服务列表
---- allServerList、upServerList
- 启用负载均衡
---- ZoneAwareNIWSDiscoveryLoadBalancer.enabled true