Spring Cloud 服務(wù)注冊與發(fā)現(xiàn)源碼筆記 (Nacos/Consul/Eureka)

三合一唉!!!! 超級尊享版!!!!! 肝了一天唉!!!!

Eureka

關(guān)鍵類

# 服務(wù)注冊
1.EurekaClientAutoConfiguration
    注冊了眾多的 bean
    一部分用于和 Eureka Server 交互
    一部分和 Commons 項目對接
    注冊了(EurekaClient/EurekaAutoServiceRegistration/ApplicationInfoManager/EurekaRegistration)
       
2.EurekaClient
    與 Eureka Server 端交互
    負(fù)責(zé)向 Eureka Server 端注冊/注銷服務(wù)實例
    在構(gòu)造方法和 shutdown 方法中根據(jù)配置處理自動注冊和自動注銷.

3.InstanceInfoReplicator
    負(fù)責(zé)定義一個注冊或更新服務(wù)實例的任務(wù)
    負(fù)責(zé)管理任務(wù)執(zhí)行器

4.RestTemplateEurekaHttpClient
    負(fù)責(zé)根據(jù)服務(wù)實例信息構(gòu)造注冊/注銷的 Http 請求
    使用 RestTemplate 發(fā)送請求
    
5.EurekaAutoServiceRegistration
    負(fù)責(zé)管理服務(wù)實例的自動注冊/注銷, 與容器生命周期掛鉤

6.ApplicationInfoManager
    負(fù)責(zé)管理服務(wù)實例狀態(tài)變更事件(即管理監(jiān)聽者并在適當(dāng)時機觸發(fā)他們)

7.ApplicationInfoManager.StatusChangeListener
    狀態(tài)改變事件監(jiān)聽者類
    
8.EurekaHealthCheckHandler
    服務(wù)實例狀態(tài)健康檢查類, 注冊/注銷服務(wù)實例前會調(diào)用此類進(jìn)行檢查其狀態(tài)


# 服務(wù)發(fā)現(xiàn)
1.BlockingLoadBalancer (Commons 項目上)
    負(fù)責(zé)調(diào)用實際的負(fù)載均衡器去選擇一個服務(wù)實例
    負(fù)責(zé)調(diào)度負(fù)載均衡整個過程, 觸發(fā)相應(yīng)的生命周期.
    
2.RoundRobinLoadBalancer (Commons 項目上)
    實際的負(fù)載均衡策略算法類
    負(fù)責(zé)連接 ServiceInstanceListSupplier 從其中獲取服務(wù)實例列表

3.ServiceInstanceListSupplier
    定義了獲取服務(wù)實例實例列表的接口(任意方式)

4.DiscoveryClient
    定義了從服務(wù)端獲取服務(wù)實例列表的接口(更明確了)

4.DiscoveryClientServiceInstanceListSupplier
    ServiceInstanceListSupplier 的實現(xiàn)類
    連接 ReactiveDiscoveryClient 類, 將服務(wù)端獲取道德服務(wù)實例列表返回出去
    
5.EurekaDiscoveryClient
    實現(xiàn)了 DiscoveryClient 接口
    負(fù)責(zé)調(diào)用 EurekaClient 從 Eureka Server 端獲取服務(wù)實例列表.

6.EurekaClient
    與 Eureka Server 端交互
    負(fù)責(zé)向 Eureka Server 端獲取服務(wù)實例列表
    在構(gòu)造方法和 shutdown 方法中根據(jù)配置處理自動注冊和自動注銷.

7.EurekaServiceInstance
    服務(wù)實例信息對象
    實現(xiàn) ServiceInstance, 與 Commons 對接

# 總結(jié)
實現(xiàn)了 Commons 的 DiscoveryClient 接口(即 EurekaDiscoveryClient), 于是服務(wù)發(fā)現(xiàn)實現(xiàn)了;
實現(xiàn)了 Commons 的 ServiceRegisty 接口(即 EurekaServiceRegistry), 于是服務(wù)注冊也實現(xiàn)了.
再總結(jié): Commons 大發(fā)好.

服務(wù)注冊流程

