Spring Cloud Alibaba Nacos

2022/6/22 nacosspring

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

  • 提供创建并缓存NamingServiceNamingMaintainService方法
  • 提供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

Auto Registration

  • 继承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

Spring Cloud 服务发现调用

# 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中

ribbon调用流程

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