OpenFeign声明式服务调用

SpringCloud OpenFeign是一种声明式服务调用组件,它内置了客户端负载均衡,使用SpringCloud OpenFeign能够很容易的实现基于HTTP+JSON方式的服务间调用。

SpringCloud Feign、OpenFeign、和SpringCloud OpenFeign的区别

这里关于SpringCloud OpenFeign的历史需要额外说明。

在2018年之前,Feign是一个由Netflix公司维护的开源项目,除了Feign以外Netflix公司还维护了一系列好用的分布式微服务组件,如Eureka、Zuul、Hystrix等,SpringCloud项目集成了这些组件并让其几乎成为了新一代分布式微服务的事实标准。然而在2018年11月Netflix公司突然决定不再维护Eureka、Feign、Zuul、Hystrix等开源项目了,Feign由于设计精良且广泛使用,因此转为由OpenFeign开源社区维护(而Zuul、Hystrix等则彻底被放弃),SpringCloud中的相关模块也改名为SpringCloud OpenFeign。此外我们要知道SpringCloud OpenFeign是OpenFeign的一层封装,在非SpringCloud环境(如服务网格)中我们可以直接使用OpenFeign库实现HTTP客户端,而非SpringCloud OpenFeign。

OpenFeign项目的Github地址:https://github.com/OpenFeign/feign

同类方案对比

微服务架构中,服务间调用方案有很多不错的可选项,比如Dubbo(基于Hessian)、gRPC、Thrift,甚至JavaRMI等。SpringCloud中则比较推荐使用SpringCloud OpenFeign。

SpringCloud OpenFeign使用基于SpringMVC注解的声明式服务调用,用起来非常简单,而且由于JSON的通用性,如果后续考虑结合使用其他语言开发微服务也十分容易,此外JSON也十分便于观察,方便我们对整个通信链路的日志收集和调试。唯一的缺点可能就是JSON序列化和反序列化性能稍微差一些,以及文本报文较大,因此系统吞吐量可能不如二进制协议,不过考虑大多数应用的瓶颈都在数据库,CPU性能也很容易通过水平扩容解决,因此问题不大。

引入Maven依赖

我们直接在SpringBoot工程中引入SpringCloud OpenFeign的起步依赖即可。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

工程划分最佳实践

微服务工程中,对于一个工程划分的最佳实践是将其拆分为svcapi两个模块(当然,实际名字可以与此不同),svc包含了服务的完整代码,api包含了DTO数据模型和OpenFeign接口。例如下面我们有orderproduct两个微服务,假如order工程要调用product工程,那么相关的DTO和OpenFeign接口应该定义在product-api模块,而提供服务的Controller则定义在product-svc模块中。这样其它工程引入OpenFeign客户端接口时,引入product-api就行了,而不必引入完整的product-svc模块,这样能够最大程度保证引入的模块不会干扰其它微服务工程。

order-api
order-svc
product-api
product-svc

编写OpenFeign接口

SpringCloud OpenFeign是声明式的,我们需要编写OpenFeign接口,下面是一个例子,它用GET方法调用product服务的/api/v1/product/findProductById接口。

package com.gacfox.demo.product.api;

import com.gacfox.demo.product.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "product", path = "/api/v1/product")
public interface ProductClient {
    @GetMapping("/findProductById")
    public Product findProductById(@RequestParam(name = "id") Long id);
}

我们这里创建了一个Java接口,在其上标注了@FeignClient注解,value属性指定了调用的服务名,path指定了该接口对应的基础路径。Spring容器启动时,SpringCloud OpenFeign框架会自动根据接口和标注的注解生成动态代理Bean。@GetMapping@RequestParam等其它注解都是SpringMVC注解,比较容易理解,其实在实际开发中,我们会发现这个接口的方法签名和服务提供端Controller中基本是一样的,我们复制一份即可。

注意:

  1. GET请求参数需要标注@RequestParam,否则无法正确解析。
  2. OpenFeign内部调用时默认会进行客户端负载均衡,一般无需手动干预。

使用OpenFeign客户端

在需要用到OpenFeign接口的工程启动类中,我们需要使用@EnableFeignClients标注启用OpenFeign,并在参数basePackages中传入FeignClient组件的扫描包路径。

package com.gacfox.demo.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.gacfox.demo.product.api"})
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

在需要使用OpenFeign接口的具体代码中,我们直接使用依赖注入引入客户端对象即可。

package com.gacfox.demo.order.controller;

import com.gacfox.demo.order.model.Order;
import com.gacfox.demo.product.api.ProductClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
    @Resource
    private ProductClient productClient;

    @GetMapping("/findOrderById")
    public Order findOrderById(@RequestParam(name = "orderId") String orderId) {
        // ... 具体业务逻辑
    }
}

OpenFeign相关配置选项

application.properties配置文件中,我们可以对OpenFeign的超时、日志级别等进行配置。

# 建立连接超时时间
feign.client.config.default.connect-timeout=2000
# 响应超时时间
feign.client.config.default.read-timeout=2000
# 日志级别
feign.client.config.default.logger-level=basic

注意,分布式系统中,服务调用的超时时间是一定要明确配置的,否则会产生雪崩效果引起系统整体不可用。

logger-level指定了OpenFeign底层HttpClient的日志级别,可选值如下:

  • none:无日志
  • basic:基本信息日志,包括记录请求方法、URL、响应状态码、调用时间
  • headers:记录基本信息,并额外包括请求和响应的HTTP头信息
  • full:记录完整日志

然而这里要注意,上面这些枚举值并不是日志框架的日志级别,OpenFeign的所有日志默认以DEBUG级别输出,因此如果我们的工程日志级别不是DEBUG,那么无论配置什么值日志都不会输出,因此我们可能还需要配置logging.level.root=debug。该配置建议仅在开发环境中启用,生产环境还是推荐我们用INFO级别手动输出一下请求、响应报文。

OpenFeign还有很多可选配置,具体可以参考文档。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap