OpenFeign的介绍和使用

项目地址:https://spring.io/projects/spring-cloud-openfeign

OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。

Feign 与 OpenFeign

Spring Cloud OpenFeign 并不是独立的技术。它底层基于 Netflix Feign,Netflix Feign 是 Netflix 设计的开源的声明式 WebService 客户端,用于简化服务间通信。Netflix Feign 采用“接口+注解”的方式开发,通过模仿 RPC 的客户端与服务器模式(CS),采用接口方式开发来屏蔽网络通信的细节。OpenFeign 则是在 Netflix Feign 的基础上进行封装,结合原有 Spring MVC 的注解,对 Spring Cloud 微服务通信提供了良好的支持。使用 OpenFeign 开发的方式与开发 Spring MVC Controller 颇为相似。

OpenFeign的使用

服务提供者

第一步,利用 Spring Initializr 向导创建 warehouse-service 工程。

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

第二步,编辑 application.yml,配置nacos服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 应用名称
spring:
application:
name: warehouse-service
cloud:
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
nacos:
discovery:
username: nacos
password: nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
server-addr: 47.106.172.60:8848
# 注册到 nacos 的指定 namespace,默认为 public
namespace: public

# 应用服务 WEB 访问端口
server:
port: 9010

第三步,创建 Stock 库存类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 库存商品对象
*/
@Data
public class Stock {
/** 商品品类编号 */
private Long skuId;
/** 商品与品类名称 */
private String title;
/** 库存数量 */
private Integer quantity;
/** 单位 */
private String unit;
/** 描述信息 */
private String description;

public Stock(Long skuId, String title, Integer quantity, String unit) {
this.skuId = skuId;
this.title = title;
this.quantity = quantity;
this.unit = unit;
}
}

第四步,创建仓储服务控制器 WarehouseController。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//仓储服务控制器
@RestController
public class WarehouseController {

@GetMapping("/stock")
public Stock getStock(Long skuId){
Map result = new HashMap();
Stock stock = null;
if(skuId == 1101l){
//模拟有库存商品
stock = new Stock(1101l, "Apple iPhone 11 128GB 紫色", 32, "台");
stock.setDescription("Apple 11 紫色版对应商品描述");
}else if(skuId == 1102l){
//模拟无库存商品
stock = new Stock(1101l, "Apple iPhone 11 256GB 白色", 0, "台");
stock.setDescription("Apple 11 白色版对应商品描述");
}else{
//演示案例,暂不考虑无对应 skuId 的情况
}
return stock;
}
}

第五步,启动服务,将服务 warehouse-service 注册到Nacos。

image-20211211231459822

服务消费者

第一步,利用 Spring Initializr 创建 order-service 工程,引入以下依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

第二步,启用 OpenFeign 需要在应用入口 OrderServiceApplication 增加 @EnableFeignClients 注解,其含义为通知 Spring 启用 OpenFeign 声明式通信。

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients // 启用openfeign
public class OrderServiceApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

第三步,默认 OpenFeign 并不需要任何配置,在 application.yml 配置nacos服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 应用名称
spring:
application:
name: order-service
cloud:
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
nacos:
discovery:
username: nacos
password: nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
server-addr: 47.106.172.60:8848
# 注册到 nacos 的指定 namespace,默认为 public
namespace: public

# 应用服务 WEB 访问端口
server:
port: 8081

第四步,创建OpenFeign的通信接口与响应对象。

