客戶端負(fù)載均衡 Spring Cloud Ribbon
1.綜述
對(duì)于任何一個(gè)高可用高負(fù)載的系統(tǒng)來說叶摄,負(fù)載均衡是一個(gè)必不可少的名稱。在大型分布式計(jì)算體系中薄疚,某個(gè)服務(wù)在單例的情況下盹憎,很難應(yīng)對(duì)各種突發(fā)情況。因此阳懂,負(fù)載均衡是為了讓系統(tǒng)在性能出現(xiàn)瓶頸或者其中一些出現(xiàn)狀態(tài)下可以進(jìn)行分發(fā)業(yè)務(wù)量的解決方案。
Spring Cloud Ribbon 是一個(gè)基于Http和TCP的客服端負(fù)載均衡工具柜思,它是基于Netflix Ribbon實(shí)現(xiàn)的。它不像服務(wù)注冊(cè)中心巷燥、配置中心赡盘、API網(wǎng)關(guān)那樣獨(dú)立部署,但是它幾乎存在于每個(gè)微服務(wù)的基礎(chǔ)設(shè)施中缰揪。
包括前面的提供的聲明式服務(wù)調(diào)用也是基于該Ribbon實(shí)現(xiàn)的陨享。理解Ribbon對(duì)于我們使用Spring Cloud來講非常的重要,因?yàn)樨?fù)載均衡是對(duì)系統(tǒng)的高可用钝腺、網(wǎng)絡(luò)壓力的緩解和處理能力擴(kuò)容的重要手段之一抛姑。在上節(jié)的例子中,我們采用了聲明式的方式來實(shí)現(xiàn)負(fù)載均衡艳狐。
實(shí)際上定硝,內(nèi)部調(diào)用維護(hù)了一個(gè)RestTemplate對(duì)象,該對(duì)象會(huì)使用Ribbon的自動(dòng)化配置毫目,同時(shí)通過@LoadBalanced開啟客戶端負(fù)載均衡蔬啡。其實(shí)RestTemplate是Spring自己提供的對(duì)象,不是新的內(nèi)容镀虐。
ribbon最核心的概念是:一個(gè)被命名的client箱蟆,也即一個(gè)具有唯一名字的客戶端每一個(gè)負(fù)載均衡都是整體組件的一部分,它們相互協(xié)作調(diào)用遠(yuǎn)程服務(wù)刮便。每一個(gè)client都會(huì)通過類RibbonClientConfiguration創(chuàng)建一個(gè)新的子spring ApplicationContext空猜,這個(gè)子ApplicationContext上下文的名子就是client的名字(如:@FeignClient("user-server"),每個(gè)ribbon客戶端包含:
ILoadBalancer, RestClient, ServerListFilter, ServerList,IRule。
ILoadBalancer:是負(fù)載均衡的入口類辈毯。
ServerList:存儲(chǔ)遠(yuǎn)程服務(wù)所有可用節(jié)點(diǎn)
ServerListFilter:用于過濾非法的遠(yuǎn)程節(jié)點(diǎn)(如:不可用等)
IRule:負(fù)載算法(策略)坝疼,用于從可用節(jié)點(diǎn)選擇一個(gè)合適的節(jié)點(diǎn)。
RestClient:遠(yuǎn)程調(diào)用
攔截&請(qǐng)求:
2.GET請(qǐng)求
第一種getForEntity函數(shù)漓摩,該方法返回的是ResponseEntity裙士,該對(duì)象是字符串對(duì)HTTP請(qǐng)求響應(yīng)的封裝。
姓名管毙,年齡兩個(gè)參數(shù)對(duì)應(yīng)的{1}腿椎,{2}代表兩個(gè)占位符,如要傳遞多個(gè)參數(shù)以此類推夭咬。
String.class返回值啃炸,如果需要返回對(duì)象對(duì)象的.class
getBody()是返回的ResponseEntity對(duì)象中的體內(nèi)容。
/**
* Ribbon Get測試
* @return
*/
@RequestMapping(value="RibbonGet",method=RequestMethod.GET)
public String RibbonGet(@RequestParam("name") String name,@RequestParam("age") String age) {
return restTemplate.getForEntity("http://OrderService/GetTest?name={1}&age={2}", String.class,name,age).getBody();
}
注意占位符名稱
/**
* Ribbon Get測試
* @return
*/
@RequestMapping(value="RibbonGet",method=RequestMethod.GET)
public String RibbonGet(@RequestParam("name") String name,@RequestParam("age") String age) {
//傳遞Map集合
Map<String, Object> params=new HashMap<>();
params.put("name", name);
params.put("age", age);
return restTemplate.getForEntity("http://OrderService/GetTest?name={name}&age={age}", String.class,params).getBody();
}
第二種getForObject函數(shù)卓舵,該方法可以理解為對(duì)getForEntity的進(jìn)一步封裝南用,返回的直接就是身體,如果不需要關(guān)注請(qǐng)求響應(yīng)除身體外掏湾,該函數(shù)就非常好用裹虫。
/**
* Ribbon Get測試
* @return
*/
@RequestMapping(value="RibbonGet",method=RequestMethod.GET)
public String RibbonGet(@RequestParam("name") String name,@RequestParam("age") String age) {
return restTemplate.getForObject("http://OrderService/GetTest?name="+name+"&age="+age, String.class);
}
3.POST請(qǐng)求
第一種postForEntity函數(shù),該方法返回的是ResponseEntity <T>融击,其中?為請(qǐng)求響應(yīng)的體類型筑公。
/**
* Ribbon Post請(qǐng)求
* @param id
* @return
*/
@RequestMapping(value="ribbonPostOrder",method=RequestMethod.POST)
public User PostBeanTest(@RequestParam("id") Integer id) {
User user=new User();
user.setId(id);
ResponseEntity<User> entity = restTemplate.postForEntity("http://OrderService/PostBeanTest", user, User.class);
return entity.getBody();
}
第二種postForObject函數(shù),和postForEntity類似尊浪,簡化了機(jī)身的處理匣屡。
/**
* Ribbon Post請(qǐng)求
* @param id
* @return
*/
@RequestMapping(value="ribbonPostOrder",method=RequestMethod.POST)
public User PostBeanTest(@RequestParam("id") Integer id){
User user=new User();
user.setId(id);
return restTemplate.postForObject("http://OrderService/PostBeanTest", user, User.class);
}
4.PUT請(qǐng)求
把函數(shù)為無效類型,所以沒有返回內(nèi)容拇涤。
/**
* Ribbon Put請(qǐng)求
* @param id
* @return
*/
@RequestMapping(value="ribbonPutOrder",method=RequestMethod.POST)
public String ribbonPutOrder(@RequestParam("id") Integer id,@RequestParam("name") String name){
User user=new User();
user.setId(id);
user.setName(name);
restTemplate.put("http://OrderService/PutTest",user);
return "PUT成功捣作!";
}
5.DELETE請(qǐng)求
deleet函數(shù)為無效類型,所以沒有返回內(nèi)容鹅士。
/**
* Ribbon Delete請(qǐng)求
* @param id
* @return
*/
@RequestMapping(value="ribbonDeleteOrder",method=RequestMethod.POST)
public String ribbonDeleteOrder(@RequestParam("id") Integer id) {
restTemplate.delete("http://OrderService/DeleteTest/?id={1}",id);
return "刪除成功";
}
6.配置
自動(dòng)化配置
由于Ribbon中定義的每一個(gè)接口都有多種不同的策略實(shí)現(xiàn)券躁,同時(shí)這些之間又有一定的依賴關(guān)系。
com.netflix.client.config.IClientConfig
:Ribbon
的客戶端配置如绸,默認(rèn)采用com.netflix.client.config.DefaultClientConfigImpl
實(shí)現(xiàn)嘱朽。com.netflix.loadbalancer.IRule
:Ribbon
的負(fù)載均衡策略,默認(rèn)采用com.netflix.loadbalancer.ZoneAvoidanceRule
實(shí)現(xiàn)怔接,該策略能夠在多區(qū)域環(huán)境下選出最佳區(qū)域的實(shí)例進(jìn)行訪問搪泳。com.netflix.loadbalancer.IPing
:Ribbon
的實(shí)例檢查策略,默認(rèn)采用com.netflix.loadbalancer.NoOpPing
實(shí)現(xiàn)扼脐,該檢查策略是一個(gè)特殊的實(shí)現(xiàn)岸军,實(shí)際上它并不會(huì)檢查實(shí)例是否可用奋刽,而是始終返回true,默認(rèn)認(rèn)為所有服務(wù)實(shí)例都是可用的艰赞。com.netflix.loadbalancer.ServerList
:服務(wù)實(shí)例清單的維護(hù)機(jī)制佣谐,默認(rèn)采用com.netflix.loadbalancer.ConfigurationBasedServerList
實(shí)現(xiàn)。com.netflix.loadbalancer.ServerListFilter
:服務(wù)實(shí)例清單過濾機(jī)制方妖,默認(rèn)采org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter
狭魂,該策略能夠優(yōu)先過濾出與請(qǐng)求方處于同區(qū)域的服務(wù)實(shí)例。com.netflix.loadbalancer.ILoadBalancer
:負(fù)載均衡器党觅,默認(rèn)采用com.netflix.loadbalancer.ZoneAwareLoadBalancer
實(shí)現(xiàn)雌澄,它具備了區(qū)域感知的能力。
上面的配置是在項(xiàng)目中沒有引入spring Cloud Eureka
杯瞻,如果引入了Eureka
和Ribbon
依賴時(shí)镐牺,自動(dòng)化配置會(huì)有一些不同。
通過自動(dòng)化配置的實(shí)現(xiàn)魁莉,可以輕松的實(shí)現(xiàn)客戶端的負(fù)載均衡睬涧。同時(shí),針對(duì)一些個(gè)性化需求旗唁,我們可以方便的替換上面的這些默認(rèn)實(shí)現(xiàn)畦浓,只需要在springboot應(yīng)用中創(chuàng)建對(duì)應(yīng)的實(shí)現(xiàn)實(shí)例就能覆蓋這些默認(rèn)的配置實(shí)現(xiàn)。
@Configuration
public class MyRibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
這樣就會(huì)使用P使用了RandomRule實(shí)例替代了默認(rèn)的com.netflix.loadbalancer.ZoneAvoidanceRule
检疫。
也可以使用@RibbonClient
注解實(shí)現(xiàn)更細(xì)粒度的客戶端配置
Camden版本對(duì)RabbitClient配置的優(yōu)化
上面的方式主要是通過獨(dú)立創(chuàng)建一個(gè)Configuration
類來定義IPing
宅粥,IRule
等接口的具體實(shí)現(xiàn)Bean,然后通過RabbonClient
時(shí)指定要使用的具體Configuration
類來覆蓋自動(dòng)化配置的默認(rèn)實(shí)現(xiàn)电谣。在spring Cloud Ribbon Camden
版本中對(duì)RibbonClient
定義個(gè)性化配置的方法做出進(jìn)一步優(yōu)化∧ㄊ矗可以直接使用<clientName>.ribbon.<key>=<value>
的形式進(jìn)行配置剿牺。
user-service.ribbon.NFLoadBalancerPingClassName=com.netfix.loadbalancer.PingUrl
user-service
是服務(wù)名,NFLoadBalancerPingClassName
參數(shù)是用來指定IPing
接口實(shí)現(xiàn)類环壤。在Camden
版本中晒来,Spring Cloud Ribbon
新增了一個(gè)org.springframework.cloud.netflix.ribbon.PropertiesFactory
類動(dòng)態(tài)的為RibbonClient
創(chuàng)建這些接口實(shí)現(xiàn)。
在Camden
版本中我們可以通過配置的方式郑现,更加方便的為RibbonClient
指定ILoadBalancer
湃崩,IPing
,IRule
接箫,ServerList
攒读,ServerListFilter
的定制化實(shí)現(xiàn)。
參數(shù)配置
對(duì)于Ribbon的參數(shù)通常有二種方式:全局配置以及指定客戶端配置
- 全局配置的方式很簡單
只需要使用ribbon.<key>=<value>
格式進(jìn)行配置即可辛友。其中薄扁,<key>
代表了Ribbon
客戶端配置的參數(shù)名剪返,<value>
則代表了對(duì)應(yīng)參數(shù)的值。比如邓梅,我們可以想下面這樣配置Ribbon
的超時(shí)時(shí)間
ribbon.ConnectTimeout=250
全局配置可以作為默認(rèn)值進(jìn)行設(shè)置脱盲,當(dāng)指定客戶端配置了相應(yīng)的key的值時(shí),將覆蓋全局配置的內(nèi)容
- 指定客戶端的配置方式
<client>.ribbon.<key>=<value>
的格式進(jìn)行配置.<client>
表示服務(wù)名日缨,比如沒有服務(wù)治理框架的時(shí)候(如Eureka)钱反,我們需要指定實(shí)例清單,可以指定服務(wù)名來做詳細(xì)的配置匣距,
user-service.ribbon.listOfServers=localhost:8080,localhost:8081,localhost:8082
對(duì)于Ribbon參數(shù)的key以及value類型的定義面哥,可以通過查看com.netflix.client.config.CommonClientConfigKey
類。
與Eureka結(jié)合
當(dāng)在spring Cloud
的應(yīng)用同時(shí)引入Spring cloud Ribbon
和Spring Cloud Eureka
依賴時(shí)墨礁,會(huì)觸發(fā)Eureka
中實(shí)現(xiàn)的對(duì)Ribbon的
自動(dòng)化配置幢竹。這時(shí)的serverList
的維護(hù)機(jī)制實(shí)現(xiàn)將被com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
的實(shí)例所覆蓋,該實(shí)現(xiàn)會(huì)講服務(wù)清單列表交給Eureka
的服務(wù)治理機(jī)制來進(jìn)行維護(hù)恩静。IPing
的實(shí)現(xiàn)將被
com.netflix.niws.loadbalancer.NIWSDiscoveryPing
的實(shí)例所覆蓋焕毫,該實(shí)例也將實(shí)例接口的任務(wù)交給了服務(wù)治理框架來進(jìn)行維護(hù)。默認(rèn)情況下驶乾,用于獲取實(shí)例請(qǐng)求的ServerList
接口實(shí)現(xiàn)將采用Spring Cloud Eureka
中封裝的
org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList
邑飒,其目的是為了讓實(shí)例維護(hù)策略更加通用,所以將使用物理元數(shù)據(jù)來進(jìn)行負(fù)載均衡级乐,而不是使用原生的AWS AMI元數(shù)據(jù)疙咸。
在與Spring cloud Eureka
結(jié)合使用的時(shí)候,不需要再去指定類似的user-service.ribbon.listOfServers
的參數(shù)來指定具體的服務(wù)實(shí)例清單风科,因?yàn)?code>Eureka將會(huì)為我們維護(hù)所有服務(wù)的實(shí)例清單撒轮,而對(duì)于Ribbon
的參數(shù)配置,我們依然可以采用之前的兩種配置方式來實(shí)現(xiàn)贼穆。
此外题山,由于spring Cloud Ribbon
默認(rèn)實(shí)現(xiàn)了區(qū)域親和策略,所以故痊,可以通過Eureka
實(shí)例的元數(shù)據(jù)配置來實(shí)現(xiàn)區(qū)域化的實(shí)例配置方案顶瞳。比如可以將不同機(jī)房的實(shí)例配置成不同的區(qū)域值,作為跨區(qū)域的容器機(jī)制實(shí)現(xiàn)愕秫。而實(shí)現(xiàn)也非常簡單慨菱,只需要服務(wù)實(shí)例的元數(shù)據(jù)中增加zone參數(shù)來指定自己所在的區(qū)域,比如:
eureka.instance.metadataMap.zone=shanghai
在Spring Cloud Ribbon與Spring Cloud Eureka
結(jié)合的工程中戴甩,我們可以通過參數(shù)禁用Eureka
對(duì)Ribbon
服務(wù)實(shí)例的維護(hù)實(shí)現(xiàn)符喝。這時(shí)又需要自己去維護(hù)服務(wù)實(shí)例列表了。
ribbon.eureka.enabled=false.
7. 重試機(jī)制
由于Spring Cloud Eureka
實(shí)現(xiàn)的服務(wù)治理機(jī)制強(qiáng)調(diào)了cap
原理的ap
機(jī)制(即可用性和可靠性)甜孤,與zookeeper
這類強(qiáng)調(diào)cp
(一致性洲劣,可靠性)服務(wù)質(zhì)量框架最大的區(qū)別就是备蚓,Eureka
為了實(shí)現(xiàn)更高的服務(wù)可用性,犧牲了一定的一致性囱稽,在極端情況下寧愿接受故障實(shí)例也不要丟棄"健康"實(shí)例郊尝。
比如說,當(dāng)服務(wù)注冊(cè)中心的網(wǎng)絡(luò)發(fā)生故障斷開時(shí)候战惊,由于所有的服務(wù)實(shí)例無法維護(hù)續(xù)約心跳流昏,在強(qiáng)調(diào)ap的服務(wù)治理中將會(huì)把所有服務(wù)實(shí)例剔除掉,而Eureka
則會(huì)因?yàn)槌^85%的實(shí)例丟失心跳而觸發(fā)保護(hù)機(jī)制吞获,注冊(cè)中心將會(huì)保留此時(shí)的所有節(jié)點(diǎn)况凉,以實(shí)現(xiàn)服務(wù)間依然可以進(jìn)行互相調(diào)用的場景,即使其中有部分故障節(jié)點(diǎn)各拷,但這樣做可以繼續(xù)保障大多數(shù)服務(wù)的正常消費(fèi)刁绒。
在Camden
版本,整合了spring retry
來增強(qiáng)RestTemplate
的重試能力烤黍,對(duì)于我們開發(fā)者來說知市,只需要簡單配置,即可完成重試策略速蕊。
spring.cloud.loadbalancer.retry.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
user-service.ribbon.ConnectTimeout=250
user-service.ribbon.ReadTimeout=1000
user-service.ribbon.OkToRetryOnAllOperations=true
user-service.ribbon.MaxAutoRetriesNextServer=2
user-service.ribbon.maxAutoRetries=1
spring.cloud.loadbalancer.retry.enabled
:該參數(shù)用來開啟重試機(jī)制嫂丙,它默認(rèn)是關(guān)閉的。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
:斷路器的超時(shí)時(shí)間需要大于Ribbon的超時(shí)時(shí)間规哲,不然不會(huì)觸發(fā)重試跟啤。
user-service.ribbon.ConnectTimeout
:請(qǐng)求連接超時(shí)時(shí)間。
user-service.ribbon.ReadTimeout
:請(qǐng)求處理的超時(shí)時(shí)間
user-service.ribbon.OkToRetryOnAllOperations
:對(duì)所有操作請(qǐng)求都進(jìn)行重試唉锌。
user-service.ribbon.MaxAutoRetriesNextServer
:切換實(shí)例的重試次數(shù)隅肥。
user-service.ribbon.maxAutoRetries
:對(duì)當(dāng)前實(shí)例的重試次數(shù)。
根據(jù)以上配置袄简,當(dāng)訪問到故障請(qǐng)求的時(shí)候武福,它會(huì)再嘗試訪問一次當(dāng)前實(shí)例(次數(shù)由maxAutoRetries
配置),如果不行痘番,就換一個(gè)實(shí)例進(jìn)行訪問,如果還是不行平痰,再換一個(gè)實(shí)例訪問(更換次數(shù)由MaxAutoRetriesNextServer
配置)汞舱,如果還不行,返回失敗宗雇。
8.Ribbon 提供的主要負(fù)載均衡策略:
1:簡單輪詢負(fù)載均衡(RoundRobin)
以輪詢的方式依次將請(qǐng)求調(diào)度不同的服務(wù)器昂芜,即每次調(diào)度執(zhí)行i = (i + 1) mod n,并選出第i臺(tái)服務(wù)器赔蒲。
2:隨機(jī)負(fù)載均衡 (Random)
隨機(jī)選擇狀態(tài)為UP的Server
3:加權(quán)響應(yīng)時(shí)間負(fù)載均衡 (WeightedResponseTime)
根據(jù)響應(yīng)時(shí)間分配一個(gè)weight泌神,響應(yīng)時(shí)間越長良漱,weight越小,被選中的可能性越低欢际。
4:區(qū)域感知輪詢負(fù)載均衡(ZoneAvoidanceRule)
復(fù)合判斷server所在區(qū)域的性能和server的可用性選擇server
less is more.