簡介
????????Spring Cloud Ribbon是一個基于HTTP和TCP的客戶端負載均衡工具嗅蔬,它基于Netflix Ribbon實現(xiàn)。通過Spring Cloud的封裝日矫,可以讓我們輕松地將面向服務的REST模版請求自動轉換成客戶端負載均衡的服務調用斋攀。Spring Cloud Ribbon雖然只是一個工具類框架,它不像服務注冊中心、配置中心钾虐、API網關那樣需要獨立部署,但是它幾乎存在于每一個Spring Cloud構建的微服務和基礎設施中。因為微服務間的調用酿矢,API網關的請求轉發(fā)等內容,實際上都是通過Ribbon來實現(xiàn)的非洲,包括后續(xù)我們將要介紹的Feign段誊,它也是基于Ribbon實現(xiàn)的工具。所以排抬,對Spring Cloud Ribbon的理解和使用懂从,對于我們使用Spring Cloud來構建微服務非常重要。
????????在這一章中蹲蒲,我們將具體介紹如何使用Ribbon來實現(xiàn)客戶端的負載均衡番甩,并且通過源碼分析來了解Ribbon實現(xiàn)客戶端負載均衡的基本原理。
客戶端負載均衡
????????負載均衡在系統(tǒng)架構中是一個非常重要届搁,并且是不得不去實施的內容缘薛。因為負載均衡是對系統(tǒng)的高可用、網絡壓力的緩解和處理能力擴容的重要手段之一卡睦。我們通常所說的負載均衡都指的是服務端負載均衡宴胧,其中分為硬件負載均衡和軟件負載均衡。硬件負載均衡主要通過在服務器節(jié)點之間按照專門用于負載均衡的設備表锻,比如F5等恕齐;而軟件負載均衡則是通過在服務器上安裝一些用于負載均衡功能或模塊等軟件來完成請求分發(fā)工作,比如Nginx等瞬逊。不論采用硬件負載均衡還是軟件負載均衡显歧,只要是服務端都能以類似下圖的架構方式構建起來:
????????硬件負載均衡的設備或是軟件負載均衡的軟件模塊都會維護一個下掛可用的服務端清單,通過心跳檢測來剔除故障的服務端節(jié)點以保證清單中都是可以正常訪問的服務端節(jié)點确镊。當客戶端發(fā)送請求到負載均衡設備的時候士骤,該設備按某種算法(比如線性輪詢、按權重負載骚腥、按流量負載等)從維護的可用服務端清單中取出一臺服務端端地址敦间,然后進行轉發(fā)。
????????而客戶端負載均衡和服務端負載均衡最大的不同點在于上面所提到服務清單所存儲的位置束铭。在客戶端負載均衡中廓块,所有客戶端節(jié)點都維護著自己要訪問的服務端清單,而這些服務端端清單來自于服務注冊中心契沫,比如上一章我們介紹的Eureka服務端带猴。同服務端負載均衡的架構類似,在客戶端負載均衡中也需要心跳去維護服務端清單的健康性懈万,默認會創(chuàng)建針對各個服務治理框架的Ribbon自動化整合配置拴清,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration靶病,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在實際使用的時候口予,我們可以通過查看這兩個類的實現(xiàn)娄周,以找到它們的配置詳情來幫助我們更好地使用它。
????????通過Spring Cloud Ribbon的封裝沪停,我們在微服務架構中使用客戶端負載均衡調用非常簡單煤辨,只需要如下兩步:
??????????服務提供者只需要啟動多個服務實例并注冊到一個注冊中心或是多個相關聯(lián)的服務注冊中心。
??????????服務消費者直接通過調用被@LoadBalanced注解修飾過的RestTemplate來實現(xiàn)面向服務的接口調用木张。
????????這樣众辨,我們就可以將服務提供者的高可用以及服務消費者的負載均衡調用一起實現(xiàn)了。
前提
在上一章中舷礼,我們已經通過引入Ribbon實現(xiàn)了服務消費者的客戶端負載均衡功能鹃彻,讀者可用通過查看第3章中的“服務發(fā)現(xiàn)與消費”一節(jié)來獲取實驗示例。其中妻献,我們使用了非常有用的對象RestTemplate蛛株。該對象會使用Ribbon的自動化配置,同時通過配置@LoadBalanced還能夠開啟客戶端負載均衡旋奢。之前我們演示了通過RestTemplate實現(xiàn)了最簡單的服務訪問泳挥,下面我們將詳細介紹RestTemplate針對幾種不同請求類型和參數類型的服務調用實現(xiàn)。
GET 請求
????????在RestTemplate中至朗,對GET請求可以通過如下兩個方法進行調用實現(xiàn)屉符。
????????第一種:getForEntity函數。該方法返回的是ResponseEntity锹引,該對象是Spring對HTTP請求響應的封裝矗钟,其中主要存儲了HTTP的幾個重要元素,比如HTTP請求狀態(tài)碼的枚舉對象HttpStatus(也就是我們常說的404嫌变、500這些錯誤碼)吨艇、在它的父類HttpEntity中還存儲著HTTP請求的頭信息對象HttpHeaders以及泛型類型的請求體對象。比如下面的例子腾啥,就是訪問USER-SERVER服務端/user請求东涡,同時最后一個參數didi會替換url中的{1}占位符,而返回的ResponseEntity對象的body內容類型會根據第二個參數轉換為String類型倘待。
????????若我們希望返回body是一個User對象類型疮跑,也可以這樣實現(xiàn):
????????上面的例子是比較常用的方法,getForEntity函數實際上提供了以下三種不同的重載實現(xiàn)凸舵。
??getForEntity(String url, Class responseType祖娘,Object... urlVariables);
????????該方法提供了三個參數啊奄,其中url為請求的地址渐苏,responseType為請求響應體body的包裝類型掀潮,urlVariables為url中的參數綁定。GET請求的參數綁定通過使用url中拼接的方式琼富,比如http://USER-SERVICE/user?name=didi仪吧,我們可以像這樣自己將參數拼接到 url中,但更好的方法是在url中使用占位符并配合urlVariables參數實現(xiàn)GET請求的參數綁定鞠眉,比如url定義為:getForEntity("http://USER-SERVICE/user?name={1}", String.class, "didi")邑商,其中第三個參數didi會替換掉url中的{1}站位符。這里需要注意的是凡蚜,由于urlVariables參數是一個數組,所以它的順序會對應url中占位符定義的數字順序吭从。
??getForEntity(String url, Class responseType, Map urlVariables);
????????該方法提供的參數重朝蜘,只有urlVariables的參數類型與上面的方法不同。這里使用了Map類型涩金,所以使用該方法進行參數綁定時需要再占位符中指定Map中的參數的key值谱醇,比如url定義為http://USER-SERVICE/user?name={name},在Map類型的urlVariables中步做,我們就需要put一個key為name的參數來綁定url中{name}占位符的值副渴,比如:
??getForEntity(URI url, Class responseType)
????????該方法使用uri對象來代替之前url和urlVariables參數來指定訪問地址和參數綁定。URI是JDK java.net包下單一個類全度,它表示一個統(tǒng)一資源標識符(Uniform Resource Identifier)引用煮剧,比如下面的例子:
????????更多關于如何定義一個URI的方法可以參見JDK文檔,這里不做詳細說明将鸵。
????????第二種:getForObject函數勉盅。該方法可以理解為對getForEntity的進一步封裝,它通過HttpMessageConverterExtractor對HTTP的請求響應體body內容進行對象轉換顶掉,實現(xiàn)請求直接返回包裝好的對象內容草娜。比如:
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
????????當body是一個User對象時,可以直接這樣實現(xiàn):
RestTemplate restTemplate = new RestTemplate();
User result = restTemplate.getForObject(uri, User.class);
????????當不需要關注請求響應除body外的其他內容時痒筒,該函數就非常好用宰闰,可以少一個從Response中獲取body的步驟。它與getForEntity函數類似簿透,也提供了三種不同的重載實現(xiàn)移袍。
??getForObject(String url, Class responseType, Object ... urlVariables)
??getForObject(String url, Class responseType, Map urlVariables)
??getForObject(URI url, Class responseType)
POST 請求
????????在RestTemplate中,對POST請求時可以通過如下三個方法調用實現(xiàn)萎战。
??第一種:postForEntity函數咐容。
????????該方法同GET請求中的getForEntity類似,會在調用后返回ResponseEntity對象蚂维,其中T為請求響應的body類型戳粒。比如下面這個例子路狮,使用postForEntity提交POST請求到USER-SERVICE服務的/user接口,提交的body內容為user對象蔚约,請求響應返回的body類型為String奄妨。
postForEntity函數也實現(xiàn)了三種不同的重載方法。
??postForEntity(String url, Object request, Class responseType, Object... uriVariables)
??postForEntity(String url, Object request, Class responseType, Map uriVariables)
??postForEntity(URI url, Object request, Class responseType)
????????這些函數中的參數用法大部分與getForEntity一致苹祟,比如砸抛,第一個重載函數和第二個重載函數中的uriVariables參數都用來對url中的參數進行綁定使用;responseType參數是對請求響應的body內容的類型定義树枫。這里需要注意的是新增加的request參數直焙,該參數可以是一個普通對象,也可以是一個HttpEntity對象砂轻。如果是普通對象奔誓,而非HttpEntity對象的時候,RestTemplate會將請求對象轉換為一個HttpEntity對象來處理搔涝;其中Object就是request的類型厨喂,request內容會唄視作完整的body來處理;而如果request是一個HttpEntity對象庄呈,那么就會被當作一個完成的HTTP請求對象來處理蜕煌,這個request中不僅包含了body的內容,也包含了header的內容诬留。
??第二種:postForObject函數斜纪。
????????該方法也跟getForObject的類型類似,它的作用就是簡化postForEntity的后續(xù)處理故响。通過直接將請求響應的body內容包裝成對象來返回使用傀广,比如下面的例子:
????????postForObject函數也實現(xiàn)了三種不同的重載方法:
??postForObject(String url, Object request, Class responseType, Object... uriVariables)
??postForObject(String url, Object request, Class responseType, Map uriVariables)
??postForObject(URI url, Object request, Class responseType)
????????這三個函數除了返回的對象類型不同,函數的傳入參數均與postForEntity一致彩届,因此可參考之前postForEntity的說明伪冰。
??第三種:postForLocation函數。
????????該方法實現(xiàn)了以POST請求提交資源樟蠕,并返回新的資源的URI贮聂,比如下面的例子:
????????postForLocation函數也實現(xiàn)了三種不同的重載方法:
??postForLocation(String url, Object request, Object... uriVariables)
??postForLocation(String url, Object request, Map uriVariables)
??postForLocation(URI url, Object request)
????????由于postForLocation函數會返回新資源的URI,該URI就相當于指定了返回類型寨辩,所以此方法實現(xiàn)的POST請求不需要像postForEntity和postForObject那樣指定responseType吓懈。其他的參數用法相同。
Ribbon源碼分析
????????相信很多熟悉Spring的讀者看到這里一定會產生這樣的疑問:RestTemplate不是Spring自己提供的嘛靡狞?跟Ribbon的客戶端負載均衡又有什么關系呢耻警?在本節(jié)中,我們透過現(xiàn)象看本質,探索一下Ribbon是如何通過RestTemplate實現(xiàn)客戶端負載均衡的甘穿。
????????首先腮恩,回顧一下之前的消費者示例:我們是如何實現(xiàn)客戶端負載均衡的?仔細觀察一下之前的實現(xiàn)代碼温兼,可以發(fā)現(xiàn)載消費者的例子中秸滴,可能就@LoadBalanced這個注解是之前沒有接觸過的,并且從命名上來看也與負載均衡相關募判。我們不妨以此為線索來看看Spring Cloud Ribbon的源碼實現(xiàn)荡含。
????????從@LoadBalanced注解碼的注釋中,可以知道該注解用來給RestTemplate標記届垫,以使用負載均衡的客戶端(LoadBalancerClient)來配置它释液。
LoadBalancerClient
org.springframework.cloud.client.loadbalancer.LoadBalancerClient
英文釋義:
??????????為了給一些系統(tǒng)使用,創(chuàng)建一個帶有真實host和port的URI装处。
? ? ? ? ??一些系統(tǒng)使用帶有原服務名代替host的URI均澳,比如http://myservice/path/to/service。
??????????該方法會從服務實例中取出host:port來替換這個服務名符衔。
補充:父接口ServiceInstanceChooser
????????從該接口中,我們可以通過定義的抽象方法來了解到客戶端負載均衡器中應具備的幾種能力:
??ServiceInstance choose(String serviceId):父接口ServiceInstanceChooser的方法糟袁,根據傳入的服務名serviceId判族,從負載均衡器中挑選一個對應服務的實例。
?? T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):使用從負載均衡器中挑選出的服務實例來執(zhí)行請求內容项戴。
??URI reconstructURI(ServiceInstance instance, URI original):為系統(tǒng)構建一個合適的host:port形式的URI形帮。在分布式系統(tǒng)中,我們使用邏輯上的服務名稱作為host來構建URI(替代服務實例的host:port形式)進行請求周叮,比如http://myservice/path/to/service辩撑。在該操作的定義中,前者ServiceInstance對象是帶有host和port的具體服務實例仿耽,而后者URI對象則是使用邏輯服務名定義為host的URI合冀,而返回的URI內容則是通過ServiceInstance的服務實例詳情拼接host:port形式的請求地址。
????????順著LoadBalancerClient接口的所屬包org.springframework.cloud.client.loadbalancer项贺,我們對其內容進行整理君躺,可以得出如下圖的關系:
從類的命名上可初步判斷LoadBalancerAutoConfiguration為實現(xiàn)客戶端負載均衡器的自動化配置類。通過查看源碼我們可以驗證這一點假設:
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
????????從LoadBalancerAutoConfiguration類上的注解可知开缎,Ribbon實現(xiàn)負載均衡自動化配置需要滿足下面兩個條件:
??@ConditionalOnClass(RestTemplate.class):RestTemplate必須存在于當前工程的環(huán)境中棕叫。
??@ConditionalOnBean(LoadBalancerClient.class):在Spring的Bean工程中必須有LoadBalancerClient的實現(xiàn)bean。
????????該自動配置類奕删,主要做了下面三件事:
????????創(chuàng)建了一個LoadBalancerInterceptor的Bean俺泣,用于實現(xiàn)對客戶端發(fā)起請求時進行攔截,以實現(xiàn)客戶端負載均衡。
????????創(chuàng)建了一個RestTemplateCustomizer的Bean伏钠,用于給RestTemplate增加LoadbalancerInterceptor
????????維護了一個被@LoadBalanced注解修飾的RestTemplate對象列表横漏,并在這里進行初始化,通過調用RestTemplateCustomizer的實例來給需要客戶端負載均衡的RestTemplate增加LoadBalancerInterceptor攔截器贝润。
????????接下來绊茧,我們看看LoadBalancerInterceptor攔截器是如何將一個普通的RestTemplate
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
????????通過源碼以及之前的自動化配置類,我們可以看到在攔截器中注入了LoadBalancerInterceptor的實現(xiàn)打掘。當一個被@LoadBalanced注解修飾的RestTemplate對象向外發(fā)起HTTP請求時华畏,會被LoadBalancerInterceptor類的intercept函數所攔截。由于我們在使用RestTemplate時候采用了服務名作為host尊蚁,所以直接從HttpRequest的URI對象中通過getHost()就可以拿到服務名亡笑,然后調用execute函數去根據服務名來選擇實例并發(fā)起實際的請求。
????????分析到這里横朋,LoadBalancerClient還只是一個抽象的負載均衡器接口仑乌,所以我們還需要找到它的具體實現(xiàn)類進一步進行分析,通過查看Ribbon的源碼琴锭,可以很容易地在org.springframework.cloud.netflix.ribbon包下找到對應的實現(xiàn)類RibbonLoadBalancerClient晰甚。
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
????????在execute函數的實現(xiàn)中,第一步做的就是通過getServer根據傳入的服務名serviceId去獲得具體的服務實例:
????????通過getServer函數的實現(xiàn)源碼决帖,我們可以看到這里獲取具體服務實例的時候并沒有使用LoadBalancerClient接口中的choose函數厕九,而是使用了ribbon自身的ILoadBalancer接口中定義的chooseServer函數。
????????可以看到地回,在該接口中定義了一個客戶端負載均衡器需要的一系列抽象動作:
????????addServers:向負載均衡器中維護的實例列表增加服務實例扁远。
????????chooseServer:通過某種策略,從負載均衡器中挑選出一個具體實例刻像。
????????markServerDwon:用來通知和標識負載均衡器中某個實例已經停止服務畅买,不然負載均衡器在下一次獲取服務實例清單前都會認為服務實例均是正常服務的。
????????getReachableServers:獲取當前正常服務的實例列表细睡。
????????getAllServers:獲取所有已知的服務實例列表谷羞,包括正常服務和停止服務的實例。
????????在該接口定義中涉及到的server對象定義的是一個傳統(tǒng)的服務端節(jié)點溜徙,在該類中存儲了服務端節(jié)點的一些元數據信息洒宝,包括:host,port以及一些部署信息等萌京。
????????而對于該接口的實現(xiàn)雁歌,我們可以整理出如上圖所示的結構。我們可以看到BaseloadBalancer類實現(xiàn)了基礎的負載均衡知残,而DynamicServerListLoadBalancer和ZoneAwareLoadBalancer在負載均衡的策略上做了一些功能的擴展靠瞎。
那么在整合Ribbon的時候Spring Cloud默認采用了那個具體實現(xiàn)呢?
????????org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
? ??從RibbonClientConfiguration配置類,可以知道在整合時默認采用ZoneAvoidanceRule來實現(xiàn)負載均衡器乏盐。
????????下面佳窑,我們再回到RibbonLoadBalancer的execute函數邏輯,在通過ZoneAwareLoaderBalancer的chooseServer函數獲取了負載均衡策略分配到的服務實例對象server之后父能,將其內容包裝成RibbonServer對象(該對象除了存儲了服務實例的信息之外神凑,還增加了服務名serviceId、是否需要使用HTTPS等其他信息)何吝,然后使用該對象再回調LoadBalancerInterceptor請求攔截器中LoadBalancerRequest的apply(final ServiceInstance instance)函數溉委,向一個實際的具體服務實例發(fā)起請求,從而實現(xiàn)一開始以服務名為host的URI請求爱榕,到實際的host:post形式的具體地址的轉換瓣喊。
????????apply(ServiceInstance instance)函數中傳入的ServiceIntance接口是對服務實例的抽象定義。在該接口中暴露服務治理系統(tǒng)中每個服務實例需要提供的一些基本信息黔酥,比如:serviceId藻三、host、port等跪者,具體定義如下:
????????org.springframework.cloud.client.ServiceInstance;
? ??????apply(ServiceInstance instance)函數中傳入的ServiceIntance接口是對服務實例的抽象定義棵帽。在該接口中暴露服務治理系統(tǒng)中每個服務實例需要提供的一些基本信息,比如:serviceId渣玲、host岖寞、port等,具體定義如下:
????org.springframework.cloud.client.ServiceInstance;
????????上面提到的具體包裝Server服務實例的RibbonServer對象就是ServiceInstance接口的實現(xiàn)柜蜈,可以看到它除了包含了server對象之外,還存儲了服務名指巡、是否使用https標識以及一個map類型的元數據集合。
????????那么apply(final ServiceInstance instance)函數,在接收到了具體ServiceInstance實例后泼橘,是如何通過LoadBalancerClient接口中的reconstructURI操作來組織具體請求地址的呢魁索?
apply(ServiceInstance instance)的兩個實現(xiàn)
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerInterceptor
org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory
????????從apply的實現(xiàn)中,我們可以看到它具體執(zhí)行的時候勉耀,還創(chuàng)建了ServiceRequestWrapper對象指煎,該對象繼承了HttpRequestWrapper并重寫了getURI函數,重寫后的getURI會通過調用LoadBalancerClient接口的reconstructURI函數來重新構建一個URI進行訪問便斥。
org.springframework.cloud.client.loadbalancer.ServiceRequestWrapper
????????在LoadBalancerInterceptor攔截器中至壤,InterceptingRequestExecution的實例具體執(zhí)行exection.execute(serviceRequest,body)時,會調用InterceptingClientHttpRequest的內部類InterceptingRequestExecution類的execute函數枢纠,具體實現(xiàn)如下:
org.springframework.http.client.InterceptingClientHttpRequest的內部類
????????可以看到在創(chuàng)建請求的時候requestFactory.createRequest(request.getURI,request.getMethod)像街,這里的會調用之前介紹的ServiceRequestWrapper對象中重寫的getURI函數。
? ??????此時,它就會使用RibbonLoadBalancerClient中實現(xiàn)的reconstructURI來組織具體請求的服務實例地址镰绎。
?? ?? ? ????從reconstructURI函數中脓斩,我們可以看到,它通過ServiceInstance實例對象的servicId畴栖,從SpringClientFactory類的clientFactory對象中獲取對應serviceId的負載均衡器的上下文RibbonLoadBalancerContext對象随静。然后根據ServiceInstance中的信息來構建具體服務實例信息的server對象,并使用RibbonLoadBalancerContext對象的reconstructURIWithServer函數來構建服務實例等URI吗讶。
????????為了幫助理解燎猛,簡單介紹一下上面提到的SpringClientFactory和RibbonLoadBalancerContext:
??????????SpringClientFactory類是一個用來創(chuàng)建客戶端負載均衡器的工廠類,該工廠類會為每一個不同名的Ribbon客戶端生成不同的Spring上下文关翎。
??????????RibbonLoadBalancerContext類是LoadBalancerContext的子類扛门,該類用于存儲一些被負載均衡使用的上下文內容和API操作(reconstructURIWithServer就是其中之一)
com.netflix.loadbalancer.LoadBalancerContext;
????????從LoadBalancerContext的reconstructURIWithServer函數中我們可以看到纵寝,它同reconstructURI的定義類似论寨。只是reconstructURI的第一個保存具體服務實例的參數使用了Spring Cloud定義的ServiceInstance,而reconstructURIWithServer中使用了Netflix中定義的server爽茴,所以在RibbonLoadBalancerClient實現(xiàn)reconstructURI時候葬凳,做了一次轉換,使用ServiceInstance的host和port信息來構建了一個Server對象來給reconstructURIWithServer使用室奏。從reconstructURIWithServer的實現(xiàn)邏輯中火焰,我們可以看到,它從Server對象中獲取host和port信息胧沫,然后根據以服務名為host的URI對象original中獲取其他請求信息 昌简,將兩者內容進行拼接整合,形成最終要訪問的服務實例等具體地址绒怨。
????????另外纯赎,從RibbonLoadBalancerClient的execute的函數邏輯中,我們還能看到在回調攔截器中南蹂,執(zhí)行具體的請求后犬金,ribbon還通過RibbonStatsRecorder對象對服務的請求還進行了跟蹤記錄,有興趣的讀者可以繼續(xù)研究六剥。
????????分析到這里晚顷,我們已經可以大致理清Spring Cloud Ribbon中實現(xiàn)客戶端負載均衡的基本脈絡,了解了它是如何通過LoadBalancerInterceptor攔截器對RequestTemplate的請求進行攔截疗疟,并利用Spring Cloud的負載均衡器LoadBalancerClient將以服務名為host的URI轉換成具體的服務實例地址的過程该默。同時通過分析LoadBalancerClient的Ribbon實現(xiàn)RibbonLoadBalancerClient,可以知道在使用Ribbon實現(xiàn)負載均衡器的時候策彤,實際使用的還是Ribbon中定義ILoadBalancer接口的實現(xiàn)权均,自動化配置會采用ZoneAwareLoadBalancer的實例來實現(xiàn)客戶端負載均衡顿膨。
負載均衡器
????????通過之前的分析,我們已經對Spring Cloud如何使用有了基本的了解叽赊。雖然Spring Cloud中定義了LoadBalancerClient為負載均衡器等接口恋沃,并且針對Ribbon實現(xiàn)了RibbonLoadBalancerClient,但是它在具體實現(xiàn)客戶端負載均衡時必指,則是通過Ribbon的ILoadBalancer接口實現(xiàn)囊咏。下面我們根據ILoadBalancer接口的實現(xiàn)類逐個看看它都是如何實現(xiàn)客戶端負載均衡的。
??AbstractLoadBalancer
com.netflix.loadbalancer.AbstractLoadBalancer
????????AbstractLoadBalancer是ILoadBalancer接口的抽象實現(xiàn)塔橡。在該抽象類中定義了一個關于測試服務實例的分組枚舉類ServerGroup梅割,它包含了三種不同類型:
????????ALL:所有服務實例
????????STATUS_UP:正常服務的實例
????????STATUS_NOT_UP:停止服務的實例
????????另外,還實現(xiàn)了一個chooseServer()函數葛家,該函數通過調用接口中的chooseServer(Object key)實現(xiàn)户辞,其中參數key為null,表示在選擇具體服務實例時忽略key的條件判斷癞谒。
最后還定義了兩個抽象函數:
? ? ? ? ?getServerList(ServerGroup serverGroup):定義了根據分組類型來獲取不同的服務實例列表
????????getLoadBalancerStats():定義了獲取LoadBalancerStats對象的方法底燎,loadBalancerStats對象被用來存儲負載均衡器中各個服務實例當前的屬性和統(tǒng)計信息,這些信息非常有用弹砚,我們可以利用這些信息來觀察負載均衡的運行情況双仍,同時這些信息也是用來制定負載均衡策略的重要依據。
??BaseLoadBalancer
com.netflix.loadbalancer.BaseLoadBalancer
英文釋義:
??????????一個負載均衡器的基礎實現(xiàn)桌吃,該負載均衡器的作用是維護一張可以將服務設置到服務池的任意表朱沃。
????????一個ping能覺得一個服務的活性。本質上茅诱,這個類保存一個“all”服務列表和一個“up”服務列表逗物,還有支持消費者使用它們。
????????BaseLoadBalancer類是Ribbon負載均衡器的基礎實現(xiàn)類瑟俭,在該類中定義很多關于均衡負載相關的基礎內容:
????????定義并維護了兩個存儲服務實例server對象的列表翎卓。一個用于存儲所有服務實例的清單,一個用于存儲正常服務的實例清單尔当。
????????定義了之前我們提到的用來存儲負載均衡器各服務實例屬性和統(tǒng)計信息的LoadBalancerStats對象。
? ? ? ? ?定義了檢查服務實例是否正常服務的ping對象蹂安,在BaseLoadBalancer中默認為null椭迎,需要在構造時注入它的具體實現(xiàn)。
????????定義了檢查服務實例操作的執(zhí)行策略對象IPingStrategy田盈,在BaseLoadBalancer中默認使用了該類中定義的靜態(tài)內部類SerialPingStrategy實現(xiàn)畜号。根據源碼,我們可以看到該策略采用線性遍歷ping服務實例等方式實現(xiàn)檢查允瞧。該策略在當IPing當實現(xiàn)速度不理想简软,或是Server列表過大時蛮拔,可能會影響系統(tǒng)性能,這時候需要通過實現(xiàn)IPingStrategy接口并重寫pingServers(IPing ping, Server[] servers)函數去擴展ping的執(zhí)行策略痹升。
????????定義了負載均衡的處理規(guī)則IRule對象建炫,從BaseLoadBalancer中chooseServer(Object key)的實現(xiàn)源碼,我們可以知道負載均衡器實際進行服務實例選擇任務是委托給了IRule實現(xiàn)中的choose函數中實現(xiàn)而在這里疼蛾,默認初始化了RoundRobinRule為IRule的實現(xiàn)對象肛跌。RoundRobinRule實現(xiàn)了最基本且常用的線性負載均衡規(guī)則。
????????啟動ping任務:在BaseLoadBalancer的默認構造函數中察郁,會直接啟動一個用于定時檢查server是否健康的任務衍慎。該任務默認的執(zhí)行間隔為:10秒。
????????實現(xiàn)了ILoadBalancer接口定義的負載均衡器應具備的一系列基本操作:
????????addServer(List newServer):向負載均衡器中增加新的服務實例列表皮钠,該實現(xiàn)將原本已經維護著的所有服務實例清單allServerList和新傳入的服務實例清單newServers都加入到newList進行處理稳捆,在BaseLoadBalancer中實現(xiàn)的時候會使用新的列表覆蓋舊的列表。而之后介紹的幾個擴展實現(xiàn)類對于服務實例清單更新的優(yōu)化都是通過對setServersList函數的重寫來實現(xiàn)的麦轰。
????????chooseServer(Object key):挑選一個具體的服務實例乔夯,在上面介紹IRule的時候,已經做了說明原朝。
????????markServerDown(Server server):標記某個服務實例暫停服務驯嘱。
? ??????getReachableServers():獲取可用的(可達的)實例列表。由于BaseLoadBalancer中單獨維護了一個正常服務的實例清單喳坠,所以直接返回即可鞠评。
????????getAllServer():獲取所有的服務實例列表。由于BaseLoadBalancer中單獨維護了一個所有服務的實例清單壕鹉,所以也直接返回它即可剃幌。
? ??DynamicServerListLoadBalancer
com.netflix.loadbalancer.DynamicServerListLoadBalancer
英文釋義:
??????????這是一個負載均衡器,它有從動態(tài)源獲取候選服務列表的能力晾浴。
??????????這個服務列表能夠在運行期間動態(tài)的改變负乡。
??????????它也包含一些場所,列表中的服務在這些場所中被篩選出符合標準的服務脊凰。
????????DynamicServerListLoadBalancer類繼承于BaseLoadBalancer類抖棘,它是對基礎負載均衡器的擴展。在該負載均衡器中狸涌,實現(xiàn)了服務實例清單的在運行期的動態(tài)更新能力切省;同時,它還具備了對服務實例清單的過濾功能帕胆,也就是說我們可以通過過濾器來選擇性的獲取一批服務實例清單朝捆。
??ServerList
????????從DynamicServerListLoadBalancer的成員定義中,我們馬上可以發(fā)現(xiàn)新增了一個關于服務列表的操作對象:ServerList serverListImpl懒豹。其中范型T從類名中對于T的限定DynamicServerListLoadBalancer可以獲知它是一個server的子類芙盘,即代表了一個具體的服務實例的擴展類驯用。而Server接口定義如下所示:
????????它定義了兩個抽象方法:getInitialListOfServers用于獲取初始化的服務實例清單,而getUpdatedListOfServers用于獲取更新的服務實例清單儒老。那么該接口的實現(xiàn)有哪些呢蝴乔?通過搜索源碼,我們可以整出如下圖的結構:
? ??????從圖中我們可以看到有很多個ServerList的實現(xiàn)類贷盲,那么在DynamicServerListLoadBalancer中的ServerList默認配置到底使用了哪個具體實現(xiàn)呢淘这?既然在該負載均衡器中需要實現(xiàn)服務實例的動態(tài)更新,那么勢必需要ribbon具備訪問eureka來獲取服務實例的能力巩剖,所以我們從Spring Cloud整合ribbon與eureka的EurekaRibbonClientConfiguration铝穷,在該類中可以找到下面創(chuàng)建ServerList實例的內容。
????????那么DiscoveryEnabledNIWSServerList是如何實現(xiàn)這兩個服務實例的獲取的呢佳魔?從源碼中可以看到這兩個方法都是通過該類中的私有函數obtainServersViaDiscovery來通過服務發(fā)現(xiàn)機制來實現(xiàn)服務實例的獲取曙聂。
? ??????而obtainServersViaDiscovery的實現(xiàn)邏輯如下,主要依靠EurekaClient從服務注冊中心中獲取到具體的服務實例InstanceInfo列表(EurekaClient的具體實現(xiàn)鞠鲜,這里傳入的vipAddress可以理解為邏輯上的服務名宁脊,比如“USER-SERVICE”)。接著贤姆,對這些服務實例進行遍歷榆苞,將狀態(tài)為“UP”(正常服務)的實例轉換成DiscoveryEnabledServer對象,最后將這些實例組織成列表返回霞捡。
????????在DiscoveryEnabledNIWSServerList中通過EurekaClient從服務注冊中心獲取到最新的服務實例清單后坐漏,返回的List到了DomainExtractingServerList類中,將繼續(xù)通過setZones函數進行處理碧信。而這里的處理具體內容如下所示赊琳,主要完成將DiscoveryEnabledNIWSServerList返回的List列表中的元素,轉換成內部定義的DiscoveryEnabledServer的子類對象DomainExtractingServer砰碴,在該對象的構造函數中將為服務實例對象設置一些必要的屬性躏筏,比如id、zone呈枉、isAliveFlag趁尼、readyToServe等信息。
??ServerListUpdater
????????通過上面的分析我們已經知道了Ribbon與Eureka整合后猖辫,如何實現(xiàn)從Eureka Server中獲取實例清單酥泞。那么它又是如何觸發(fā)向Eureka Server去獲取服務實例清單以及如何在獲取到服務實例清單后更新本地的服務實例清單的呢?繼續(xù)來看DynamicServerListLoadBalancer中的實現(xiàn)內容住册,我們可以很容易地找到下面定義的關于ServerListUpdater的內容:
? ??????根據該接口的命名婶博,我們基本就能猜到瓮具,這個對象實現(xiàn)的是對ServerList的更新荧飞,所以可以稱它為“服務更新器”凡人。從下面的源碼中可以看到,在ServerListUpdater內部還定義了一個UpdateAction接口叹阔,上面定義的updateAction對象就是以匿名內部類的方式創(chuàng)建了一個它的具體實現(xiàn)挠轴,其中doUpdate實現(xiàn)的內容就是對ServerList的具體更新操作。除此之外耳幢,“服務更新器”中還定義了一系列控制它和獲取它的信息的操作岸晦。
? ??????而ServerListUpdater的實現(xiàn)類不多,具體如下所示睛藻。
根據兩個類的注釋启上,我們可以很容易地知道它們的作用。
????????PollingServerListUpdater:動態(tài)服務列表更新的默認策略店印,也就是說冈在,DynamicServerListLoadBalancer負載均衡器中的默認實現(xiàn)就是它,它通過定時任務的方式進行服務列表的更新按摘。
????????EurekaNotificationServerListUpdater:該更新器也可服務于DynamicServerListLoadBalancer負載均衡器包券,但是它的觸發(fā)機制與PollingServerListUpdater不同,它需要利用Eureka的事件監(jiān)聽器驅動服務列表的更新操作炫贤。
????????下面我們來詳細看看它默認實現(xiàn)的PollingServerListUpdater溅固。先從用于啟動“服務更新器”的start函數源碼看起,具體如下兰珍。我們可以看到start函數的實現(xiàn)內容驗證了之前提到的:以定時任務的方式進行服務列表的更新侍郭。它先創(chuàng)建了一個Runnable的線程實現(xiàn),在該實現(xiàn)中調用了上面提到的具體更新服務實例列表的方法updateAction.doUpdate()俩垃,最后再為這個Runnable線程實現(xiàn)啟動了一個定時任務執(zhí)行励幼。
????????繼續(xù)看PollingServerListUpdater的其他內容,我們可以找到用于啟動定時任務的兩個參數initialDelayMs和refreshIntervalMs的默認定義分別為1000和30*1000口柳,單位為毫秒苹粟。也就是說,更新服務實例在初始化之后延遲1秒后開始執(zhí)行跃闹,并以30秒為周期重復執(zhí)行嵌削。除了這些內容之外,還能看到它還會記錄最后更新時間望艺、是否存活等信息苛秕,同時也實現(xiàn)了ServerListUpdater中定義的一些其他操作內容,這些操作相對比較簡單找默,這里不再具體說明艇劫,有興趣的讀者可以自己查看源碼了解實現(xiàn)原理。
??????????ServerListFilter
????????在了解了更新服務實例等定時任務是如何啟動的之后惩激,我們回到updateAction店煞,doUpdate()調用的具體實現(xiàn)位置蟹演,在DynamicServerListLoadBalancer中,它的實際委托給了updateListOfServers函數顷蟀,具體實現(xiàn)如下:
????????可以看到酒请,這里終于用到了之前提到的ServerList的getUpdateListOfServers(),通過之前的介紹已經知道了這一步實現(xiàn)了從Eureka Server中獲取服務可用實例等列表鸣个。在獲得了服務實例列表之后羞反,這里又將引入一個新的對象filter,追溯該對象的定義囤萤,我們可以找到它是ServerListFilter定義的昼窗。
????????ServerListFilter接口非常簡單,該接口中定義了一個方法List getFilteredListOfServers(List servers)涛舍,主要用于實現(xiàn)對服務實例列表的過濾膏秫,通過傳入的服務實例清單,根據一些規(guī)則返回過濾后的服務實例清單做盅。該接口的實現(xiàn)如下圖所示缤削。
????????其中,除了ZonePreperenceServerListFilter的實現(xiàn)是Spring Cloud Ribbon中對Netflix Ribbon的擴展實現(xiàn)外吹榴,其他均是Netflix Ribbon中對原生實現(xiàn)類亭敢。下面,我們可以分別看看這些過濾器都有說明特點图筹。
????????AbstractServerListFilter:這是一個抽象過濾器帅刀,在這里定義了過濾時需要的一個重要依據對象LoadBalancerStats,我們在之前介紹過远剩,該對象存儲了關于負載均衡器的一些屬性和統(tǒng)計信息等扣溺。
????????ZoneAffinityServerListFilter:該過濾器基于“區(qū)域感知(Zone Affinity)”的方式實現(xiàn)服務實例等過濾,也就是說瓜晤,它會根據提供服務的實例所處的區(qū)域(Zone)與消費者的所處區(qū)域(Zone)進行比較锥余,過濾掉那些不是同處一個區(qū)域的實例。
????????從上面的源碼中我們可以看到痢掠,對于服務實例列表的過濾是通過Iterables.filter(servers,this.zoneAffinityPredicate.getServerOnlyPredicate())來實現(xiàn)的驱犹,其中判斷依據由ZoneAffinityPredicate實現(xiàn)服務實例與消費者的Zone比較。而在過濾之后足画,這里并不會馬上返回過濾的結果雄驹,而是通過shouldEnableZoneAffinity函數來判斷是否要啟用“區(qū)域感知”的功能。從下面shouldEnableZoneAffinity的實現(xiàn)中淹辞,我們可以看到医舆,它使用了LoadBalancerStats的getZoneSnapshot方法來獲取這些過濾后的同區(qū)域實例等基礎指標(包含實例數量、斷路器斷開數、活動請求數蔬将、實例平均負載等)兼贡,根據一系列的算法求出下面的幾個評價價值并與設置的閥值進行對比(下面的為默認值),若有一個條件符合娃胆,就不啟用“區(qū)域感知”過濾掉服務實例清單。這一算法實現(xiàn)為集群出現(xiàn)區(qū)域故障時等曼,依然可以依靠其他區(qū)域的實例進行正常服務提供了完善的高可用保障里烦。同時,通過這里的介紹禁谦,我們也可以關聯(lián)著來理解之前介紹Eureka的時候提到的對于區(qū)域分配設計來保證區(qū)域故障的高可用問題胁黑。
????????blackOutServerPercentage:故障實例百分比(斷路器斷開數/實例數量)>=0.8。
????????activeRequestsPerServer:實例平均負載>=0.6州泊。
????????availableServers:可用實例數(實例數量-斷路器斷開數)<2丧蘸。
????????DefaultNIWSServerListFilter:該過濾器也繼承自ZoneAffinityServerListFilter,是默認的NIWS(Netflix Internal Web Service)過濾器遥皂。
????????ServerListSubsetFilter:該過濾器也繼承自ZoneAffinityServerListFilter力喷,它非常適用于擁有大規(guī)模服務器集群(上百或更多)的系統(tǒng)。因為它可以產生一個“區(qū)域感知”結果的子集列表演训,同時它還能夠通過比較服務實例等通信失敗數量和并發(fā)連接數來判定該服務是否健康來選擇性地從服務實例列表中剔除那些相對不夠健康的實例弟孟。
????????ZonePerferenceServerListFilter:Spring Cloud整合時新增的過濾器。若使用Spring Cloud整合Eureka和Ribbon時會默認使用該過濾器样悟。它實現(xiàn)了通過配置或者Eureka實例元數據的所屬區(qū)域(Zone)來過濾出同區(qū)域的服務實例拂募。如下面的源碼所示,它的實現(xiàn)非常簡單窟她,首先通過父類ZoneAffinityServerListFilter的過濾器來獲得“區(qū)域感知”的服務實例列表陈症,然后遍歷這個結果,取出根據消費者配置預設的區(qū)域Zone來進行過濾震糖,如果過濾掉結果是空就直接返回父類獲取的結果录肯,如果不為空就返回通過消費者配置的Zone過濾后的結果。
ZoneAwareLoadBalancer
????????ZoneAwareLoadBalancer負載均衡器是對DynamicServerListLoadBalancer的擴展吊说。在DynamicServerListLoadBalancer中嘁信,我們可以看到它并沒有重寫選擇具體服務實例等chooseServer函數,所以它依然會采用在BaseLoadBalancer中實現(xiàn)的算法疏叨。使用RoudRobinRule規(guī)則潘靖,以線性輪詢的方式選擇調用的服務實例,該算法實現(xiàn)簡單并沒有區(qū)域(Zone)的概念蚤蔓,所以它會把所有實例視為一個Zone下的節(jié)點來看待卦溢,這樣就會周期性地產生跨區(qū)域(Zone)訪問的情況,由于跨區(qū)域會產生更高的延遲,這些實例主要以防止區(qū)域性故障實現(xiàn)高可用為目的而不能作為常規(guī)訪問的實例单寂,所以在多區(qū)域部署的情況下會有一定的性能問題贬芥,而該負載均衡器則可以避免這樣的問題。那么它是如何實現(xiàn)的呢宣决?
????????首先蘸劈,在ZoneAwareLoadBalancer中,我們可以發(fā)現(xiàn)尊沸,它并沒有重寫setServerList威沫,說明實現(xiàn)服務實例清單的更新主邏輯沒有修改。但是我們可以發(fā)現(xiàn)你重寫了這個函數setServerListForZones(Map> zoneServersMap)洼专“袈樱看到這里可能會有一些陌生,因為它并不是借口中定義的必備函數屁商,所以我們不妨去父類DynamicServerListLoadBalancer中尋找一下該函數烟很,我們可以找到下面的定義:
????????setServerListForZones函數的調用位于更新服務實例清單函數setServersList的最后,同時從其實現(xiàn)的內容來看蜡镶,它在父類DynamicServerListLoadBalancer中的作用是根據按區(qū)域Zone分組的實例列表雾袱,為負載均衡器中的LoadBalancerStats對象創(chuàng)建ZoneStats并放入Map zoneStatsMap集合中,每一個區(qū)域Zone對應一個ZoneStats官还,它用于存儲每個Zone的一些狀態(tài)和統(tǒng)計信息谜酒。
????????在ZoneAwareLoadBalancer中對setServerListForZones的重寫如下:
????????可以看到,在該實現(xiàn)中創(chuàng)建了一個ConcurrentHashMap()類型的balancers對象妻枕,它將用來存儲每個Zone區(qū)域對應的負載均衡器僻族。而具體的負載均衡器的創(chuàng)建則是通過在下面的第一個循環(huán)中調用getLoadBalancer函數來完成,同時在創(chuàng)建負載均衡器的時候會創(chuàng)建它的規(guī)則(如果當前實現(xiàn)中沒有IRule的實例屡谐,就創(chuàng)建一個AvailabilityFilteringRule規(guī)則述么;如果一件有具體實例,就克隆一個)愕掏。在創(chuàng)建完負載均衡器后又馬上調用setServersList函數為其設置對應Zone區(qū)域的實例清單邢笙。而第二個循環(huán)則是對Zone區(qū)域中實例清單的檢查如捅,看看是否有Zone區(qū)域下已經沒有實例了,是的話就將balancers中對應Zone區(qū)域的實例列表清空,該操作的作用是為了后續(xù)選擇節(jié)點時間档冬,防止過期的Zone區(qū)域統(tǒng)計信息干擾具體實例等選擇算法起趾。
????????在了解了該負載均衡器是如何擴展服務實例清單的實現(xiàn)后班巩,我們具體看看它是如何挑選服務實例查近,來實現(xiàn)對區(qū)域的識別的:
????????從源碼中我們可以看到,只有負載均衡器中維護的實例所屬的Zone區(qū)域個數大于1的時候才會執(zhí)行這里的選擇策略语卤,否則還是將使用父類的實現(xiàn)追逮。當Zone區(qū)域的個數大于1的時候酪刀,它的實現(xiàn)步驟如下所示。
??????????調用ZoneAvoidanceRule中靜態(tài)方法createSnapshot(lbStats)钮孵,為當前負載均衡器中所有的Zone區(qū)域分別創(chuàng)建快照骂倘,保存在Map zoneSnapshot中,這些快照中的數據將用于后續(xù)的算法巴席。
??????????調用ZoneAvoidanceRule中的靜態(tài)方法getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get())历涝,來獲取可用的Zone區(qū)域集合,在該函數中會通過Zone區(qū)域快照中的統(tǒng)計數據來實現(xiàn)可用區(qū)的挑選漾唉。
? ? ? ? ?首先它會剔除符合這些規(guī)則的Zone區(qū)域:所屬實例數位零的Zone區(qū)域荧库;Zone區(qū)域內實例等平均負載小于零,或者實例故障率(斷路器斷開次數/實例數)大于等于閥值(默認為0.99999)毡证。
????????然后根據Zone區(qū)域等實例平均負載計算出最差的Zone區(qū)域,這里的最差指的是實例平均負載最高的Zone區(qū)域蔫仙。
? ? ? ? 如果在上面的過程中沒有符合剔除要求的區(qū)域料睛,同時實例最大平均負載小于閥值(默認為20%),就直接返回所有Zone區(qū)域為可用區(qū)域摇邦。否則恤煞,從最壞Zone區(qū)域集合中隨機選擇一個,將它從可用Zone區(qū)域集合中剔除施籍。
??????????當獲得的可用Zone區(qū)域集合不為空居扒,并且個數小于Zone區(qū)域總數,就隨機選擇一個Zone區(qū)域丑慎。
??????????在確定了某個Zone區(qū)域后喜喂,則獲取了對應Zone區(qū)域的服務均衡器,并調用chooseServer來選擇具體的服務實例竿裂,而在chooseServer中將使用IRule接口的choose函數來選擇具體的服務實例玉吁。在這里,IRule接口的實現(xiàn)會使用ZoneAvoidanceRule來挑選出具體的服務實例腻异。
????????關于ZoneAvoidanceRule的策略以及其他一些還未提到的負載均衡策略进副,我們將在下一節(jié)更近詳細的解讀。
負載均衡策略
????????通過上面的源碼解讀悔常,我們已經對Ribbon實現(xiàn)的負載均衡器以及其中包含的服務實例過濾器影斑、服務實例信息的存儲對象、區(qū)域的信息快照等都有了深入的認識和理解机打,但是對于負載均衡器中的服務實例選擇策略只是講解了幾個默認實現(xiàn)的內容矫户,而對于IRule的其他實現(xiàn)還沒有詳細解讀,下面我們來看看在Ribbon中都提供了哪些負載均衡的策略實現(xiàn)残邀。
????????如下圖所示吏垮,可以看到在Ribbon中實現(xiàn)了非常多的選擇策略障涯,其中也包含了我們在前面內容中提到過的RoudRobinRule和ZoneAvoidanceRule。下面我們來詳細可讀一下IRule接口的各個實現(xiàn)膳汪。
??AbstractLoadBalancerRule
????????負載均衡策略的抽象類唯蝶,在該抽象類中定義了負載均衡器ILoadBalancer對象,該對象能夠在具體實現(xiàn)選擇服務策略時遗嗽,獲取到一些負載均衡器中維護的信息來作為分配依據粘我,并以此設計一些算法來實現(xiàn)針對特定場景的高效策略。
??RandomRule
????????該策略實現(xiàn)了從服務實例清單中隨機選擇一個服務實例的功能痹换。具體的選擇邏輯在一個while(server==null)循環(huán)之內征字,而根據選擇邏輯的實現(xiàn),正常情況下每次選擇都應該選出一個服務實例娇豫,如果出現(xiàn)死循環(huán)獲取不到服務實例匙姜,如果出現(xiàn)死循環(huán)獲取不到服務實例時,則很有可能存在并發(fā)的Bug冯痢。
??RoundRobinRule
????????該策略實現(xiàn)了按照線性輪詢的方式的方式一次選擇每個服務實例的功能氮昧。它的具體實現(xiàn)如下,其詳細結構與RandomRule非常類似浦楣。除了循環(huán)條件不同外袖肥,就是從可用列表中獲取所謂的邏輯不通。從循環(huán)條件中振劳,我們可以看到增加了一個count計數變量椎组,該變量會在每次循環(huán)之后累加,也就是說历恐,如果一直選擇不到server超過10次寸癌,那么就會結束嘗試,并打印一個警告信息No available alive servers after 10tries from load balancer : ... 弱贼。
??RetryRule
????????該策略實現(xiàn)了一個具備重試機制的實例選擇功能灵份。從下面的實現(xiàn)中我們可以看到,在其內部還定義了一個IRule對象哮洽,默認使用了RoudRobinRule實例填渠。而在choose方法中則實現(xiàn)了對內部定義的策略進行反復嘗試的策略,若期間能夠選擇到具體的服務實例就反悔鸟辅,若選擇不到就根據設置結束時間為閥值(maxRetryMillis參數定義的值+choose方法開始執(zhí)行的時間戳)氛什,當超過該閥值就返回null。
??WeightedResponseTimeRule
????????該策略是對RoundRobinRule的擴展匪凉,增加了根據實例等運行情況來計算權重枪眉,并根據權重來挑選實例,以達到更優(yōu)的分配效果再层,它的實現(xiàn)主要有三個核心內容贸铜。
????????定時任務
? ? ? ? 權重計算
????????實例選擇
??ClientConfigEnabledRoundRobinRule
????????該策略較為特殊堡纬,我們一般不直接使用它,因為它本身并沒有實現(xiàn)什么特殊的處理邏輯蒿秦,正如下面的源碼所示烤镐,在它的內部定義了一個RoundRobinRule策略,而choose函數的實現(xiàn)也正是使用了RoundRobinRule的線性輪詢機制棍鳖,所以它實現(xiàn)的功能實際上與RoundRobinRule相同炮叶,那么定義它有什么特殊的用處呢?
????????雖然我們不會直接使用該策略渡处,但是通過繼承該策略镜悉,默認的choose就實現(xiàn)了線性輪詢機制,在子類中做一些高級策略時通常有可能會存在一些無法實施的情況医瘫,那么久可以用父類的實現(xiàn)作為備選侣肄。在后面中我們將繼續(xù)介紹的高級策略均是基于ClientConfigEnabledRoundRobinRule的擴展。
??BestAvailableRule
????????該策略繼承自ClientConfigEnabledRoundRobinRule醇份,在實現(xiàn)中它注入了負載均衡器的統(tǒng)計對象LoadBalancerStats稼锅,同時在具體的choose算法中利用LoadBalancerStats保存的實例統(tǒng)計信息來選擇滿足要求的實例。從如下源碼中我們可以看到被芳,它通過遍歷負載均衡器中維護的所有服務實例缰贝,會過濾掉故障的實例馍悟,并找出并發(fā)請求數最小的一個畔濒,所以該策略的特性是可選出最空閑的實例。
????????同時锣咒,由于該算法的核心依據是統(tǒng)計對象LoadBalancerStats侵状,當其為空的時候,該策略是無法執(zhí)行的毅整。所以從源碼中我們可以看到趣兄,當loadBalancerStats為空的時候,它會采用父類的線性輪詢策略悼嫉,正如我們在介紹ClientConfigEnabledRoundRobinRule時那樣艇潭,它的子類在無法滿足實現(xiàn)高級策略的時候,可以使用線性輪詢策略的特性戏蔑。后面將要介紹的策略因為也都繼承自ClientConfigEnabledRoundRobinRule蹋凝,所以它們都會具有這樣的特性。
??PredicateBasedRule
????????這是一個抽象策略总棵,它也繼承了ClientConfigEnabledRoundRobinRule鳍寂,從其命名中可以猜出這是一個基于Predicate實現(xiàn)的策略,Predicate是Google Guava Collections工具對集合進行過濾掉條件接口情龄。
????????Google Guava Collections是一個對Java Collections Framework增強和擴展的開源項目迄汛。雖然Java Collections Framework已經能夠滿足我們大多數搶空下使用集合的要求捍壤,但是當遇到一些特殊情況時,我們的代碼會比較冗長且容易出錯鞍爱。Google Guava Collections可以幫助我們讓集合操作代碼更為簡短精煉并大大增強代碼的可讀性鹃觉。
??AvailabilityFilteringRule
????????該策略繼承自上面介紹的抽象策略PredicateBasedRule,所以它也繼承了“先過濾清單硬霍,再輪詢選擇”的的基本處理邏輯帜慢,其中過濾條件使用了AvailabilityPredicate。簡單地說唯卖,該策略通過線性抽樣的方式直接嘗試尋找可用且較空閑的實例來使用粱玲,優(yōu)化了父類每次都要遍歷所有實例的開銷。
??ZoneAvoidanceRule
????????該策略我們在介紹負載均衡器ZoneAwareLoadBalancer時已經提到過拜轨,它也是PredicateBasedRule的具體實現(xiàn)類抽减,在之前的介紹中主要針對ZoneAvoidanceRule中用于選擇Zone區(qū)域策略的一些靜態(tài)函數,比如createSnapshot(LoadBalancerStats lbStats)橄碾、getAvailableZones(Map snapshot, double triggeringLoad,double triggeringBlackoutPercentage)卵沉。在這里我們將詳細看看ZoneAvoidanceRule作為服務實例過濾條件的實現(xiàn)原理。從下面ZoneAvoidanceRule的源碼片段中可以看到法牲,它使用了CompositePredicate來進行服務實例清單的過濾史汗。這是一個組合過來條件,在其構造函數中拒垃,它以ZoneAvoidanceRule為主過濾條件停撞,AvailabilityPredicate為次過濾條件初始化了組合過濾條件的實例。
如果需要給我修改意見的發(fā)送郵箱:erghjmncq6643981@163.com
本博客的代碼示例已上傳GitHub:Spring Cloud Netflix組件入門
資料參考:《Spring Cloud 微服務實戰(zhàn)》
轉發(fā)博客悼瓮,請注明戈毒,謝謝。