1
2
3
4
5
@FeignClient("warehouse-service")
public interface WarehouseFeignClient {
@GetMapping("/stock")
Stock getStock(@RequestParam("skuId") Long skuId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 库存商品对象
*/
@Data
@ToString
public class Stock {
/** 商品品类编号 */
private Long skuId;
/** 商品与品类名称 */
private String title;
/** 库存数量 */
private Integer quantity;
/** 单位 */
private String unit;

}

OpenFeign 通过“接口+注解”形式描述数据传输逻辑,并不需要程序员编写具体实现代码便能实现服务间高可用通信。

  • @FeignClient 注解说明当前接口为 OpenFeign 通信客户端,参数值 warehouse-service 为服务提供者 ID,这一项必须与 Nacos 注册 ID 保持一致。在 OpenFeign 发送请求前会自动在 Nacos 查询 warehouse-service 所有可用实例信息,再通过内置的 Ribbon 负载均衡选择一个实例发起 RESTful 请求,进而保证通信高可用。
  • 声明的方法结构,接口中定义的方法通常与服务提供者的方法定义保持一致。这里有个非常重要的细节:用于接收数据的 Stock 对象并不强制要求与提供者端 Stock 对象完全相同,消费者端的 Stock 类可以根据业务需要删减属性,但属性必须要与提供者响应的 JSON 属性保持一致,在 OpenFeign 获取响应后便根据 JSON 属性名自动反序列化到 Stock 对象中。
1
2
3
4
1.在第一次访问 WarehouseFeignClient 接口时,Spring 自动生成接口的实现类并实例化对象。
2.当调用 getStock() 方法时,Ribbon 获取 warehouse-service 可用实例信息,根据负载均衡策略选择合适实例。
3.OpenFeign 根据方法上注解描述的映射关系生成完整的 URL 并发送 HTTP 请求,如果请求方法是 @PostMapping,则参数会附加在请求体中进行发送。
4.warehouse-service 处理完毕返回 JSON 数据,消费者端 OpenFeign 接收 JSON 的同时反序列化到 Stock 对象,并将该对象返回。

如何更改 OpenFeign 默认的负载均衡策略

服务消费者 application.yml 文件增加指定服务提供者的负载均衡配置信息。

1
2
3
4
# 指定服务的负载均衡策略
warehouse-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

为何通过yml文件能配置负载均衡策略

关键类:RibbonClientConfiguration

1
2
3
4
5
6
7
8
9
10
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}

是否设置负载均衡规则IRule

1
2
3
public boolean isSet(Class clazz, String name) {
return StringUtils.hasText(getClassName(clazz, name));
}

判断规则就是应用环境是否设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param key com.netflix.loadbalancer
* @param name warehouse-service yml配置的服务提供者名称
* return com.netflix.loadbalancer.RandomRule
*/
public String getClassName(Class clazz, String name) {
if (this.classToProperty.containsKey(clazz)) {
// 此时classNameProperty是NFLoadBalancerRuleClassName
String classNameProperty = this.classToProperty.get(clazz);
// name + "." + NAMESPACE + "." + classNameProperty 为 warehouse-servic.ribbon.NFLoadBalancerRuleClassName
// className为yml配置的负载均衡规则全限定类名 com.netflix.loadbalancer.RandomRule
String className = environment
.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
return className;
}
return null;
}

classToProperty是什么呢

1
2
3
4
5
6
7
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}

通过如果配置存则获取负载均衡规则

1
return this.propertiesFactory.get(IRule.class, config, name)

反射实例化yml配置的负载均衡规则类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public <C> C get(Class<C> clazz, IClientConfig config, String name) {
String className = getClassName(clazz, name);
if (StringUtils.hasText(className)) {
try {
Class<?> toInstantiate = Class.forName(className);
return (C) SpringClientFactory.instantiateWithConfig(toInstantiate,
config);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load " + className
+ " for class " + clazz + " named " + name);
}
}
return null;
}

开启默认的 OpenFeign 数据压缩功能

在 OpenFeign 中,默认并没有开启数据压缩功能。但如果你在服务间单次传递数据超过 1K 字节,强烈推荐开启数据压缩功能。默认 OpenFeign 使用 Gzip 方式压缩数据,对于大文本通常压缩后尺寸只相当于原始数据的 10%~30%,这会极大提高带宽利用率。但有一种情况除外,如果应用属于计算密集型,CPU 负载长期超过 70%,因数据压缩、解压缩都需要 CPU 运算,开启数据压缩功能反而会给 CPU 增加额外负担,导致系统性能降低,这是不可取的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# OpenFeign 数据压缩功能
feign:
# 压缩
compression:
# 请求
request:
# 开启请求数据的压缩功能
enabled: true
# 压缩支持的MIME类型
mime-types: text/xml, application/xml, application/json
# # 数据压缩下限 1024表示传输数据大于1024 才会进行数据压缩(最小压缩值标准)
min-request-size: 1024
response:
enabled: true

替换默认通信组件

OpenFeign 默认使用 Java 自带的 URLConnection 对象创建 HTTP 请求,但接入生产时,如果能将底层通信组件更换为 Apache HttpClient、OKHttp 这样的专用通信组件,基于这些组件自带的连接池,可以更好地对 HTTP 连接对象进行重用与管理。作为 OpenFeign 目前默认支持 Apache HttpClient 与 OKHttp 两款产品。

第一步、引入 feign-okhttp 依赖包。

1
2
3
4
5
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.0</version>
</dependency>

第二步、配置OkHttpClient 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Spring IOC容器初始化时构建okHttpClient对象
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//读取超时时间
.readTimeout(10, TimeUnit.SECONDS)
//连接超时时间
.connectTimeout(10, TimeUnit.SECONDS)
//写超时时间
.writeTimeout(10, TimeUnit.SECONDS)
//设置连接池
.connectionPool(new ConnectionPool())
.build();
}

第三步、在 application.yml 中启用 OkHttp。

1
2
3
feign:
okhttp:
enabled: true