SpringCloud OpenFeign是一种声明式服务调用组件,它内置了客户端负载均衡,使用SpringCloud OpenFeign能够很容易的实现基于HTTP+JSON方式的服务间调用。
这里关于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性能也很容易通过水平扩容解决,因此问题不大。
我们直接在SpringBoot工程中引入SpringCloud OpenFeign的起步依赖即可。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
微服务工程中,对于一个工程划分的最佳实践是将其拆分为svc
和api
两个模块(当然,实际名字可以与此不同),svc
包含了服务的完整代码,api
包含了DTO数据模型和OpenFeign接口。例如下面我们有order
和product
两个微服务,假如order
工程要调用product
工程,那么相关的DTO和OpenFeign接口应该定义在product-api
模块,而提供服务的Controller则定义在product-svc
模块中。这样其它工程引入OpenFeign客户端接口时,引入product-api
就行了,而不必引入完整的product-svc
模块,这样能够最大程度保证引入的模块不会干扰其它微服务工程。
order-api
order-svc
product-api
product-svc
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中基本是一样的,我们复制一份即可。
注意:
@RequestParam
,否则无法正确解析。在需要用到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) {
// ... 具体业务逻辑
}
}
在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还有很多可选配置,具体可以参考文档。