在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"两个接口:
结果分别如下:
因为我们开启了
hystrix
,所以在服务提供者不可用的时候,返回了Fallback的结果。接着我们启动服务提供者service-provider,并再次调用"call"接口,结果如下:
返回的结果正确了,说明通过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之后真的方便了很多......