服務管理與通訊,基礎原理分析

知了一笑發表於2022-06-08

涉及輕微的原始碼展示,可放心參考;

一、基礎簡介

服務註冊發現是微服務架構中最基礎的能力,下面將從原始碼層面分析實現邏輯和原理,在這之前要先來看下依賴工程的基礎結構,涉及如下幾個核心元件:

  • commons:服務元件的抽象宣告,本文只分析註冊發現與負載均衡;
  • nacos:當下常用的註冊中心元件,用來進行服務管理;
  • feign:服務間通訊互動元件,在服務請求時涉及負載均衡的策略;
  • ribbon:在服務間通訊請求時,提供多種負載均衡的策略實現;

在熟悉工程依賴之間的結構時,還要明白服務間互動的流程和原理,這樣在分析原始碼設計時,有一個清晰的思路與輪廓;如何實現下面的服務互動模式,在閱讀原始碼工程時,圍繞如下兩個核心邏輯:

  • 註冊發現:註冊時如何上報服務的資訊資料,這些資料以怎樣的方式管理;
  • 負載均衡:當請求的服務同時存在多個時,以什麼樣的策略選擇執行請求的服務;

在這裡先簡單的聊一下個人在閱讀原始碼工程時的基本思路,比如微服務元件:通常從配置引數作為切入口,觀察基於引數構建的核心物件,再重點分析物件的管理模式,以及適配的擴充套件能力,最後結合專案的應用場景即可:

閱讀原始碼最重要的是耐著心情慢慢看,並隨手畫下核心流程,實際上如果有一定的程式設計經驗,不管是閱讀什麼工程的原始碼,只要用心去分析單點的實現原理,都算不上過度複雜,但是元件通常為了複用能力,會去適配多種複雜的場景,這樣勢必要採用抽象的封裝和設計模式,原始碼工程的複雜度自然就會相應提高,這個話題後續會細聊。

二、服務註冊

1、服務配置

首先從Nacos配置引數開始,這裡只設定服務發現的兩個引數:1Nacos註冊中心的服務端地址,2在服務的後設資料中載入分支號;然後來具體的看原始碼流程:

在配置引數載入的過程中,有很多預設的預設值,所以需要關注最終會提供的引數資訊,來判斷是否需要自定義設定,另外AutoConfig配置要重點看例項化的物件;斷點的流程可以按照如下的方式做設定,這裡陳列的是在配置載入階段的幾個核心節點:

  • 引數:NacosDiscoveryProperties#getNacosProperties
  • 配置:NacosServiceAutoConfiguration#nacosServiceManager
  • 構建:NacosServiceManager#buildNamingService

NamingService是Nacos服務管理介面,涉及註冊、查詢、撤銷、檢查等多個方法,即對應的是Nacos服務端的相應API請求,在註冊執行的階段會細說用法。

2、註冊構建

看完服務配置之後再看註冊配置,對於配置中複雜的設計,需要重點關注兩個資訊:ConditionalOn和matchIfMissing,這樣很容易發現預設載入:

  • 配置:NacosServiceRegistryAutoConfiguration#nacosServiceRegistry
  • 註冊:NacosServiceRegistry#register
  • 例項:NacosServiceRegistry#getNacosInstanceFromRegistration

在構建服務註冊的核心類NacosServiceRegistry時,通過服務的登記資訊轉換為註冊的例項化物件,然後通過NamingService介面方法,上報例項化物件;需要注意的是,雖然這裡只看了Nacos中的相關API,但實際上API實現了諸多spring-cloud-commons包中宣告的介面,比如Registration、ServiceInstance等。

3、執行上報

通常微服務的註冊中心元件,都是基於server-client架構和部署方式,客戶端需要根據自身啟動狀態去上報或者撤銷註冊,服務端負責統一維護註冊資料:

  • 實現:NacosNamingService#registerInstance
  • 執行:NamingProxy#registerService
  • 介面:InstanceController#register

在最終執行服務註冊時,其動作本質就是請求Nacos服務端的一個Post方法,並將配置資料上報,例如:IP地址、埠、後設資料、權重等;這樣客戶端註冊邏輯執行完成,然後再看服務端資料視覺化介面,就可以看到註冊的客戶端服務。

至於Nacos服務端是如何管理這些註冊資料的,參考部署版本的nacos-naming模組原始碼,閱讀上報介面和頁面中的列表載入的實現即可;注意在初始的配置檔案中,加入的branch分支引數也在後設資料結構中。

在NamingService介面中,涉及多個服務管理的方法,在執行原理上基本相同就不在贅述,這樣註冊中心的Client端和Server端就形成了通訊機制,接下來再看Client端之間的通訊。

三、服務通訊

1、基礎配置

Feign在配置方面比較複雜,提供了多個場景下的適配能力,這裡只以兩個常見的引數作為切入點:1通訊超時時間,2Http選型(採用預設值);

  • 引數:FeignClientProperties#getConfig
  • 註解:FeignClientsRegistrar#registerFeignClients
  • 配置:FeignAutoConfiguration#feignContext
  • 構建:FeignClientFactoryBean#getTarget

這裡要重點關注的是註解的掃描和註冊以及容器管理,要理解Feign的上下文環境需要明白上文中描述的服務間互動原理,然後參考FeignClientFactoryBean工廠類中構建邏輯。

2、通訊邏輯

雖然Feign註解的方式可以簡化開發,但是在具體執行的時候還是Http的請求響應模式,這裡可以參考LoadBalancerFeignClient類中的execute方法:

  • 配置:FeignRibbonClientAutoConfiguration
  • 通訊構建:LoadBalancerFeignClient#execute
  • 負載均衡:AbstractLoadBalancerAwareClient#executeWithLoadBalancer

不管是Feign元件還是Spring框架,預設的負載均衡策略都是採用Ribbon的實現方式,在上述流程中配置和負載均衡命令都依賴Ribbon元件,接下來看服務選擇策略。

四、負載均衡

1、命令構建

這裡構建了呼叫負載均衡介面的命令,ILoadBalancer介面中提供服務管理的相關方法,其中最核心的就是chooseServer方法,然後結合具體的策略規則實現服務的選擇的功能:

  • 命令構建:LoadBalancerCommand.Builder#build
  • 負載容器:LoadBalancerContext#getServerFromLoadBalancer
  • 選擇介面:ILoadBalancer#chooseServer

2、策略規則

Ribbon元件中負載均衡的策略有好幾種規則,比如隨機選擇、Key匹配、權重傾斜等;在工作中常用的就是預設規則即RoundRobinRule,以及基於Key設計的灰度模式,簡單做法就是服務啟動時在後設資料中新增的分支號作為匹配的標識;

  • 規則設定:BaseLoadBalancer#setRule
  • 隨機策略:RoundRobinRule#choose
  • 過濾策略:PredicateBasedRule#choose

現在回到流程的開始看,通過Nacos元件進行服務註冊和管理,通過Feign元件基於Ribbon負載均衡策略做服務通訊,如果單看各節點元件的邏輯還比較容易理解,但是通過Spring框架做元件之間的協作排程時,複雜程度明顯提高;

如果是剛開始閱讀原始碼的階段,可以只關注相應流程的核心邏輯,選擇性忽略細節的實現原理,當然重點還是要多讀讀Spring的設計,這樣時間久了自然會有很多收穫。

五、參考原始碼

程式設計文件:
https://gitee.com/cicadasmile/butte-java-note

應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent

相關文章