在spring cloud中服務之間的調(diào)用我們通常是通過Feign來完成的昧识。Feign作為一個聲明式WebService客戶端攻臀,使用非常的簡單焕数,通過在我們的接口上添加@FeignClient
注解,我們很容易就實現(xiàn)一個服務調(diào)用的客戶端刨啸。使用注解也可以減少開發(fā)的代碼量堡赔,可以說非常的方便。另外Feign內(nèi)部也集成了Ribbon從而自動幫我們實現(xiàn)客戶端的負載均衡设联,可以說是spring cloud微服務的必用組件善已。
一、背景
通常我們使用spring cloud進行微服務開發(fā)的時候我們通常會配置相應的注冊中心离例,比如:Eureka换团、Nacos、Consul
宫蛆,這樣Feign在進行服務調(diào)用的時候一般會到注冊中心定位具體的服務地址艘包,然后在通過Ribbon實現(xiàn)路由到具體的服務節(jié)點,從而實現(xiàn)服務的調(diào)用耀盗。這次我們項目小組在做項目改造的時候技術(shù)棧雖然也選擇了spring cloud辑甜,但是我們并沒有完全按照一般的模式進行開發(fā)。最開始的時候我們確定注冊中心使用的是Nacos袍冷,但是通過和其他部門的商議,決定放棄使用Nacos猫牡,而使用k8s原生的服務發(fā)現(xiàn)功能(微服務部署在k8s集群)胡诗。所以隨之而來的就是使用spring cloud kubernets。感興趣的可以看官方的介紹:Spring Cloud Kubernetes,自己在家辦公期間也看了一些文檔煌恢,并做了一個demo進行了技術(shù)上的驗證骇陈,使用上是沒有問題的。感覺正常辦公之后應該就可以進行正常開發(fā)了瑰抵,然而到現(xiàn)場辦公之后我們需要和其他部門的服務之間進行交互你雌,這時候好像遇到了了問題,我們的服務是在自己的namespace下二汛,這樣服務之間怎么調(diào)用婿崭??肴颊?氓栈?(這里說明一點,應該是可以發(fā)現(xiàn)k8s所有namespace下的服務的)趕緊去找其他部門的大佬請教婿着,畢竟在k8s上我真的是菜鳥授瘦,一看恍然大悟,自己咋就這么笨呢.......好吧竟宋,既然這樣我也按照他們的方式提完,把spring cloud kubernets去掉吧(其實不需要去除),最終整個微服務保留的依賴只有Feign丘侠。
其實如果對Feign熟悉的話應該知道Feign除了可以通過服務名稱調(diào)用之外徒欣,還可以通過URL,而同時使用name和url的話婉陷,以url為準(這個自己網(wǎng)上看到的資料帚称,沒有驗證)。這樣我們就可以直接通過在配置文件配置好相關(guān)服務的url就好了嗎;喟摹4扯谩!完美解決.....
二担神、demo
下面我通過一個小demo來演示一下楼吃,我就創(chuàng)建兩個服務,一個服務的提供方service-provider妄讯,一個服務的調(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亥贸、 服務提供者:service-provider
在service-provider項目我們寫一個很簡單的被調(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);
}
}
服務配置文件如下:
spring.application.name=service-provider
server.port=18080
2、 服務調(diào)用者:service-consumer
service-consumer項目的pom和上文一樣炕置,代碼部分我們編寫一個調(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);
}
}
接著我們需要定一個Feign的客戶端男韧,因為我們沒有使用注冊中心,因此通過服務名稱來實現(xiàn)服務調(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必須要有值,因此必須要配置口锭,雖然沒用朦前。服務的name和url的值讀取的是配置文件的值。配置文件如下:
spring.application.name=service-consumer
server.port=8080
service.provider.name=provider
service.provider.url=http://localhost:18080
feign.hystrix.enabled=true
這樣兩個服務的代碼和配置就算完成了鹃操,接下來我們就測試一下韭寸,首先啟動service-consumer,然后分別調(diào)用"hello"和"call"兩個接口:
結(jié)果分別如下:
因為我們開啟了
hystrix
组民,所以在服務提供者不可用的時候棒仍,返回了Fallback的結(jié)果。接著我們啟動服務提供者service-provider臭胜,并再次調(diào)用"call"接口莫其,結(jié)果如下:
返回的結(jié)果正確了,說明通過url成功實現(xiàn)了服務間的調(diào)用耸三。
有人會說通過url實現(xiàn)服務間的調(diào)用沒什么用的乱陡,你一個服務會有那個多實例,服務的負載均衡怎么辦仪壮?確實如此憨颠,但是呢,在k8s中就好解決了啊积锅,k8s本身提供了服務發(fā)現(xiàn)的功能爽彤。我們知道k8s中服務--Service是一個邏輯上的概念,服務本身并不會提供具體的服務缚陷,具體的服務是由服務的pod完成的适篙。一個服務可以有一個或多個pod,也就是我們所說的實例箫爷,通過服務路由到某一個具體的pod嚷节,由k8s幫我們?nèi)ネ瓿桑覀儾恍枰P(guān)心虎锚,當然感興趣的可以自己研究一下硫痰,其實原理應該都差不多,一個服務有個Endpoint的地址列表(雖然都是虛擬的窜护,但是k8s內(nèi)部可以訪問)效斑。所以Feign的url我們只需要配置成k8s中我們服務的地址即可,而在k8s中服務的地址是:<service_name>.<namespace>.svc.<domain>
柱徙,一般<domain>的值都是固定的鳍悠,所以可以簡寫成<service_name>.<namespace>
税娜,即我們只需要服務的名稱和其所在的namespace就可以訪問了。其實想想我們項目組還是不太應該去掉spring cloud kubernets依賴藏研,去除掉之后服務間調(diào)用需要在配置文件添加服務的名稱和url,不是很方便.....沒有因為同一個namespace下直接根據(jù)服務名稱就可以進行調(diào)用了概行。不得不說使用k8s之后真的方便了很多......