# EurekaClient 構(gòu)造方法觸發(fā)(前提 shouldRegisterWithEureka 為 true)
1.EurekaClientAutoConfiguration 讓容器里注冊了一個 EurekaClient
2.EurekaClient 這個類在構(gòu)造方法中的 initScheduledTasks() 生成了一個 InstanceInfoReplicator 對象
3.然后調(diào)用其 instanceInfoReplicator.start(), 邏輯是添加一個定時任務(wù)(僅執(zhí)行一次), 定時任務(wù)執(zhí)行 run()
4.run() 里面會執(zhí)行 discoveryClient.register() 也就是注冊實例信息到 Eureka 上去.
5.register() 里面會調(diào)用 RestTemplateEurekaHttpClient#register() 
6.這個方法是構(gòu)造一個 HTTP 請求, 地址為 serviceUrl + "apps/" + info.getAppName(), Method 為 POST, 即 Eureka server 的ip/apps/服務(wù)實例名稱(如spring.application.name), 當(dāng)然請求 body 還會帶上實例信息 info 對象.

# 根據(jù)容器生命周期觸發(fā)
1.EurekaClientAutoConfiguration 讓容器里注冊了一個 EurekaAutoServiceRegistration.
2.這個類即是 SmartLifecycle(與容器生命周期綁定), 也是 SmartApplicationListener(監(jiān)聽容器加載/關(guān)閉事件)
3.因此其對應(yīng)的 start()/stop() 和 onApplicationEvent() 都實現(xiàn)了對應(yīng)的邏輯.
4.如 start() 的邏輯為調(diào)用 EurekaServiceRegistry#register()
5.register() 先修改本地服務(wù)實例的裝填, 再通過 com.netflix.discovery.DiscoveryClient#registerHealthCheck() 來往任務(wù)管理器中提交一個任務(wù), 后臺執(zhí)行, 任務(wù) InstanceInfoReplicator#onDemandUpdate()
6.此任務(wù)就是最終也會調(diào)用前面提到的 (4) 中的 run(). 然后就注冊上去了.

# PS
(6) 中的任務(wù), 與狀態(tài)監(jiān)聽是一致的

總結(jié): 容器注冊 EurekaClient, 調(diào)用構(gòu)造方法完成大量初始化工作后, 另起一個線程調(diào)用 restTemplate 發(fā)送 HTTP 請求將當(dāng)前服務(wù)實例信息發(fā)送給 Eureka server. 完成服務(wù)注冊工作.

graph TB

