在spring cloud中服務(wù)之間的調(diào)用我們通常是通過Feign來完成的。Feign作為一個(gè)聲明式WebService客戶端,使用非常的簡(jiǎn)單,通過在我們的接口上添加@FeignClient注解,我們很容易就實(shí)現(xiàn)一個(gè)服務(wù)調(diào)用的客戶端。使用注解也可以減少開發(fā)的代碼量,可以說非常的方便。另外Feign內(nèi)部也集成了Ribbon從而自動(dòng)幫我們實(shí)現(xiàn)客戶端的負(fù)載均衡,可以說是spring cloud微服務(wù)的必用組件。
一、背景
通常我們使用spring cloud進(jìn)行微服務(wù)開發(fā)的時(shí)候我們通常會(huì)配置相應(yīng)的注冊(cè)中心,比如:Eureka、Nacos、Consul,這樣Feign在進(jìn)行服務(wù)調(diào)用的時(shí)候一般會(huì)到注冊(cè)中心定位具體的服務(wù)地址,然后在通過Ribbon實(shí)現(xiàn)路由到具體的服務(wù)節(jié)點(diǎn),從而實(shí)現(xiàn)服務(wù)的調(diào)用。這次我們項(xiàng)目小組在做項(xiàng)目改造的時(shí)候技術(shù)棧雖然也選擇了spring cloud,但是我們并沒有完全按照一般的模式進(jìn)行開發(fā)。最開始的時(shí)候我們確定注冊(cè)中心使用的是Nacos,但是通過和其他部門的商議,決定放棄使用Nacos,而使用k8s原生的服務(wù)發(fā)現(xiàn)功能(微服務(wù)部署在k8s集群)。所以隨之而來的就是使用spring cloud kubernets。感興趣的可以看官方的介紹:Spring Cloud Kubernetes,自己在家辦公期間也看了一些文檔,并做了一個(gè)demo進(jìn)行了技術(shù)上的驗(yàn)證,使用上是沒有問題的。感覺正常辦公之后應(yīng)該就可以進(jìn)行正常開發(fā)了,然而到現(xiàn)場(chǎng)辦公之后我們需要和其他部門的服務(wù)之間進(jìn)行交互,這時(shí)候好像遇到了了問題,我們的服務(wù)是在自己的namespace下,這樣服務(wù)之間怎么調(diào)用????(這里說明一點(diǎn),應(yīng)該是可以發(fā)現(xiàn)k8s所有namespace下的服務(wù)的)趕緊去找其他部門的大佬請(qǐng)教,畢竟在k8s上我真的是菜鳥,一看恍然大悟,自己咋就這么笨呢.......好吧,既然這樣我也按照他們的方式,把spring cloud kubernets去掉吧(其實(shí)不需要去除),最終整個(gè)微服務(wù)保留的依賴只有Feign。
其實(shí)如果對(duì)Feign熟悉的話應(yīng)該知道Feign除了可以通過服務(wù)名稱調(diào)用之外,還可以通過URL,而同時(shí)使用name和url的話,以u(píng)rl為準(zhǔn)(這個(gè)自己網(wǎng)上看到的資料,沒有驗(yàn)證)。這樣我們就可以直接通過在配置文件配置好相關(guān)服務(wù)的url就好了嗎!??!完美解決.....
二、demo
下面我通過一個(gè)小demo來演示一下,我就創(chuàng)建兩個(gè)服務(wù),一個(gè)服務(wù)的提供方service-provider,一個(gè)服務(wù)的調(diào)用方service-consumer。
在pom文件中添加Feign的依賴。pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ypc.cloud</groupId>
<artifactId>service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-provider</name>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1、 服務(wù)提供者:service-provider
在service-provider項(xiàng)目我們寫一個(gè)很簡(jiǎn)單的被調(diào)用的接口,代碼如下:
@Slf4j
@RestController
public class DemoController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String applicationName;
@GetMapping("/provider/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("hello from " + applicationName + ", service port=" + port);
}
}
服務(wù)配置文件如下:
spring.application.name=service-provider
server.port=18080
2、 服務(wù)調(diào)用者:service-consumer
service-consumer項(xiàng)目的pom和上文一樣,代碼部分我們編寫一個(gè)調(diào)用service-provider的接口,代碼如下:
@Slf4j
@RestController
@RequestMapping("/consumer")
public class HelloController {
private ProviderClient providerClient;
public HelloController(ProviderClient providerClient) {
this.providerClient = providerClient;
}
@GetMapping("/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("hello from service-consumer");
}
@GetMapping("/feign")
public ResponseEntity<String> call() {
log.info(">>>> call service-provider <<<<");
String result = providerClient.hello();
return ResponseEntity.ok(result);
}
}
接著我們需要定一個(gè)Feign的客戶端,因?yàn)槲覀儧]有使用注冊(cè)中心,因此通過服務(wù)名稱來實(shí)現(xiàn)服務(wù)調(diào)用是行不通的,必須通過url。
ProviderClient代碼如下:
@FeignClient(name = "${service.provider.name}",url = "${service.provider.url}",fallback = ProviderClientFallback.class)
public interface ProviderClient {
@GetMapping("/provider/hello")
String hello();
}
@FeignClient注解中name或者value必須要有值,因此必須要配置,雖然沒用。服務(wù)的name和url的值讀取的是配置文件的值。配置文件如下:
spring.application.name=service-consumer
server.port=8080
service.provider.name=provider
service.provider.url=http://localhost:18080
feign.hystrix.enabled=true
這樣兩個(gè)服務(wù)的代碼和配置就算完成了,接下來我們就測(cè)試一下,首先啟動(dòng)service-consumer,然后分別調(diào)用"hello"和"call"兩個(gè)接口:
結(jié)果分別如下:


因?yàn)槲覀冮_啟了
hystrix,所以在服務(wù)提供者不可用的時(shí)候,返回了Fallback的結(jié)果。接著我們啟動(dòng)服務(wù)提供者service-provider,并再次調(diào)用"call"接口,結(jié)果如下:

返回的結(jié)果正確了,說明通過url成功實(shí)現(xiàn)了服務(wù)間的調(diào)用。
有人會(huì)說通過url實(shí)現(xiàn)服務(wù)間的調(diào)用沒什么用的,你一個(gè)服務(wù)會(huì)有那個(gè)多實(shí)例,服務(wù)的負(fù)載均衡怎么辦?確實(shí)如此,但是呢,在k8s中就好解決了啊,k8s本身提供了服務(wù)發(fā)現(xiàn)的功能。我們知道k8s中服務(wù)--Service是一個(gè)邏輯上的概念,服務(wù)本身并不會(huì)提供具體的服務(wù),具體的服務(wù)是由服務(wù)的pod完成的。一個(gè)服務(wù)可以有一個(gè)或多個(gè)pod,也就是我們所說的實(shí)例,通過服務(wù)路由到某一個(gè)具體的pod,由k8s幫我們?nèi)ネ瓿?,我們不需要關(guān)心,當(dāng)然感興趣的可以自己研究一下,其實(shí)原理應(yīng)該都差不多,一個(gè)服務(wù)有個(gè)Endpoint的地址列表(雖然都是虛擬的,但是k8s內(nèi)部可以訪問)。所以Feign的url我們只需要配置成k8s中我們服務(wù)的地址即可,而在k8s中服務(wù)的地址是:<service_name>.<namespace>.svc.<domain>,一般<domain>的值都是固定的,所以可以簡(jiǎn)寫成<service_name>.<namespace>,即我們只需要服務(wù)的名稱和其所在的namespace就可以訪問了。其實(shí)想想我們項(xiàng)目組還是不太應(yīng)該去掉spring cloud kubernets依賴,去除掉之后服務(wù)間調(diào)用需要在配置文件添加服務(wù)的名稱和url,不是很方便.....沒有因?yàn)橥粋€(gè)namespace下直接根據(jù)服務(wù)名稱就可以進(jìn)行調(diào)用了。不得不說使用k8s之后真的方便了很多......