SpringCloud Nacos + Ribbon 調用服務的 2 種方法!
- 2022 年 3 月 10 日
- 筆記
在 Nacos 中,服務調用主要是通過 RestTemplate + Ribbon 實現的,RestTemplate 是 Spring 提供的 Restful 請求實現類,而 Ribbon 是客戶端負載均衡器,通過 Ribbon 可以獲取服務實例的具體資訊(IP 和埠號),之後再通過 RestTemplate 加服務實例的具體資訊就可以完成一次服務調用了。
而 RestTemplate + Ribbon 調用服務的實現方式兩種:通過程式碼的方式調用服務和通過註解方式調用服務。但兩種實現方式的原理都是一樣的:都是通過註冊中心,將可用服務列表拉取到本地(客戶端),再通過客戶端負載均衡器得到某個伺服器的具體資訊,然後請求此伺服器即可,如下圖所示:
1.程式碼方式調用
通過程式碼的方式調用服務在實際工作中並不常用,主要是寫法太麻煩,但了解它對於後面理解註解調用方式有很大的幫助,所以我們這裡重點來看一下。
服務調用需要有兩個角色:一個是服務提供者(Provider),另一個是服務調用者(Consumer),接下來我們來創建一下這兩個角色。
1.1 創建服務提供者:Provider
第一步:先創建一個 Spring Boot 項目(Spring Cloud 項目是基於 Spring Boot 創建的),添加 spring-web 和 nacos-discovery 依賴,具體依賴資訊如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支援 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:設置 Nacos 相關配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-provider # 項目名稱(nacos 註冊的服務名)
cloud:
nacos:
discovery:
username: nacos # nacos 登錄用戶名
password: nacos666 # nacos 密碼
server-addr: 127.0.0.1:8848 # nacos 服務端地址
server:
port: 8081 # 項目啟動埠號
第三步:添加服務方法,如下程式碼所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class HttpProviderApplication {
public static void main(String[] args) {
SpringApplication.run(HttpProviderApplication.class, args);
}
/**
* 為客戶端提供可調用的介面
*/
@RequestMapping("/call/{name}")
public String call(@PathVariable String name) {
return "I'm Provider. Received a message from: " + name;
}
}
然後使用相同的方法再創建 2 個服務提供者,最終對應的埠號分別為:
127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083
這 3 個服務提供者分別列印的內容是「I’m Provider…」、「I’m Provider2…」、「I’m Provider3…」,如下圖所示:
1.2 創建服務調用者:Consumer
本文的核心是服務調用者的實現程式碼,它的創建方式和服務提供者的創建方式類似。
第一步:創建一個 Spring Boot 項目,添加 spring-web 和 nacos-discovery 依賴,具體依賴內容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支援 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
可能有人會有疑問,本文標題是 Spring Cloud Alibaba Nacos + Ribbon,那為什麼不添加 Ribbon 的依賴呢?
這是因為 Spring Cloud Alibaba Nacos 中已經內置了 Ribbon 框架了,打開項目的依賴樹就可以清楚的看到了,如下圖所示:
第二步:設置 Nacos 相關配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-consumer # 項目名稱(nacos 註冊的服務名)
cloud:
nacos:
discovery:
username: nacos # nacos 登錄用戶名
password: nacos666 # nacos 密碼
server-addr: 127.0.0.1:8848 # nacos 服務端地址
server:
port: 8091 # 項目啟動埠號
第三步:在項目啟動類中,使用 Spring Java Config 的方式聲明 RestTemplate 對象,如下程式碼所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class RibbonCodeConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonCodeConsumerApplication.class, args);
}
/**
* 使用 Spring Java Config 方式聲明 RestTemplate
*/
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
第四步:使用 RestTemplate + Ribbon 的程式碼方式調用服務,首先使用 Ribbon 提供的 LoadBalancerClient 對象的 choose 方法,根據 Nacos 中的服務 id 獲取某個健康的服務實例,服務實例中包含服務的 IP 地址和埠號,然後再使用 RestTemplate 根據獲取到的 IP 和 埠號訪問服務即可,具體實現程式碼如下:
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class ConsumerController {
// Ribbon 提供的負載均衡對象
@Resource
private LoadBalancerClient loadBalancerClient;
// Spring 提供進行 Restful 請求對象
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer")
public String consumer(@RequestParam String name) {
// 根據 Ribbon 提供的對象 + Nacos 的服務 id 獲取服務實例
ServiceInstance serviceInstance = loadBalancerClient.choose("springcloud-nacos-provider");
// 獲取服務實例中的 ip
String ip = serviceInstance.getHost();
// 獲取服務實例中的埠號
int port = serviceInstance.getPort();
// 使用 restTemplate 請求並獲取結果
String result = restTemplate.getForObject("//" + ip + ":" + port + "/call/" + name,String.class);
return result;
}
}
以上程式的執行結果如下圖所示:
2.註解方式調用
使用註解方式調用服務就簡單多了,服務提供者的創建方法和上面相同,這裡就不再贅述了,接下來我們來創建一個註解方式的服務調用者 Consumer。
第一步:創建一個 Spring Boot 項目,添加 spring-web 和 nacos-discovery 依賴,具體依賴內容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支援 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:設置 Nacos 相關配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-consumer # 項目名稱(nacos 註冊的服務名)
cloud:
nacos:
discovery:
username: nacos # nacos 登錄用戶名
password: nacos666 # nacos 密碼
server-addr: 127.0.0.1:8848 # nacos 服務端地址
server:
port: 8092 # 項目啟動埠號
第三步:在項目啟動類中,使用 Spring Java Config 的方式聲明 RestTemplate 對象,此步驟中,需要在 RestTemplate 對象上加上 @LoadBalanced 註解,加上此註解之後就可以讓 RestTemplate 對象自動支援負載均衡了,如下程式碼所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class RibbonAnnotationConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonAnnotationConsumerApplication.class, args);
}
@LoadBalanced // 使 RestTemplate 自動支援 Ribbon 負載均衡
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
第四步:創建客戶端請求方法,具體實現程式碼如下:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class ConsumerController {
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer")
public String consumer(@RequestParam String name) {
// 請求並獲取結果(springcloud-nacos-provider 為 Nacos 服務id)
String result = restTemplate.getForObject("//springcloud-nacos-provider/call/" + name, String.class);
return result;
}
}
以上程式的執行結果如下圖所示:
註解實現原理分析
通過上述程式碼我們可以看出,Nacos 實現調用服務的關鍵是通過 @LoadBalanced,它為 RestTemplate 賦予了負載均衡的能力,從而可以正確的調用到服務,那 @LoadBalanced 是如何實現的呢?
要知道這個問題的答案,就得閱讀 LoadBalancerAutoConfiguration 的源碼。LoadBalancerAutoConfiguration 是實現客戶端負載均衡器的自動裝配類,隨著 Spring 的啟動而啟動,它的源碼內容有很多,我們這裡截取部分核心的方法來看一下:
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> {
restTemplateCustomizers.ifAvailable((customizers) -> {
Iterator var2 = this.restTemplates.iterator();
while(var2.hasNext()) {
RestTemplate restTemplate = (RestTemplate)var2.next();
Iterator var4 = customizers.iterator();
while(var4.hasNext()) {
RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
customizer.customize(restTemplate);
}
}
});
};
}
這裡的 this.restTemplates.iterator() 既所有被 @LoadBalanced 註解修飾的 RestTemplate 對象,所有被 @LoadBalanced 修飾的 RestTemplate 對象會被強轉為 RestTemplateCustomizer 對象,而這個對象的實現源碼如下:
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
也就是所有被 @LoadBalanced 註解修飾的 RestTemplate 對象,會為其添加一個 loadBalancerInterceptor 的攔截器,攔截器的實現源碼如下:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
從上述源碼可以看出,@LoadBalanced 的執行流程是,被 @LoadBalanced 註解修飾的 RestTemplate 對象,會被 LoadBalancerInterceptor 攔截器所攔截,攔截之後使用 LoadBalancerClient 對象,按照負載均衡的策略獲取一個健康的服務實例,然後再通過服務實例的 IP 和埠,調用實例方法,從而完成服務請求。
總結
Nacos 調用 Restful 服務是通過內置的 Ribbon 框架實現的,它有兩種調用方法,通過程式碼的方式或通過註解的方式完成調用。其中註解的方式使用起來比較簡單,只需要在 RestTemplate 對象上添加一個 @LoadBalanced 註解,就可以為請求對象賦予負載均衡的能力了。
是非審之於己,毀譽聽之於人,得失安之於數。
公眾號:Java中文社群
Java面試合集://gitee.com/mydb/interview