A(EurekaClientAutoConfiguration)
A1(EurekaClient)
A2(InstanceInfoReplicator)
A3(EurekaClient#register)
A4(RestTemplateEurekaHttpClient)
A5(RestTemplate)
A6(Eureka Server)
A7(EurekaHttpResponse)

A--讓容器里注冊一個-->A1
A1--在構(gòu)造方法中生成一個-->A2
A2--在 run 方法中調(diào)用-->A3
A3--又把服務(wù)實例信息交給-->A4
A4--根據(jù)服務(wù)實例信息構(gòu)造HTTP請求-->A5
A5--將服務(wù)實例信息發(fā)送給-->A6
A6--返回一個-->A7


image-20210129171345329

服務(wù)注銷流程

# 容器關(guān)閉(配置了容器 shouldUnregisterOnShutdown=true)
1.在 DiscoveryClient#shutdown() 中根據(jù) shouldUnregisterOnShutdown 判斷是否需要注銷
2.然后在 unregister() 中調(diào)用 RestTemplateEurekaHttpClient#cancel() 
3.cancel() 中使用 restTemplate 構(gòu)造 Method 為 DELETE, URL 為 serviceUrl + "apps/" + appName + '/' + id 的請求并發(fā)送給 Eureka server 告知其注銷 appName 下對應(yīng)的服務(wù)實例(根據(jù) id).

# 接收到容器關(guān)閉事件
1.EurekaClientAutoConfiguration 讓容器里注冊了一個 EurekaAutoServiceRegistration.
2.這個類即是 SmartLifecycle(與容器生命周期綁定), 也是 SmartApplicationListener(監(jiān)聽容器加載/關(guān)閉事件)
3.因此其對應(yīng)的 start()/stop() 和 onApplicationEvent() 都實現(xiàn)了對應(yīng)的邏輯.
4.如 stop() 的邏輯為調(diào)用 EurekaServiceRegistry#deregister()
5.deregister() 的作用是執(zhí)行 ApplicationInfoManager#setInstanceStatus() 將狀態(tài)改為 DOWN
6.因為 ApplicationInfoManager 這個類專門管理狀態(tài)變化事件, 因此還會將事件發(fā)布出去. 
7.而在 initScheduledTasks 中就添加了這樣的一個監(jiān)聽者, 起作用為調(diào)用 InstanceInfoReplicator#onDemandUpdate()
8.接著會調(diào)用 InstanceInfoReplicator#run(), 第一行代碼 refreshInstanceInfo() 會確保狀態(tài)為最新的(那也還是 DOWN)
9.但是其最終并不是調(diào)用 cancel 注銷, 而 register 注冊, 不過其中的狀態(tài)是 DOWN, 因此會有 server 那邊判斷; 所以容器關(guān)閉事件并不會觸發(fā) cancel, 但效果應(yīng)是一樣的.

總結(jié): 要么是 shutdown() 中 unregister 調(diào)用了 cancel(), 發(fā)出了 Method 為 DELETE 的請求來注銷; 要么就是 EurekaClientAutoConfiguration 中與容器生命周期做關(guān)聯(lián), 全程使用 register 接口來更新狀態(tài).

graph TD

A(容器關(guān)閉#close)
A1(DiscoveryClient#shutdown)
A2(DiscoveryClient#unregister)
A3(RestTemplateEurekaHttpClient#cancel)
A4(RestTemplate)
A5(Eureka Server) 

A--觸發(fā)-->A1
A1--調(diào)用-->A2
A2--調(diào)用-->A3
A3--構(gòu)造HTTP請求交給-->A4
A4--發(fā)送注銷申請給-->A5

image-20210129171237603

服務(wù)發(fā)現(xiàn)流程

1.Spring Cloud Commons 中注冊了一個 ServiceInstanceListSupplier, 具體為(DiscoveryClientServiceInstanceListSupplier)
2.這個類的作用是借助 ReactiveDiscoveryClient 的 getInstances(String serviceId) 方法向 LoadBalancer 提供從具體的 server(如Eureka) 獲取服務(wù)實例對象列表, 這樣只要實現(xiàn) ReactiveDiscoveryClient 并放入容器就可以和 Spring cloud LoadBalancer 對接了.
3.在 EurekaDiscoveryClientConfiguration 中讓容器注冊了一個 DiscoveryClient(具體為 EurekaDiscoveryClient).
4.因為 Spring Cloud Commons 做了大量的預(yù)備對接工作, 所以對接其實就結(jié)束了.
5.那再簡單說下 EurekaDiscoveryClient 的實現(xiàn), 即注入一個 EurekaClient eurekaClient, 然后調(diào)用 DiscoveryClient#getInstancesByVipAddress() 就獲取到了 ServiceInstance 列表.

總結(jié): Commons 中準(zhǔn)備好了對接的方式: 實現(xiàn) DiscoveryClient 接口, 接著我們的確實現(xiàn)了 DiscoveryClient 接口, 即 EurekaDiscoveryClient, 而這這個類則會調(diào)用 EurekaClient 的 getInstancesByVipAddress 從 Eureka Server 端獲取注冊了的服務(wù)實例信息.

graph TD

A1(BlockingLoadBalancerClient#位于Commons項目)
A2(RoundRobinLoadBalancer#位于Commons項目)
A4(LoadBalancerClientConfiguration#位于Commons項目)
A5(DiscoveryClientServiceInstanceListSupplier)
A6(EurekaDiscoveryClient#getInstances)
A7(EurekaClient#getInstancesByVipAddress)
A9(Eureka Server)
A8(ServiceInstance 集合)

A1--choose 方法調(diào)用-->A2
A2--choose 方法調(diào)用-->A5
A4--注入一個-->A5
A5--調(diào)用-->A6
A6--調(diào)用-->A7
A7--從-->A9
A9--獲取-->A8
A10(EurekaDiscoveryClientConfiguration)--注入一個-->A6

image-20210129233854498

Consul

關(guān)鍵類

# 服務(wù)注冊
1.ConsulAutoServiceRegistrationAutoConfiguration
    負(fù)責(zé)添加自動注冊/注銷相關(guān)的 bean
    注冊了 ConsulAutoServiceRegistration/ConsulAutoServiceRegistrationListener/ConsulAutoRegistration
    
2.ConsulServiceRegistryAutoConfiguration
    負(fù)責(zé)注冊服務(wù)注冊相關(guān)的 bean
    注冊了 ConsulServiceRegistry
    
3.ConsulDiscoveryClientConfiguration
    負(fù)責(zé)注冊服務(wù)發(fā)現(xiàn)相關(guān)的 bean
    注冊了 ConsulDiscoveryClient
    
4.ConsulServiceRegistry
    負(fù)責(zé)與 ConsulClient 對接, 再提供注冊/注銷功能

5.ConsulClient
    與 Consul Server 端交互
    負(fù)責(zé)向 Consul Server 端注冊/注銷服務(wù)實例

6.AgentConsulClient
    負(fù)責(zé)根據(jù)服務(wù)實例信息構(gòu)造注冊/注銷的 Http 請求
    
7.ConsulAutoServiceRegistration/ConsulAutoServiceRegistrationListener
    負(fù)責(zé)管理服務(wù)實例的自動注冊/注銷, 與容器生命周期掛鉤



# 服務(wù)發(fā)現(xiàn)
1.BlockingLoadBalancer (Commons 項目上)
    負(fù)責(zé)調(diào)用實際的負(fù)載均衡器去選擇一個服務(wù)實例
    負(fù)責(zé)調(diào)度負(fù)載均衡整個過程, 觸發(fā)相應(yīng)的生命周期.
    
2.RoundRobinLoadBalancer (Commons 項目上)
    實際的負(fù)載均衡策略算法類
    負(fù)責(zé)連接 ServiceInstanceListSupplier 從其中獲取服務(wù)實例列表

3.ServiceInstanceListSupplier
    定義了獲取服務(wù)實例實例列表的接口(任意方式)

4.DiscoveryClient
    定義了從服務(wù)端獲取服務(wù)實例列表的接口(更明確了)

4.DiscoveryClientServiceInstanceListSupplier
    ServiceInstanceListSupplier 的實現(xiàn)類
    連接 ReactiveDiscoveryClient 類, 將服務(wù)端獲取道德服務(wù)實例列表返回出去
    
5.ConsulDiscoveryClient
    實現(xiàn)了 DiscoveryClient 接口
    負(fù)責(zé)調(diào)用 ConsulClient 從 Consul Server 端獲取服務(wù)實例列表.

6.ConsulClient
    與 Consul Server 端交互
    負(fù)責(zé)向 Consul Server 端獲取服務(wù)實例列表

7.ConsulServiceInstance
    服務(wù)實例信息對象
    實現(xiàn) ServiceInstance, 與 Commons 對接

# 總結(jié)
實現(xiàn)了 Commons 的 DiscoveryClient 接口(即 ConsulDiscoveryClient), 于是服務(wù)發(fā)現(xiàn)實現(xiàn)了;
實現(xiàn)了 Commons 的 ServiceRegisty 接口(即 ConsulServiceRegistry), 于是服務(wù)注冊也實現(xiàn)了.
再總結(jié): Commons 大發(fā)好.

服務(wù)注冊流程

1.ConsulAutoServiceRegistration 調(diào)用 org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry#register() 完成注冊
2.接著 register() 調(diào)用 ConsulClient#agentServiceRegister()
3.然后會調(diào)用 AgentConsulClient#agentServiceRegister()
4.生成并發(fā)送請求, 請求地址為 /v1/agent/service/register

總結(jié): 其實和 Eureka 差不多, 只是我跳過了一點點細(xì)節(jié). 基本是就是 與 ServiceRegistry 交互了, 非常的配合 Commons 項目, 就像一個人寫的一樣...

服務(wù)發(fā)現(xiàn)流程

1.首先是 DiscoveryClientServiceInstanceListSupplier 會調(diào)用 ConsulDiscoveryClient#getInstances()
2.接著 getInstances() 會調(diào)用 ConsulClient#getHealthServices()
3.然后就是發(fā)送 HTTP 請求了, 請求地址為 /v1/health/service/, 返回的對象封裝處理下得到 ServiceInstance 的實現(xiàn)類 ConsulServiceInstance.

總結(jié): 這次真的是和 Eureka 類似, 從 Commons 到 ConsulDiscoveryClient, 流程是一樣的. 而其實 ConsulDiscoveryClient 的邏輯也和 EurekaDiscoverClient 類似... 只能說其實服務(wù)注冊發(fā)現(xiàn)這個框架, 我們關(guān)注的功能其實并不是難點. 難點是 server 端的管理.

Nacos

關(guān)鍵類

# 服務(wù)注冊
1.NacosServiceRegistryAutoConfiguration
    負(fù)責(zé)添加自動注冊/注銷相關(guān)的 bean
    注冊了 NacosAutoServiceRegistration/NacosServiceRegistry/NacosRegistration
    
2.NacosDiscoveryClientConfiguration
    負(fù)責(zé)注冊服務(wù)發(fā)現(xiàn)相關(guān)的 bean
    注冊了 NacosDiscoveryClient
    
3.NacosServiceRegistry
    負(fù)責(zé)與 NamingService 對接, 再提供注冊/注銷功能

4.NamingService
    與 Nacos Server 端交互
    負(fù)責(zé)向 Nacos Server 端注冊/注銷服務(wù)實例
    調(diào)用 NamingProxy

5.NamingProxy
    負(fù)責(zé)根據(jù)服務(wù)實例信息構(gòu)造注冊/注銷的 Http 請求
    
6.NacosAutoServiceRegistration
    負(fù)責(zé)管理服務(wù)實例的自動注冊/注銷, 與容器生命周期掛鉤

7.NacosRegistration
    本地實例對象, 相比服務(wù)實例數(shù)據(jù)更多


# 服務(wù)發(fā)現(xiàn)
1.BlockingLoadBalancer (Commons 項目上)
    負(fù)責(zé)調(diào)用實際的負(fù)載均衡器去選擇一個服務(wù)實例
    負(fù)責(zé)調(diào)度負(fù)載均衡整個過程, 觸發(fā)相應(yīng)的生命周期.
    
2.RoundRobinLoadBalancer (Commons 項目上)
    實際的負(fù)載均衡策略算法類
    負(fù)責(zé)連接 ServiceInstanceListSupplier 從其中獲取服務(wù)實例列表

3.ServiceInstanceListSupplier
    定義了獲取服務(wù)實例實例列表的接口(任意方式)

4.DiscoveryClient
    定義了從服務(wù)端獲取服務(wù)實例列表的接口(更明確了)

4.DiscoveryClientServiceInstanceListSupplier
    ServiceInstanceListSupplier 的實現(xiàn)類
    連接 ReactiveDiscoveryClient 類, 將服務(wù)端獲取道德服務(wù)實例列表返回出去
    
5.NacosDiscoveryClient
    實現(xiàn)了 DiscoveryClient 接口
    負(fù)責(zé)調(diào)用 NamingService 從 Nacos Server 端獲取服務(wù)實例列表.

6.NamingService
    與 Nacos Server 端交互
    負(fù)責(zé)向 Nacos Server 端獲取服務(wù)實例列表

7.NacosServiceInstance
    服務(wù)實例信息對象
    實現(xiàn) ServiceInstance, 與 Commons 對接

# 總結(jié)
實現(xiàn)了 Commons 的 DiscoveryClient 接口(即 NacosDiscoveryClient), 于是服務(wù)發(fā)現(xiàn)實現(xiàn)了;
實現(xiàn)了 Commons 的 ServiceRegisty 接口(即 ConsulServiceRegistry), 于是服務(wù)注冊也實現(xiàn)了.
再總結(jié): Commons 大發(fā)好.

服務(wù)注冊流程

1.NacosAutoServiceRegistration 調(diào)用 NacosServiceRegistry#register 完成注冊
2.接著 register() 調(diào)用 NacosNamingService#registerInstance()
3.然后會調(diào)用 NamingProxy#registerService()
4.生成并發(fā)送請求, 請求地址為 /nacos/v1/ns/instance

總結(jié): 這和 Consul 差不多; 還是繼承 AbstractAutoServiceRegistration 來與 ServiceRegistry 交互了, 也是非常的配合 Commons 項目啊!!

服務(wù)發(fā)現(xiàn)流程

1.首先是 DiscoveryClientServiceInstanceListSupplier 會調(diào)用 NacosDiscoveryClient#getInstances()
2.接著 getInstances() 會調(diào)用 NacosServiceDiscovery#getInstances()
3.然后就是發(fā)送 HTTP 請求了, 請求地址為 /nacos/v1/ns/instance/list, 返回的對象封裝處理下得到 ServiceInstance 的實現(xiàn)類 NacosServiceInstance.

總結(jié): 依然是和 Eureka/Consul 類似, 從 Commons 到 NacosDiscoveryClient, 流程是一樣的. 而其實 NacosDiscoveryClient 的邏輯也和 ConsulDiscoveryClient/EurekaDiscoverClient 類似, 最終總是發(fā)起 HTTP 查詢 server 端, 所以 server 端的代碼才比較有趣啊.

Nacos Config

Nacos Config Client 加載 nacos server 配置原理

關(guān)鍵類

1.NacosConfigBootstrapConfiguration
    注冊了一個 NacosPropertySourceLocator

2.NacosPropertySourceLocator
    用于從 nacos server 上加載 dataId 對應(yīng)的配置文件到 environment 的 PropertySource 集合中.
    
3.ConfigService
    用于與 nacos server 通信, 獲取配置文件, 乃至訂閱更新
    
4.NacosPropertySourceBuilder
    借助 ConfigService 與 NacosDataParserHandler 從 nacos 獲取 NacosPropertySource
    
5.NacosDataParserHandler
    用于序列化(轉(zhuǎn)碼)服務(wù)端的配置文件數(shù)據(jù)為一個 PropertySource(即 NacosPropertySource)


# 自動刷新
1.NacosConfigAutoConfiguration
2.NacosContextRefresher
    

總結(jié): ConfigService 與 NacosDataParserHandler 是兩個干事的, 其他都是渣渣!!!

從 server 獲取配置流程

1.在 NacosConfigBootstrapConfiguration 中注冊了一個 PropertySourceLocator(即 NacosPropertySourceLocator), 可用于為 environment 添加 PropertySource
2.然后實現(xiàn) locate 方法, 即 NacosPropertySourceLocator#locate()
3.在 locate() 的最后面, 調(diào)用了 loadApplicationConfiguration()
4.這個方法通過多次調(diào)用 loadNacosDataIfPresent() 加載 dataId 和不同文件后綴以及profile 組和得到不同的 dataId.
5.loadNacosDataIfPresent() 調(diào)用了 loadNacosPropertySource()
6.其最終調(diào)用了 NacosPropertySourceBuilder#loadNacosData()
7.loadNacosData() 調(diào)用 ConfigService#getConfig() 
8.getConfig() 會使用 ClientWorker#getServerConfig() 發(fā)起 HTTP 請求從 nacos server 獲取配置文件. 其請求地址是 /v1/cs/configs

還是挺簡單的, 發(fā)個 HTTP 獲取配置文件, 然后轉(zhuǎn)化成一個 PropertySource, 再在 NacosPropertySourceLocator 的 locate 中扔進(jìn) environment 中去. Bingo!!!

從 server 實時更新原理

1.在 NacosConfigAutoConfiguration 注冊了一個 NacosContextRefresher
2.其實現(xiàn)了 ApplicationListener<ApplicationReadyEvent>, 也就是會在容器準(zhǔn)備事件觸發(fā)后調(diào)用 NacosContextRefresher#registerNacosListenersForApplications()
3.在進(jìn)行兩個配置判斷后(即 isRefreshEnabled() 和 PropertySource 的 isRefreshable), 調(diào)用 NacosContextRefresher#registerNacosListener() 
4.這個方法通過 NacosConfigService#addListener() 注冊一個 AbstractSharedListener 監(jiān)聽者到 cacheData.
5.而 ClientWorker#checkConfigInfo() 中添加的 LongPollingRunnable 任務(wù), 會在 run() 中執(zhí)行 getServerConfig() 獲取服務(wù)配置文件, 然后與當(dāng)前緩存對比 md5, 若更新則通過 cacheData 取出剛剛添加的 listener, 觸發(fā)事件(即 receiveConfigInfo() )
6.觸發(fā)后又會再發(fā)布一個 RefreshEvent 事件
7.接著流轉(zhuǎn)到 RefreshEventListener.onApplicationEvent()
8.然后又會調(diào)用 ContextRefresher.refresh(), 這個方法中的 refreshEnvironment() 會調(diào)用 addConfigFilesToEnvironment(), 這方法先復(fù)制一個 StandardEnvironment, 將其放入到 SpringApplication 中, 再調(diào)用 SpringApplication 的 run() 走一遍, 會觸發(fā) NacosPropertySourceLocator.locate(), 于是復(fù)制的 environment 里面有新數(shù)據(jù)了, 再將其拷貝替換到當(dāng)前的 environment. 完成 environment 的刷新. 
9.接著發(fā)布一個 EnvironmentChangeEvent 事件, 用來刷新 @ConfigurationProperties 注解的 Bean. (這么喜歡事件?不愧是寫出 RocketMQ 的阿里啊!!!)
10.接著在 ConfigurationPropertiesRebinder 中接收到這個事件, 觸發(fā) onApplicationEvent()
11.然后會執(zhí)行 ConfigurationPropertiesRebinder#rebind()
12.其邏輯是將 postProcessBeforeInitialization 中存起來的 ConfigurationPropertiesBean 類型的 map 遍歷, 進(jìn)行重新綁定(即 destroyBean 后再 initializeBean, 則會從新配置重新賦值). 

總結(jié): 有的地方(NacosContextRefresher)監(jiān)聽容器初始化添加配置更新的監(jiān)聽者, 自己不干活只發(fā)布配置刷新事件;

有的地方(ClientWorker)添加輪詢?nèi)蝿?wù)獲取服務(wù)端配置文件與本地 cacheData 對比 md5 來判斷是否觸發(fā)配置更新的監(jiān)聽者;

然后有的地方(RefreshEventListener)監(jiān)聽配置刷新事件然后用 SpringApplication.run() 通過觸發(fā) NacosPropertySourceLocator#locate() 為一個復(fù)制出來的 environment 對象添加從服務(wù)端獲取最新配置, 再拷貝到當(dāng)前的 environment 對象, 完成 environment 配置的刷新, 之后發(fā)布 environment 刷新事件;

有的地方(ConfigurationPropertiesRebinder)監(jiān)聽 environment 刷新事件, 然后為 @ConfigurationProperties 注解生成的 bean 重新綁定配置.

再總結(jié): 挺好的, 奧運火炬手啊這是, 不停傳遞!!!!!

關(guān)鍵類

1.NacosConfigAutoConfiguration
    注冊一個監(jiān)聽容器初始化事件的 bean (NacosContextRefresher)

2.NacosContextRefresher
    再初始化事件觸發(fā)后添加一個 AbstractSharedListener 監(jiān)聽者到 cacheData

3.AbstractSharedListener
    當(dāng)服務(wù)端配置與本地不同后觸發(fā)
    觸發(fā)后發(fā)布 RefreshEvent 事件

4.CacheData
    管理配置更新事件監(jiān)聽者
    對比服務(wù)端配置與本地配置的 md5
    md5 不同則觸發(fā) AbstractSharedListener
    
5.RefreshEvent
    通過容器發(fā)布
    當(dāng)服務(wù)端配置發(fā)送更新觸發(fā)
    觸發(fā)后調(diào)用 ContextRefresher#refresh()
    
6.LongPollingRunnable
    輪詢?nèi)蝿?wù), 獲取服務(wù)端最新配置, 與 cacheData 對比

7.ClientWorker
    管理輪詢?nèi)蝿?wù)(LongPollingRunnable)
    負(fù)責(zé)與 nacos 服務(wù)端通信
    
8.ContextRefresher
    監(jiān)聽 RefreshEvent 事件
    負(fù)責(zé)獲取服務(wù)端最新配置到 environment 對象中
    發(fā)布 EnvironmentChangeEvent 事件
    
9.EnvironmentChangeEvent
    通過容器發(fā)布
    當(dāng) environment 對象更新后觸發(fā)
    觸發(fā)后調(diào)用 ConfigurationPropertiesRebinder#rebind()
    
10.ConfigurationPropertiesRebinder
    監(jiān)聽 EnvironmentChangeEvent 事件
    負(fù)責(zé)重新綁定用了 @ConfigurationProperties 注解的 Bean 的值

graph TB

A1(NacosConfigAutoConfiguration)
A2(NacosContextRefresher)
A3(ApplicationReadyEvent)
A4(NacosContextRefresher#registerNacosListener)
A5(NacosConfigService#addListener)
 
A7(ClientWorker)
A8(LongPollingRunnable#輪詢?nèi)蝿?wù))
A9(NacosConfigService#getServerConfig)
B1(AbstractSharedListener#配置更新事件的監(jiān)聽者)
B2(cacheData)
B3(RefreshEvent)
B4(RefreshEventListener)
B5(ContextRefresher#refresh)
B6(ContextRefresher#refreshEnvironment)
B7(ContextRefresher#addConfigFilesToEnvironment)
B8(SpringApplication#run)
B9(StandardEnvironment)
C1(NacosPropertySourceLocator#locate)
C2(當(dāng)前容器的 environment 對象)
C3(EnvironmentChangeEvent)
C4(ConfigurationPropertiesRebinder#onApplicationEvent)
C5(ConfigurationPropertiesRebinder#rebind)
C6(用了 ConfigurationProperties 注解 Bean)

A1--注冊了一個-->A2
A2--監(jiān)聽了-->A3
A3--事件觸發(fā)后調(diào)用-->A4
A4--通過-->A5
A5--注冊了一個-->B1

 
A7--構(gòu)造方法中創(chuàng)建了一個-->A8
A8--run 方法中調(diào)用-->A9
A9--獲取最新配置與-->B2

B2--比較 md5, 不同則觸發(fā)-->B1
B1--事件觸發(fā)后發(fā)布-->B3
B4--監(jiān)聽了-->B3
B3--事件觸發(fā)后調(diào)用-->B5
B5--調(diào)用-->B6
B6--調(diào)用-->B7
B7--利用-->B8
B7--復(fù)制當(dāng)前容器 environment 對象到-->B9
B8--觸發(fā)-->C1
C1--獲取了最新配置到-->B9
B9--復(fù)制最新配置回到-->C2

C2--更新完配置后發(fā)布-->C3
G1(ConfigurationPropertiesRebinder)--監(jiān)聽了-->C3
C3--事件觸發(fā)后調(diào)用-->C4
C4--調(diào)用-->C5
C5--獲取新配置綁定到-->C6



image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逃魄,隨后出現(xiàn)的幾起案子毛雇,更是在濱河造成了極大的恐慌莺债,老刑警劉巖迈着,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浊竟,死亡現(xiàn)場離奇詭異暴氏,居然都是意外死亡斯议,警方通過查閱死者的電腦和手機撒轮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門乞旦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人题山,你說我怎么就攤上這事兰粉。” “怎么了顶瞳?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵玖姑,是天一觀的道長愕秫。 經(jīng)常有香客問我,道長焰络,這世上最難降的妖魔是什么戴甩? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮闪彼,結(jié)果婚禮上甜孤,老公的妹妹穿的比我還像新娘。我一直安慰自己畏腕,他們只是感情好缴川,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著描馅,像睡著了一般把夸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铭污,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天恋日,我揣著相機與錄音,去河邊找鬼嘹狞。 笑死岂膳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刁绒。 我是一名探鬼主播闷营,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼知市!你這毒婦竟也來了傻盟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嫂丙,失蹤者是張志新(化名)和其女友劉穎娘赴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跟啤,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡诽表,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了隅肥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竿奏。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖腥放,靈堂內(nèi)的尸體忽然破棺而出泛啸,到底是詐尸還是另有隱情,我是刑警寧澤秃症,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布候址,位于F島的核電站吕粹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏岗仑。R本人自食惡果不足惜匹耕,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荠雕。 院中可真熱鬧稳其,春花似錦、人聲如沸舞虱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矾兜。三九已至,卻和暖如春患久,著一層夾襖步出監(jiān)牢的瞬間椅寺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工蒋失, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留返帕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓篙挽,卻偏偏與公主長得像荆萤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铣卡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容