kubernetes使用Feign實(shí)現(xiàn)服務(wù)間調(diào)用

在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é)果分別如下:

圖-1.png

圖-2.png

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

返回的結(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之后真的方便了很多......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容