SpringCloud下基于Ribbon的負載均衡實踐
負載均衡大體可以分為兩類:集中式、進程內(nèi)。前者也被稱為服務端負載均衡,其一般位于服務集群的前端,統(tǒng)一接收、處理、轉(zhuǎn)發(fā)客戶端的請求。典型地包括F5硬件、LVS、Nginx等技術方案;而后者也被稱為客戶端負載均衡,其是在客戶端側(cè)根據(jù)某種策略選擇合適的服務實例直接進行請求,其典型代表有Ribbon

環(huán)境搭建
搭建服務提供者
我們在服務提供者payment中添加一個Controller,如下所示
@RestController
@RequestMapping("pay")
public?class?PaymentController?{
????@GetMapping("/hello")
????public?String?hello(@RequestParam?String?name)?{
????????String?msg?=?"[Payment?Service-"+?serverPort?+"]:?"?+?name;
????????return?msg;
????}
}
這里使用Consul作為注冊中心。創(chuàng)建三個payment服務的實例,分別使用8004、8005、8006端口。如下所示

搭建服務消費者
在服務消費者order中,我們同樣也會引入spring-cloud-starter-consul-discovery依賴。由于該依賴間接依賴了spring-cloud-starter-netflix-ribbon,如下圖所示。故無需顯式引入spring-cloud-starter-netflix-ribbon依賴

在服務消費者order中,先聲明一個restTemplate實例,然后通過Controller調(diào)用payment服務
@Configuration
public?class?RestTemplateConfig?{
????/**
?????*?@LoadBalanced?注解作用:
?????*???1.?基于服務名調(diào)用的restTemplate實例
?????*???2.?支持負載均衡
?????*?@return
?????*/
????@Bean
????@LoadBalanced
????public?RestTemplate?restTemplate()?{
????????return?new?RestTemplate();
????}
}
...
@RestController
@RequestMapping("order")
public?class?OrderController2?{
????//?使用?注冊中心的服務名
????public?static?final?String?PAYMENT_URL?=?"http://payment";
????@Qualifier("restTemplate")
????@Autowired
????private?RestTemplate?restTemplate;
????@GetMapping("/test2")
????public?String?test2(@RequestParam?String?name)?{
????????String?msg?=?restTemplate.getForObject(PAYMENT_URL?+"/pay/hello?name={1}",?String.class,?name);
????????String?result?=?"[Order?Service?#test2]:?"?+?msg;
????????return?result;
????}
}
創(chuàng)建order服務的實例,使用82端口。如下所示

本版本Ribbon默認采用ZoneAvoidanceRule區(qū)域敏感性策略,測試效果如下符合預期

負載均衡策略
Ribbon通過實現(xiàn)IRule接口內(nèi)置了多種負載均衡策略

RoundRobinRule:隨機策略。隨機選擇服務實例 RandomRule:輪詢策略。依次選擇服務實例 RetryRule:重試策略 BestAvailableRule:最低并發(fā)策略。選擇正在請求的并發(fā)量最小的服務實例。如果服務實例處于熔斷狀態(tài),則忽略該服務實例 AvailabilityFilteringRule:可用性敏感策略。過濾掉 處于熔斷狀態(tài) 或 正在請求的并發(fā)量高的服務實例 ZoneAvoidanceRule:區(qū)域敏感性策略。復合判斷服務實例所在區(qū)域的性能和服務實例的可用性 WeightedResponseTimeRule:響應時間加權策略。根據(jù)各服務實例的響應時間計算權重,響應時間越長,權重越低,選擇該服務實例的概率越低
策略配置
全局配置
在服務消費者order中,我們可以配置全局的負載均衡策略。這里以RoundRobinRule隨機策略為例。首先通過Java配置類聲明一個該策略的實例對象
@Configuration
public?class?RandomRuleConfig?{
????@Bean
????public?IRule?randomRule()?{
????????return?new?RandomRule();
????}
}
需要注意的是,該Java配置類不能被@ComponentScan注解掃描到,否則該配置類就會被所有的Ribbon客戶端所共享。換言之,Ribbon策略實例的Java配置類不要放在@ComponentScan注解所在的當前包及其子包下即可。如下所示,我們所有策略實例的Java配置類均放在ribbon包下,而SpringBoot啟動類在order包下

