一、前言
1.1 實作目标
服務A調用服務B1和B2(B1和B2提供同種服務),當服務B1/B2在停止和重新釋出階段,或B1/B2有一個服務故障時,
- 需保證服務A正常調用B服務,達到無感覺釋出的效果(服務B高可用)
- 需保證服務A的請求負載均衡,避免某個B服務節點壓力過大(服務B負載均衡)
說明:這裡是獨立使用Ribbon,不依賴于Eureka、Zookeeper等任何服務注冊發現元件。
1.2 環境
JDK 1.8,SpringCloud Greenwich.SR2,SpringBoot 2.1.3.RELEASE
二、實作
本文示例,CONSUMER-SERVICE服務調用PRODUCER-SERVICE服務。在進行以下步驟前,請先啟動兩個普通的SpringBoot服務PRODUCER-SERVICE。
2.1 pom依賴
因為這裡獨立使用Ribbon,是以CONSUMER-SERVICE隻需要spring-cloud-starter-netflix-ribbon,啟動主類也無需更多的注解,如
@EnableEurekaClient、@EnableDiscoveryClient、@EnableCircuitBreaker等,隻需保留@SpringBootApplication即可。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2 RestTemplate配置
Ribbon是對RestTemplate的加強,需要為RestTemplate添加注解@LoadBalanced,使之具有負載均衡能力。如下:
@Bean
@LoadBalanced
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
2.3 Ribbon配置
注意:網上和書上很多教程都沒有提到ribbon.restclient.enabled這一配置,導緻再怎麼嘗試都無法成功自動重試。
spring.application.name=CONSUMER-SERVICE
server.port=8801
ribbon.restclient.enabled=true
#開啟重試機制
spring.cloud.loadbalancer.retry.enabled=true
#請求連接配接的逾時時間
PRODUCER-SERVICE.ribbon.ConnectTimeout=250
#請求處理的逾時時間
PRODUCER-SERVICE.ribbon.ReadTimeout=1000
#對所有操作請求都進行重試,預設false,隻有GET請求會重試;這是防止POST等對資料有影響的請求在重試後因為接口未做幂等性導緻資料異常,影響較大
PRODUCER-SERVICE.ribbon.OkToRetryOnAllOperations=true
#指定請求重試開關,經調試源碼該屬性未被使用,疑似bug,導緻不論怎麼設定,都是隻有服務提供者的Get請求可以被自動重試
#PRODUCER-SERVICE.ribbon.RequestSpecificRetryOn=true
#切換執行個體的重試次數
PRODUCER-SERVICE.ribbon.MaxAutoRetriesNextServer=2
#對目前執行個體的重試次數
PRODUCER-SERVICE.ribbon.MaxAutoRetries=1
#服務PRODUCER-SERVICE的位址
PRODUCER-SERVICE.ribbon.listOfServers=localhost:8080,localhost:8083
2.4 Ribbon調用服務
在Controller中,注入RestTemplate,使用服務名(即spring.application.name)的方式,調用PRODUCER-SERVICE服務的GET接口,如下:
@Controller
@RequestMapping("consumer/api")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
@ResponseBody
public String test() {
ResponseEntity<String> entity = restTemplate
.getForEntity("http://PRODUCER-SERVICE/outer/data?res=3&msgKey=token123", String.class);
return entity.getBody();
}
}
三、測試運作
3.1 負載均衡測試
啟動一個消費者服務CONSUMER-SERVICE,多次通路/consumer/api/test,可以通過給PRODECER-SERVICE服務的/outer/data接口添加調試日志的列印,來确認預設使用了輪詢的負載均衡政策。
3.2 高可用測試
停止其中一個PRODECER-SERVICE服務執行個體,确認輪詢到已停止的服務時,可以成功地在未停止的服務上自動重試請求。
四、無法成功自動重試的幾種情況
本人在單獨使用Ribbon的過程中,碰到以下幾種無法自動重試其他服務節點的情況:
4.1 ribbon.restclient.enabled
遇到Ribbon的問題,網上一搜,千篇一律,也不知道作者們是否親自實踐證明可用,就随意發篇文章。言歸正傳,若不設定ribbon.restclient.enabled=true,在本人的實驗環境中是無法自動重試的。
4.2 Maven打包有警告
在SpringCloud生态的開發中,各元件往往自動依賴了很多其他的jar包,如果向Maven本地倉庫下載下傳的過程中,網絡不好,就會下載下傳到一份不完整的jar包或pom檔案,最終可能會導緻打包出錯。下面是在使用Maven打包項目時,比較常見下面的警告:
[WARNING] The POM for com.sun.jersey:jersey-core:jar:1.19.1 is invalid, transitive dependencies (if any) will not be available
[WARNING] The POM for com.sun.jersey:jersey-client:jar:1.19.1 is invalid, transitive dependencies (if any) will not be available
...
可以看出來,提示我們傳遞依賴将失效,是以有可能整個打包過程是SUCCESS的,但是最後啟動jar包時,卻可能報NoClassDef等缺少jar包的錯誤。是以,我們需要在打包時加上參數 -X 以檢視具體原因:
[WARNING] The POM for com.sun.jersey:jersey-core:jar:1.19.1 is invalid, transitive dependencies (if any) will not be available: 1 problem was encountered while building the effective model for com.sun.jersey:jersey-core:[unknown-version]
[FATAL] Non-parseable POM E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom: processing instruction can not have PITarget with reserved xml name (position: END_TAG seen ...</properties>\n\n</project>\n<?xml ... @627:7) @ E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom, line 627, column 7
[WARNING] The POM for com.sun.jersey:jersey-client:jar:1.19.1 is invalid, transitive dependencies (if any) will not be available: 1 problem was encountered while building the effective model for com.sun.jersey:jersey-client:[unknown-version]
[FATAL] Non-parseable POM E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom: processing instruction can not have PITarget with reserved xml name (position: END_TAG seen ...</properties>\n\n</project>\n<?xml ... @627:7) @ E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom, line 627, column 7
[WARNING] The POM for com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.1 is invalid, transitive dependencies (if any) will not be available: 1 problem was encountered while building the effective model for com.sun.jersey.contribs:jersey-apache-client4:[unknown-version]
[FATAL] Non-parseable POM E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom: processing instruction can not have PITarget with reserved xml name (position: END_TAG seen ...</properties>\n\n</project>\n<?xml ... @627:7) @ E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom, line 627, column 7
特别注意FATAL這個最嚴重的日志級别的資訊, 日志提醒我們本地倉庫的pom檔案E:\maven\repository\com\sun\jersey\jersey-project\1.19.1\jersey-project-1.19.1.pom解析出錯,然後我們去此目錄下,可以發現這個檔案未被下載下傳完整。最後的解決方式是删除此檔案的父目錄,重新打包,則會自動下載下傳一份完整的pom檔案,警告消失,打包成功。
4.3 OkToRetryOnAllOperations和RequestSpecificRetryOn失效
在上文的例子中,消費者服務調用的生産者服務接口是GET類型,自動重試沒有任何問題。接着,本人嘗試讓消費者調用生産者服務的POST接口,同時仍然設定了ribbon.OkToRetryOnAllOperations=true,結果無法成功重試,然後去調試ribbon源碼,檢視自動重試機制,經查,OkToRetryOnAllOperations和RequestSpecificRetryOn屬性可以成功擷取到,但RequestSpecificRetryOn并未被擷取出來使用,疑似Ribbon的bug。這裡記錄一下調試的重要部分,
(1)Ribbon配置屬性類com.netflix.client.config.CommonClientConfigKey;
(2)Ribbon判斷是否重試:

是以,單獨使用Ribbon需謹慎!