Spring Cloud:Feign - 轻量级Restful HTTP客户端

什么是Feign

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 RetrofitJAXRS-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 vs OpenFeign

Feign

Feign是用来做客户端负载均衡和服务调用的。

Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign官方文档。

使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。


interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> 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<String> assignees;
  int milestone;
  List<String> 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<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

OpenFegin

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<Store> getStores();

        @RequestMapping(method = RequestMethod.GET, value = "/stores")
        Page<Store> getStores(Pageable pageable);

        @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
        Store update(@PathVariable("storeId") Long storeId, Store store);
}

Feign原理简述

  • 启动时,程序会扫描@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项目。

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

2. RemoteLogService服务

@FeignClient(name = ServiceNameConstants.UPMS_SERVICE, fallbackFactory = RemoteLogServiceFallbackFactory.class)
public interface RemoteLogService {
  
  /**
  * 保存日志
  * @param sysLog 日志实体
  * @return success、false
  */
  @PostMapping("/sys/log")
  R<Boolean> saveLog(@RequestBody SysLog sysLog);
}

3. RemoteLogServiceFallbackFactory降级实现

@Component
public class RemoteLogServiceFallbackFactory implements FallbackFactory<RemoteLogService> {
  		@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. 启动后调用接口验证

FeignClient注解参数说明

参数

默认值

说明

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版本),需显示移除:

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.cloud</groupId>             		
			<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
		</exclusion>
	</exclusions>
</dependency>

Gzip压缩

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连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。

Feign的HTTP客户端支持3种框架:HttpURLConnection、HttpClient、OkHttp。

默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient、okhttp作为底层的通信框架。

例如将Feign的HTTP客户端工具修改为HttpClient,OKHttp修改类似。

1. 添加依赖

<!-- feign httpclient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

2. 全局配置

feign:
  httpclient:
    # 开启httpclient
	enabled: true

3. 测试验证

请求超时

@FeignClient支持通过FeignClientsConfiguration和configuration properties进行配置。

基于FeignClientsConfiguration配置


@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等的默认来源。

基于configuration properties配置

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<String, String> 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<RemoteLogService> {
  
  @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<Boolean> saveLog(SysLog sysLog) {
    log.error("feign 插入日志失败", cause);
    return R.failed("熔断");
  }
}

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值