前面我们使用Ribbon实现了客户端调用的负载均衡。但我们的调用还是使用的RestTemplate

1
2
3
4
5
6
7
@Autowired
private RestTemplate restTemplate;

@GetMapping("/consumer/{id}")
public String consume(@PathVariable String id) {
return restTemplate.getForObject("http://eureka-client-service-provider/provider?id=" + id, String.class);
}

这样的做法有两个不好的地方。一个是当URL中的参数过多时难以进行拼接维护;另一个是我们还是能够很明显的知道我们在发送HTTP请求。我们期望能够像调用本地方法一样去调用远程服务,不用关心URL的参数拼接和HTTP请求的发送。

Feign简介

Feign是一个声明式HTTP客户端,我们只需按照一定的规则定义好接口,它就能帮助我们完成HTTP的调用。Spring CloudFeign进行了增强,使其支持了Spring MVC注解,同时整合了RibbonEureka,让微服务间的调用更加方便。

使用Feign调用远程服务

IDEA中创建一个名为openfeignmaven工程,然后在pom.xml中引入以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!-- version -->
<properties>
<spring.boot.version>2.2.0.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
</properties>

<!-- 预定义依赖 -->
<dependencyManagement>
<dependencies>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud -->
<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>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
</dependencies>

src/main/resources目录下创建application.yml配置文件,并添加以下配置项:

1
2
3
4
5
6
7
8
9
server:
port: 9004
spring:
application:
name: openfeign-service-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:520/eureka/,http://localhost:521/eureka/

编写启动类OpenFeignApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.sunchaser.sparrow.microservice.springcloud.openfeign;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/2/23
*/
@SpringBootApplication
@EnableFeignClients
public class OpenFeignApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(OpenFeignApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}

使用org.springframework.cloud.openfeign.EnableFeignClients注解开启对feign客户端的扫描。

下面我们就来写一个feign客户端来完成微服务间的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.sunchaser.sparrow.microservice.springcloud.openfeign.clients;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/9/13
*/
@FeignClient(url = "http://127.0.0.1:9000", name = "ProviderFeignClient")
public interface ProviderFeignClient {
@GetMapping("/provider")
String provide(@RequestParam(value = "id") String id);
}

使用@FeignClient注解声明该接口是一个feign客户端从而被@EnableFeignClients注解扫描到,指定url为服务提供方的地址http://127.0.0.1:9000(提前启动好了服务提供者eureka-client-service-provider),指定name为任意唯一id(当前接口类名)。

然后我们就可以像定义Spring MVC中的Controller那样定义我们的接口方法。使用@GetMapping("/provider")定义请求方式和请求路径,使用@RequestParam(value = "id")定义请求参数。

接下来我们就可以使用feign客户端了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.sunchaser.sparrow.microservice.springcloud.openfeign.controller;

import com.sunchaser.sparrow.microservice.springcloud.openfeign.clients.ProviderFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/9/13
*/
@RestController
public class OpenFeignConsumerController {
@Autowired
private ProviderFeignClient providerFeignClient;

@GetMapping("/consumer/{id}")
public String consume(@PathVariable String id) {
return providerFeignClient.provide(id);
}
}

我们像调用本地方法一样调用了远程服务。发送GET请求:GET http://localhost:9004/consumer/1,可看到浏览器输出为provide:1

调用注册中心Eureka上的服务提供者

上面的调用我们是通过@FeignClient注解的url属性指定了服务提供者的地址。这种情况适用于服务提供者为第三方提供的REST API,无法注册到内部的注册中心上。

对于服务注册中心上的服务提供者,我们只需要指定name属性为服务提供者的名称即可进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.sunchaser.sparrow.microservice.springcloud.openfeign.clients;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/9/13
*/
//@FeignClient(url = "http://127.0.0.1:9000", name = "ProviderFeignClient")
@FeignClient(name = "eureka-client-service-provider")
public interface ProviderFeignClient {
@GetMapping("/provider")
String provide(@RequestParam(value = "id") String id);
}

负载均衡器Ribbon会将eureka-client-service-provider解析成Eureka Server服务注册表中的服务地址进行负载均衡调用。

重启openfein服务。重复发送GET请求:GET http://localhost:9004/consumer/1,可看到两个服务提供者实例控制台上均有相关日志输出。

至此,我们使用feign完成了服务间的调用,同时还实现了客户端的负载均衡。