通過@RibbonClients注解的defaultConfiguration屬性來設置其全局的負載均衡策略,如下所示
@SpringBootApplication
//?使用Consul作為注冊中心時使用
@EnableDiscoveryClient?
//?設置Ribbon全局負載均衡策略為隨機策略
@RibbonClients(defaultConfiguration?=?RandomRuleConfig.class)
public?class?OrderApplication?{
?
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(OrderApplication.class,?args);
????}
}
效果如下所示,符合預期

根據(jù)所調(diào)用的服務進行配置
可以通過@RibbonClient注解,對所調(diào)用的服務分別配置相應的策略。用法如下所示
@SpringBootApplication
//?使用Consul作為注冊中心時使用
@EnableDiscoveryClient?
//?調(diào)用payment服務時,采用隨機策略
@RibbonClient(name?="payment",?configuration?=?RandomRuleConfig.class)
public?class?OrderApplication?{
?
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(OrderApplication.class,?args);
????}
}
對于多個配置,還可以通過@RibbonClients注解整合到一處
//?調(diào)用payment服務時,采用隨機策略;?調(diào)用bill服務時,采用響應時間加權策略
@RibbonClients({
????@RibbonClient(name?=?"payment",configuration?=?RandomRuleConfig.class),
????@RibbonClient(name?=?"bill",configuration?=?WeightedResponseTimeRuleConfig.class)
})
基于配置文件的策略配置
事實上,不僅可以通過注解配置負載均衡策略。還可以直接通過配置文件進行配置,這樣可以直接免去相應均衡策略實例的Java配置類??赏ㄟ^ [調(diào)用的服務名].ribbon.NFLoadBalancerRuleClassName 配置項進行配置。示例如下所示
#?調(diào)用payment服務時,采用隨機策略
payment:
????ribbon:
????????NFLoadBalancerRuleClassName:?com.netflix.loadbalancer.RoundRobinRule
#?調(diào)用bill服務時,采用響應時間加權策略
bill:
????ribbon:
????????NFLoadBalancerRuleClassName:?com.netflix.loadbalancer.WeightedResponseTimeRule
自定義負載均衡策略
通過查看IRule接口實現(xiàn)類,不難發(fā)現(xiàn)IRule接口是一個均衡策略接口。而具體的實現(xiàn)類則是通過抽象類AbstractLoadBalancerRule進行拓展的。所以,如果我們期望自定義均衡策略,可以直接繼承實現(xiàn)AbstractLoadBalancerRule類即可,而不用從IRule接口進行拓展。如下即是我們實現(xiàn)的一個負載均衡策略,具體通過實現(xiàn)choose方法完成服務實例的選擇。這里為了簡便起見,我們的目標為總是使用某端口的服務實例
package?com.aaron.SpringCloud1.ribbon;
public?class?CustomRule?extends?AbstractLoadBalancerRule?{
????@Override
????public?void?initWithNiwsConfig(IClientConfig?clientConfig)?{
????}
????@Override
????public?Server?choose(Object?key)?{
????????ILoadBalancer?lb?=?getLoadBalancer();
????????//獲取服務列表
????????List?serverList?=?lb.getAllServers();
????????//?獲取端口為8005的服務實例
????????Server?target?=?serverList.stream()
????????????.filter(?server?->?server.getPort()==8005?)
????????????.findAny()
????????????.orElse(null);
????????return?target;
????}
}
在使用上,其與Ribbon內(nèi)置的均衡策略使用并無二致。要么通過 Java配置類+注解 的方式,要么通過配置文件的方式。當然對于前者而言,Java配置類不要放在@ComponentScan注解所在的當前包及其子包下
package?com.aaron.SpringCloud1.ribbon;
@Configuration
public?class?CustomRuleConfig?{
????@Bean
????public?IRule?customRule()?{
????????return?new?CustomRule();
????}
}
測試結(jié)果如下,符合預期

參考文獻
Spring微服務實戰(zhàn) John Carnell著 鳳凰架構(gòu) 周志明著
