1.Ribbon
目前主流的負載方案分為兩種:一種是集中式負載均衡,在消費者和服務提供方中間使用獨立的代理方式進行負載,有硬件的(比如F5),也有軟件的(比如Nginx)。另一種則是客戶端自己做負載均衡,根據自己的請求情況做負載,Ribbon就屬於這種。
一句話:Ribbon是Netflix開源的一款用於客戶端負載均衡的工具軟件。
1.1 Ribbon模塊
Ribbon模塊如下:
·ribbon-loadbalancer:負載均衡模塊,可獨立使用,也可以和別的模塊一起使用。Ribbon內置的負載均衡算法都實現在其中。
·ribbon-eureka:基於Eureka封裝的模塊,能夠快速方便地集成Eureka。
·ribbon-transport:基於Netty實現多協議的支持,比如Http、Tcp、Udp等
·ribbon-httpclient:基於Apache HttpClient封裝的REST客戶端,繼承了負載均衡模塊,可以直接在項目中使用來調用接口。
·ribbon-example:Ribbon使用代碼示例,通過這些示例能夠讓你的學習事半功倍。
·ribbon-core:一些比較核心且具有通用性的代碼,客戶端API的一些配置和其他API的定義。
1.2 Ribbon使用
Ribbon依賴:
<dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon</artifactId> <version>2.7.17</version> </dependency>
接下來編寫一個客戶端來調用接口:
2. RestTemplate結合Ribbon使用
上面是簡單地使用Ribbon進行了負載的一個調用,意味着Ribbon是可以單獨使用的。在Spring Cloud中使用Ribbon會更簡單,因為Spring Cloud在Ribbon的基礎上進行了一層封裝,將很多配置都集成好了。本節將在Spring Cloud項目中使用Ribbon。
2.1 使用RestTemplate與整合Ribbon
Spring提供了一種簡單便捷的模板類來進行API的調用,那就是RestTemplate。
2.1.1 使用RestTemplate
本節更仔細地講解RestTemplate的具體使用方法。
首先我們看看GET請求的使用方式:在fsh-house服務的HouseController中增加兩個接口,一個通過@RequestParam來傳遞參數,返回一個對象信息;另一個通過@PathVariable來傳遞參數,返回一個字符串。代碼如下:
@GetMapping("/data") public HouseInfo getData(@RequestParam("name")String name){ return new HouseInfo(1L,"上海","虹口","東體小區"); } @GetMapping("/data/{name}") public String getData2(@PathVariable("name")String name) { return name; } }
在fsh-substitution服務中用RestTemplate來調用我們剛剛定義的兩個接口,如代碼:
@GetMapping("/data") public HouseInfo getData(@RequestParam("name")String name){ return restTemplate.getForObject("http://localhost:8081/house/data?name="+name, HouseInfo.class); } @GetMapping("/data/{name}") public String getData2(@PathVariable("name")String name){ return restTemplate.getForObject("http://localhost:8081/house/data/{name}", String.class,name); }
注意getForObject方法的幾個參數:url,返回值類型,參數。
除了getForObject,還可以使用getForEntity來獲取數據,代碼如下:
@GetMapping("/data") public HouseInfo getData3(@RequestParam("name")String name){ ResponseEntity<HouseInfo> responseEntity=restTemplate.getForEntity("http://localhost:8081/house/data?name="+name, HouseInfo.class); if(responseEntity.getStatusCodeValue()==200) { return responseEntity.getBody(); } return null; }
getForEntity中可以獲取返回的狀態嗎、請求頭等信息,通過getBody獲取響應的內容。
接下來看看怎么使用POST方式調用接口。在HouseController中增加一個save方法用來接收HouseInfo數據,如代碼所示:
@PostMapping("/save") public Long addData(@RequestBody HouseInfo houseInfo) { System.out.println(houseInfo.getName()); return 1001L; }
接着寫調用代碼,用postForObject來調用,如代碼所示:
@GetMapping("/save") public Long add() { HouseInfo houseInfo=new HouseInfo(); houseInfo.setCity("上海"); houseInfo.setRegion("虹口"); houseInfo.setName("興旺新區"); Long id=restTemplate.postForObject("http://localhost:8081/house/save", houseInfo, Long.class); return id; } }
除了postForObject還可以使用postForEntity方法,用法都一樣。
除了get和post對應的方法之外,RestT還提供了put、delete等操作方法,還有一個比較實用的就是exchange方法。exchange可以執行get、post、put、delete這4種請求方式。可自行學習。
2.1.2 整合Ribbon
在Spring Cloud項目中集成Ribbon只需要加入下面的以來即可,其實也可以不用額皮質,因為Eureka中已經引用了Ribbon,如代碼所示:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency>
這個配置我們加在fangjia-fsh-substitution-service中。
2.2 RestTemplate負載均衡示例
對之前的代碼進行改造,輸出一些內容,證明我們集成的Ribbon是有效的。
改造hello接口,在接口中輸出當前服務的端口,用來區分調用的服務,代碼如下:
@RestController @RequestMapping("/house") public class HouseController{ @Value("${server.port}") private String severPort; @GetMapping("/hello") public String hello(){ return "Hello"+severPort; } }
接着改造callHello接口的代碼,將調用結果輸出到控制台,如代碼所示:
@RestController @RequestMapping("/substitution") public class SubstitutionController{ @Autowired private RestTemplate restTemplate; @GetMapping("/callHello") public String callHello(){ String result=restTemplate.getForObject("http://fsh-house/house/hello",String.class); System.out.println("調用結果:"+result); return result; } }
2.3 @LoadBalanced注解原理
為什么在RestTemplate上加了一個@LoadBalanced之后,RestTemplate就能夠跟Eureka結合了,可以使用服務名稱去調用接口,還可以負載均衡?
這功勞應歸功於Spring Cloud給我們做了大量的底層工作,因為它將這些都封裝好了。
主要的邏輯就是給RestTemplate增肌攔截器,在請求之前對請求的地址進行替換,或者根據具體的負載策略選擇服務地址,然后再去調用,這就是@LoadBalanced的原理。
深入原理,以后在研究吧。
2.4 Ribbon API使用
當你有一些特殊的需求,想通過Ribbon獲取對應的服務信息,可以使用LoadBalancerClient來獲取,比如你想獲取一個fsh-house服務的服務地址,可以通過LoadBalancerClient的choose方法選擇一個:
@Autowired private LoadBalancerClient loadBalancer; @GetMapping("/choose") public Object chooseUrl(){ ServiceInstance instance=loadBalancer.choose("fsh-house"); return instance; }
2.5 Ribbon飢餓加載
在進行服務調用的時候,如果網絡情況不好,第一次調用會超時。通過配置eager-load來提前初始化客戶端就可以解決這個問題:
#開啟Ribbon的飢餓加載模式 ribbon.eager-load.enabled=true #指定需要飢餓加載的服務名,也就是你需要調用的服務 ribbon.eager-load.clients=fsh-house
3.負載均衡策略介紹
Ribbon作為一款客戶端負載均衡框架,默認的負載策略是輪詢,同時也提供了很多其他的策略能夠讓用戶根據自身的業務需求進行選擇。
4.自定義負載策略
通過實現IRule接口可以自定義負載策略,主要的選擇服務邏輯在choose方法中。
5.配置詳解
5.1 常用配置
5.1.1 禁用Eureka
當我們在RestTemplate上添加@LoadBalanced注解后,就可以用服務名稱來調用接口了,當有多個服務的時候,還能做負載均衡。這是因為Eureka中的服務信息已經被拉取到了客戶端本地,如果我們不想和Eureka集成,可通過一下配置將其禁用:
#禁用Eureka ribbon.eureka.enabled=false
當我們禁用了Eureka之后,就不能使用服務名稱去調用接口了,必須指定服務地址。
5.1.2 配置接口地址列表
禁用Eureka之后就需要手動配置調用的服務地址了,配置如下:
#禁用Eureka后手動配置服務地址
fsh-house.ribbon.listOServers=localhost:8081,localhost:8083
這個配置是針對具體服務的,前綴就是服務名稱,配置完之后就可以和之前一樣使用服務名稱來調用接口了。
5.1.3 配置負載均衡策略
Ribbon默認的策略是輪詢。可以通過配置指定服務使用哪種策略來進行負載操作:
#配置負載均衡策略
fsh-house.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
5.1.4 超時時間
Ribbon中有兩種時間相關的設置,分別是請求連接的超時時間和請求處理的超時時間,設置規則如下:
#請求連接的超時時間 ribbon.connectTimeOut=2000 #請求處理的超時時間 ribbon.readTimeOut=5000
5.1.5 請求重試
在服務調用過程中,難免會發生一些異常情況,比如網絡問題導致的調用失敗,當調用失敗時通過重試機制進行再次調用,可以減少出錯的數量,同時也需要注意多次調用導致的臟數據問題。通過下面的配置可以對Ribbon的重試次數進行配置:
#對當前實例的重試次數 ribbon.maxAutoRetries=1 #切換實例的重試次數 ribbon.maxAutoRetriesNextServer=3 #對所有操作請求都進行重試 ribbon.okToRetryOnAllOperations=true
6 重試機制
重試機制就是當Ribbon發現請求的服務不可到達時,重新請求另外的服務。
6.1 RetryRule重試
最簡單的方法就是利用Ribbon自帶的重試策略進行重試,此時只需要指定某個服務的負載策略為重試策略即可:
fsh-house.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RetryRule
6.2 Spring Retry重試
除了使用Ribbon自帶的重試策略,我們還可以通過基礎Spring Retry來進行重試操作
在pom中添加Spring Retry的依賴,如代碼所示:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.2.5.RELEASE</version> </dependency>
配置重試次數等信息:
#對當前實例的重試次數 ribbon.maxAutoRetries=1 #切換實例的重試次數 ribbon.maxAutoRetriesNextServer=3 #對所有操作請求都進行重試 ribbon.okToRetryOnAllOperations=true