Feign是一款Netflix开源的轻量级的Restful Http Client,实现了负载均衡和Rest调用的开源框架,封装了Ribbon(Spring Cloud 2020版本后替代为LoadBalancer)和RestTemplate,实现了面向接口编程,进一步降低了项目耦合度。
官方定义:
Feign makes writing java http clients easier.
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了负载均衡,使代码变得更加简洁,减少客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要创建一个接口,然后添加注解即可。
Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。
Feign是用来做客户端负载均衡和服务调用的。
Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign官方文档。
使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public static class Issue {
String title;
String body;
List assignees;
int milestone;
List labels;
}
public class MyApp {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
Feign本身并不支持Spring MVC注解,为了方便使用,Spring Cloud孵化了OpenFeign。OpenFeign的@FeignClinent支持了Spring MVC的注解,可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,同时实现负载均衡调用逻辑。
官网:https://github.com/spring-cloud/spring-cloud-openfeign
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page getStores(Pageable pageable);
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
启动时,程序会扫描@EnableFeignClient知道的basepackage包下的所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。
RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
RequestTemplate声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
最后client封装成LoadBaLanceClient,结合ribbon或loadbalancer负载均衡地发起调用。
提供默认的encode和decode方法和异常fallback回调函数。
1. 添加依赖
由于openfeign依赖Spring Cloud,请参考Spring Cloud Project page创建并配置Spring Cloud项目。
org.springframework.cloud
spring-cloud-starter-openfeign
2. RemoteLogService服务
@FeignClient(name = ServiceNameConstants.UPMS_SERVICE, fallbackFactory = RemoteLogServiceFallbackFactory.class)
public interface RemoteLogService {
/**
* 保存日志
* @param sysLog 日志实体
* @return success、false
*/
@PostMapping("/sys/log")
R saveLog(@RequestBody SysLog sysLog);
}
3. RemoteLogServiceFallbackFactory降级实现
@Component
public class RemoteLogServiceFallbackFactory implements FallbackFactory {
@Override
public RemoteLogService create(Throwable cause) {
RemoteLogServiceFallbackImpl remoteLogServiceFallback = new RemoteLogServiceFallbackImpl();
remoteLogServiceFallback.setCause(cause);
return remoteLogServiceFallback;
}
}
4. 消费端调用(异步日志记录逻辑)
@Slf4j
@RequiredArgsConstructor
public class SysLogListener {
private final RemoteLogService remoteLogService;
@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
SysLog sysLog = (SysLog) event.getSource();
log.debug("打印日志:" + sysLog.getTitle());
remoteLogService.saveLog(sysLog);
}
}
5. 启动类添加@EnableFeignClients注解,显示声明默认扫描范围,basePackages={“xxx.xxx”}
6. 启动后调用接口验证
参数 |
默认值 |
说明 |
name/value |
空字符串 |
调用服务名称,两个作用一致,别名 |
contextId |
空字符串 |
服务id |
qualifiers |
{} |
等同@Qualifier注解,显示指定Bean注入 |
url |
空字符串 |
全路径地址或hostname,http或https可选 |
configuration |
{} |
自定义当前feign client的一些配置,参考FeignClientsConfiguration |
fallback |
void.class |
熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。 |
fallbackFactory |
void.calss |
熔断机制,类似fallback,实现异常回调 |
path |
空字符串 |
自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping |
primary |
true |
等同@Primary注解,标记存在多个实现Bean时(实现fallback时)为默认实例 |
decode404 |
false |
配置响应状态码为404时是否应该抛出FeignExceptions,如果值为true,则指定decode解码,否则抛出异常 |
Spring Cloud 2020版本之前,Feign默认集成Ribbon,Nacos也很好的兼容了Fegin,默认实现了负载均衡的效果。
Spring Cloud 2020版本之后,移除了对Netfix的Ribbon,Hystrix和Zuul的支持,替代方案如下:
Netflix产品 |
推荐替代产品 |
Hystrix |
Resilience4j/Sentinel(个人推荐) |
Ribbon |
Spring Cloud Loadbalancer |
Zuul 1 |
Spring Cloud Gateway |
注意:
如果采用Nacos做注册中心, 由于spring-cloud-starter-alibaba-nacos-discovery默认引入了Ribbon依赖(2.2.1.RELEASE版本),需显示移除:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
gzip是一种数据格式,采用deflate算法压缩数据。gzip大约可以帮我们减少70%以上的文件大小。
全局配置
server:
compression:
# 是否开启压缩
enabled: true
# 配置支持压缩的 MIME TYPE
mime-types: text/html,text/xml,text/plain,application/xml,application/json
局部配置
feign:
compression:
request:
# 开启请求压缩
enabled: true
# 配置压缩支持的 MIME TYPE
mime-types: text/xml,application/xml,application/json
# 配置压缩数据大小的下限
min-request-size: 2048
response:
# 开启响应压缩
enabled: true
提示
开启压缩可以有效节约网络资源,但是会增加CPU压力,建议把最小压缩的文档大小适度调大一点。
两台服务器建立HTTP连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。
Feign的HTTP客户端支持3种框架:HttpURLConnection、HttpClient、OkHttp。
默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient、okhttp作为底层的通信框架。
例如将Feign的HTTP客户端工具修改为HttpClient,OKHttp修改类似。
1. 添加依赖
io.github.openfeign
feign-httpclient
2. 全局配置
feign:
httpclient:
# 开启httpclient
enabled: true
3. 测试验证
@FeignClient支持通过FeignClientsConfiguration和configuration properties进行配置。
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}
Spring Cloud OpenFeign提供了下列默认bean:
Decoder feignDecoder: ResponseEntityDecoder (which wraps a SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
MicrometerCapability micrometerCapability: If feign-micrometer is on the classpath and MeterRegistry is available
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: FeignCircuitBreaker.Builder
Client feignClient: If Spring Cloud LoadBalancer is on the classpath, FeignBlockingLoadBalancerClient is used. If none of them is on the classpath, the default feign client is used.
详情请参考FeignClientsConfiguration。
FooConfiguration不需要使用@Configuration注释或者确保不会被@ComponentScan扫描,否则它将成为feign.Decoder,feign.Encoder,feign.Contract等的默认来源。
application.yml
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
metrics.enabled: false
常用的配置参数为connectTimeout和readTimeout,表示请求的连接超时时间和读取超时时间。
在微服务应用中,通过feign的方式实现http的调用,可以通过实现feign.RequestInterceptor接口在feign执行后进行拦截,对请求头等信息进行修改。
例如项目中利用feign拦截器将本服务的authentication传递给下游服务
/**
* feign 请求拦截器
*
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor{
@Override
public void apply(RequestTemplate requestTemplate)
{
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest))
{
Map headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication))
{
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
}
}
}
}
Spring Cloud 2020版本之前,Feign默认集成Hystrix,但由于Hystrix已经进入了维护状态,所以Spring Cloud 2020版本之后,移除了对Netfix的Hystrix的支持,官方推荐替代方案为Resilience4j,个人推荐阿里开源的Sentinel,因为Sentinel完美兼容Feign(后续文章专门介绍Sentinel)。
使用比较简单。
1. 开启配置
feign:
sentinel:
enabled: true
2. FeignClient接口服务加入fallbackFactory
@FeignClient(fallbackFactory = RemoteLogFallbackFactory.class)
3. 添加接口实现异常类
@Component
public class RemoteLogServiceFallbackFactory implements FallbackFactory {
@Override
public RemoteLogService create(Throwable cause) {
RemoteLogServiceFallbackImpl remoteLogServiceFallback = new RemoteLogServiceFallbackImpl();
remoteLogServiceFallback.setCause(cause);
return remoteLogServiceFallback;
}
}
@Slf4j
public class RemoteLogServiceFallbackImpl implements RemoteLogService {
@Setter
private Throwable cause;
@Override
public R saveLog(SysLog sysLog) {
log.error("feign 插入日志失败", cause);
return R.failed("熔断");
}
}
微服务实战SpringCloud之Feign简介及使用