kubernetes使用Feign实现服务间调用

在spring cloud中服务之间的调用我们通常是通过Feign来完成的。Feign作为一个声明式WebService客户端,使用非常的简单,通过在我们的接口上添加@FeignClient注解,我们很容易就实现一个服务调用的客户端。使用注解也可以减少开发的代码量,可以说非常的方便。另外Feign内部也集成了Ribbon从而自动帮我们实现客户端的负载均衡,可以说是spring cloud微服务的必用组件。

一、背景

通常我们使用spring cloud进行微服务开发的时候我们通常会配置相应的注册中心,比如:Eureka、Nacos、Consul,这样Feign在进行服务调用的时候一般会到注册中心定位具体的服务地址,然后在通过Ribbon实现路由到具体的服务节点,从而实现服务的调用。这次我们项目小组在做项目改造的时候技术栈虽然也选择了spring cloud,但是我们并没有完全按照一般的模式进行开发。最开始的时候我们确定注册中心使用的是Nacos,但是通过和其他部门的商议,决定放弃使用Nacos,而使用k8s原生的服务发现功能(微服务部署在k8s集群)。所以随之而来的就是使用spring cloud kubernets。感兴趣的可以看官方的介绍:Spring Cloud Kubernetes,自己在家办公期间也看了一些文档,并做了一个demo进行了技术上的验证,使用上是没有问题的。感觉正常办公之后应该就可以进行正常开发了,然而到现场办公之后我们需要和其他部门的服务之间进行交互,这时候好像遇到了了问题,我们的服务是在自己的namespace下,这样服务之间怎么调用????(这里说明一点,应该是可以发现k8s所有namespace下的服务的)赶紧去找其他部门的大佬请教,毕竟在k8s上我真的是菜鸟,一看恍然大悟,自己咋就这么笨呢.......好吧,既然这样我也按照他们的方式,把spring cloud kubernets去掉吧(其实不需要去除),最终整个微服务保留的依赖只有Feign。
其实如果对Feign熟悉的话应该知道Feign除了可以通过服务名称调用之外,还可以通过URL,而同时使用name和url的话,以url为准(这个自己网上看到的资料,没有验证)。这样我们就可以直接通过在配置文件配置好相关服务的url就好了吗!!!完美解决.....

二、demo

下面我通过一个小demo来演示一下,我就创建两个服务,一个服务的提供方service-provider,一个服务的调用方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项目我们写一个很简单的被调用的接口,代码如下:

@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、 服务调用者:service-consumer

service-consumer项目的pom和上文一样,代码部分我们编写一个调用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的客户端,因为我们没有使用注册中心,因此通过服务名称来实现服务调用是行不通的,必须通过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,然后分别调用"hello"和"call"两个接口:
结果分别如下:

图-1.png

图-2.png

因为我们开启了hystrix,所以在服务提供者不可用的时候,返回了Fallback的结果。
接着我们启动服务提供者service-provider,并再次调用"call"接口,结果如下:
图-3.png

返回的结果正确了,说明通过url成功实现了服务间的调用。


有人会说通过url实现服务间的调用没什么用的,你一个服务会有那个多实例,服务的负载均衡怎么办?确实如此,但是呢,在k8s中就好解决了啊,k8s本身提供了服务发现的功能。我们知道k8s中服务--Service是一个逻辑上的概念,服务本身并不会提供具体的服务,具体的服务是由服务的pod完成的。一个服务可以有一个或多个pod,也就是我们所说的实例,通过服务路由到某一个具体的pod,由k8s帮我们去完成,我们不需要关心,当然感兴趣的可以自己研究一下,其实原理应该都差不多,一个服务有个Endpoint的地址列表(虽然都是虚拟的,但是k8s内部可以访问)。所以Feign的url我们只需要配置成k8s中我们服务的地址即可,而在k8s中服务的地址是:<service_name>.<namespace>.svc.<domain>,一般<domain>的值都是固定的,所以可以简写成<service_name>.<namespace>,即我们只需要服务的名称和其所在的namespace就可以访问了。其实想想我们项目组还是不太应该去掉spring cloud kubernets依赖,去除掉之后服务间调用需要在配置文件添加服务的名称和url,不是很方便.....没有因为同一个namespace下直接根据服务名称就可以进行调用了。不得不说使用k8s之后真的方便了很多......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351