一、简单介绍
-
工作原理
-
定义接口:开发人员通过定义一个Java接口,来描述要调用的RESTful API接口的请求方法、参数、请求头等信息。
-
生成代理类:在程序启动时,Fegin会根据定义的Java接口,动态生成一个代理类,并将其注入到Spring容器中。
-
发起请求:当应用程序调用代理类的方法时,Fegin会根据方法的注解信息,生成对应的HTTP请求,并用底层的HTTP客户端(比如OkHttp)发送给指定的服务提供者。
-
处理响应:当服务提供者返回响应结果时,Feign会将响应结果转换成Java对象,并返回给应用程序。
-
-
Feign与OpenFeign区别
-
注解支持
Feign仅支持JAX-RS(Java API for RESTful Web Services)注解,对Spring MVC注解不支持。OpenFeign支持Spring MVC注解,更贴近Spring开发体验;
-
编解码器
Feign仅支持QueryStringEncoder、FormEncoder和JsonEncoder三种编解码器。OpenFeign内置了SpringEncoder和StringDecoder,支持更丰富的对象与HTTP请求的编解码,如集合、 maps等;
-
contract
Feign仅支持接口方法签名与url的映射,请求细节无法定制。OpenFeign支持SpringMvcContract,可以定制请求方法、参数绑定等细节;
-
拦截器
Feign不支持请求与响应拦截器。OpenFeign支持RequestInterceptor和ResponseInterceptor,允许拦截并自定义Feign的请求与响应;
-
二、常用注解和参数
-
@FeignClient:核心注解,声明一个Feign客户端。以下是该注解常用的属性:
-
name/value:这两个的作用是一样的,指定的是调用服务的微服务名称,两个必须配置一个;
-
url:指定调用服务的全路径。如果同时指定name和url属性:则以url属性为准,忽略name属性;
-
configuration:指向FeignConfiguration配置类,该配置类如果加了@Component/@Service等注解会自动注入spring,作用于全局,可以同时指向多个,加载顺序从前往后,最后是全局的配置,但不代表相同配置后面的会覆盖前面的,需自行测试。
(举例:全局配置和引用的配置都实现接口feign.RequestInterceptor,统一加了相同的请求头,如果headerName是Content-Type,最后的配置会覆盖前面的,否则是第一个生效)
设置请求头源码:
-
fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口;
-
fallbackFactory:定义一个处理容错的工厂类,实现FallbackFactory,作用和fallback相同,但是灵活性更高,可以捕获到异常;
-
path: 定义当前FeignClient的统一前缀,当我们项目中配置了server.context-path,server.servlet-path时使用(当方法里传入URI时不生效);
-
-
@Headers、@HeaderMap、@RequestLine。feign的注解;
-
@RequestMapping、@GetMapping、@PostMapping、 @PathVariable、@RequestParam、@RequestBody、@RequestHeader。这些都是org.springframework.web里面的注解,用法一样;
三、基本用法
-
如何添加请求头
-
在@RequestMapping注解中添加headers属性,或者在方法参数前面添加@RequestHeader注解,可以是一个也可以是多个;
@RequestMapping(headers = {"headerName=headerValue"}) String test(@RequestHeader("headerName") String headerValue, @RequestHeader Map<String, String> headerMap);
-
FeignConfiguration实现接口RequestInterceptor
public class TestFeignConfiguration implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header("headerName", "headerValue"); } }
-
在方法或类上面添加@Headers注解,或者在参数前面添加@HeaderMap注解,注意使用方式。
@FeignClient(name = "feignCaller", url = "http://127.0.0.1:8080/", configuration = TestFeignConfiguration.class) @Headers({"headerName: headerValue"}) public interface FeignCaller { // 因为配置了Contract,所以不能再用spring的注解,需要用feign自带的 @RequestLine("GET /test4") @Headers({"headerName: headerValue"}) String test(@HeaderMap Map<String, String> headerMap); }
这两个注解是feign自带的,因为SpringCloud集成feign默认使用的是SpringMvcContract,所以直接使用会报错,如果想要使用需要配置自带的Contract,这时再用spring的注解就会启动报错;
public class TestFeignConfiguration { @Bean public Contract getContract(){ return new feign.Contract.Default(); } }
-
-
如何配置请求路径
@FeignClient(name = "feignCaller", path = "/feign", url = "http://127.0.0.1:8080/") public interface FeignCaller { @RequestMapping(value = "/test") String test(URI uri); }
-
参数里直接传入URI优先级最高,如果方法上面的注解@RequestMapping的value属性有值,则会拼接上value值。(如上方法调用的接口就是{URI}/test);
-
@FeignClient注解里面的url属性,在方法参数没有具体地址时生效。如果@FeignClient注解的path属性有值,则会拼接上path值,如果方法上面的注解@RequestMapping的value属性有值,则会再拼接上value值。(如上方法调用不传URI的情况下接口就是http://127.0.0.1:8080/feign/test);
-
如果@FeignClient没有url属性,则会使用name/value属性使用注册中心进行接口调用;
-
-
FeignConfiguration使用
-
全局配置,添加@Component注解,会对所有feignClient生效;
@Component @EnableFeignClients public class FeignConfiguration01 {}
-
非全局配置,只对@FeignClient注解中configuration属性指定该配置类的feignClient生效;
public class FeignConfiguration02 {}
@FeignClient(name = "feignCaller", url = "http://127.0.0.1:8080", configuration = FeignConfiguration02.class) public interface FeignCaller { @RequestMapping(value = "/test1") S tring test1(); }
-
-
请求拦截器
需要FeignConfiguration实现RequestInterceptor接口即可,上面添加请求头就是该方式;
-
性能优化
-
选择更高性能的HTTP客户端:如前所述,替换Feign默认的URLConnection,选择Apache HTTP Client、OkHttp等更高性能的HTTP客户端;
-
连接池优化:
-
合理设置连接池大小,不宜太大也不宜太小;
-
选择支持连接池复用的HTTP客户,如OkHttp;
-
Ribbon也有连接池设置,与Feign的HTTP客户端配合优化。
-
-
超时优化:
-
合理设置Feign的连接超时和读取超时。连接超时不宜太长,读取超时根据服务调用耗时设置;
-
Ribbon也有相应超时设置,与Feign协同优化。
-
-
服务线程池优化:
Feign使用JDK默认线程池,我们可以进行定制:
@Bean public ExecutorService feignExecutorService(){ // 设置核心线程数,最大线程数,队列大小,释放资源时的延迟时间 return new ThreadPoolExecutor(xx, yy, zz, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(zzz), new NamedThreadFactory("feign")); }
-
重试优:
-
合理设置Feign的重试次数和时间间隔;
-
区分对不同服务的重试策略,防止重试过度导致系统资源消耗过大。
-
-
GZIP压缩:
-
Feign支持对请求和响应进行GZIP压缩,以提高网络传输性能。可以在配置文件里开启。
四、配置文件
-
日志输出
Feign支持日志记录,我们可以设置日志级别以查看Feign的调用详情,设置为ALL,则会展示完整的请求、响应日志详情;
feign: client: config: default: loggerLevel: FULL
-
修改http客户端
Feign默认使用JDK原生的URLConnection发送HTTP请求,我们也可以选择更换HTTP客户端,如Apache HTTP Client、OkHttp等;
feign: client: config: default: okhttp: enabled: true # 开启OkHttp
-
设置最大连接数和超时时间
feign: httpclient: enabled: true max-connections: 1000 max-connections-per-route: 2000 client: config: default: readTimeout: 10000 connectTimeout: 1000
-
请求响应压缩
可以对请求和响应进行配置是否开启压缩,以及请求压缩类型和最小压缩大小等;
feign: compression: response: enabled: true request: enabled: true # 支持压缩的类型 mime-types: text/xml,application/xml,application/json # 压缩触发的最小请求大小 min-request-size: 2048
五、Feign高可用方案
对于一个微服务系统来说,服务调用是非常重要的一个环节,Feign作为一个重要的调用组件,其高可用性直接影响整个系统的高可用。这里给出一些提高Feign高可用的方案:
服务发现与注册
Feign常与Eureka、Dubbo等服务注册中心协同使用,这些服务注册中心本身也支持集群部署,可以提高Feign服务调用的高可用性;
Ribbon负载均衡
Feign内置的Ribbon组件,我们可以设置多个服务实例,并选择合适的负载均衡策略,避免单点故障;
Hystrix容错保护
Hystrix可以进行线程隔离、熔断等策略保护Feign,避免在高并发下服务被过载。熔断机制可以快速失败,避免操作阻塞;
Http客户端连接池
使用连接池,如Apache HTTP Client、OKHttp等,可以进行连接复用,避免每次调用都建立新的连接。并且这些客户端本身也支持高可用配置,如设置多个Url地址;
超时与重试机制
合理设置Feign的连接超时、读取超时时间,可以快速发现服务问题并快速失败,避免资源占用过长时间。配合重试机制,在一定次数后快速返回,防止长时间的不可用服务导致系统不可用;
熔断与限流
除Hystrix外,可以使用Resilience4J等开源组件进行更加全面和强大的熔断、限流、重试。限流可以防止突发高流量导致系统不可用;
服务跟踪
使用组件如Zipkin进行服务调用链路跟踪,一旦出现高延迟或不可用服务,可以快速定位问题所在;
降级策略
为Feign接口指定Fallback以及合适的降级策略,在服务不可用时提供备选方案,避免不可用服务导致依赖服务完全不可用;
日志记录与监控
合理配置Feign日志级别,并结合ELK等日志收集工具进行监控。一旦服务有异常状况,可以快速发现并定位问题。
综上,Feign高可用需要多方面的保障和运维,需要与服务注册中心、熔断限流组件、链路跟踪组件、监控日志组件等协同配合,共同提高Feign和依赖其的整个微服务系统的高可用性。
六、源码分析
理解Feign的源码,有助于我们更深入理解其工作原理,从而合理使用和定制Feign。这里简要分析Feign的源码:
-
Feign类:Feign类是Feign的入口,主要工作是
-
解析Feign注解,获取接口方法与url映射关系,请求类型等信息;
-
构建ReflectiveFeign类,封装接口方法与请求细节的映射;
-
构建Feign.Builder,用于创建Feign实例;
-
创建Logger用于记录Feign日志;
-
绑定Contract契约,默认是SpringMvcContract;
-
-
ReflectiveFeign类
-
维护Feign接口方法与请求模板(RequestTemplate)的映射;
-
调用接口方法时,查找请求模板,使用请求参数构造URL,发起HTTP请求;
-
将响应结果转换为接口方法 defined 返回类型,返回给调用方;
-
-
Contract接口与SpringMvcContract
-
Contract接口定义了诸如生成请求模板、构造参数值到模板变量等规则;
-
SpringMvcContract实现了Spring MVC注解方式,将方法、参数注解转化为请求模板变量与值;
-
-
Client接口与Client.Default实现
-
Client接口定义了发起HTTP请求的方法。Feign使用构建器模式,允许我们选择不同Client实现来发送请求;
-
Client.Default实现了使用JDK原生URLConnection发送HTTP请求。我们可以实现自定义Client,如使用OKHttp等;
-
-
Encoder和Decoder
-
Encoder负责对请求参数进行编码,默认使用SpringEncoder对参数进行JSON编码;
-
Decoder负责对响应结果进行解码,默认使用SpringDecoder对JSON响应进行解码;
-
-
Logger和LoggingInterceptor
-
Logger定义了记录Feign日志的规范,有4个级别:NONE、BASIC、HEADERS、FULL;
-
LoggingInterceptor拦截Feign请求与响应,将详细信息记录为Feign日志, logfile可指定日志记录位置;
-
-
Retryer接口
-
定义重试策略,Feign内置支持backoff、exponential backoff重试策略。我们也可以自定义Retryer实现。
-
这些是Feign的主要组成部分, Feign的高效与灵活正是因为这些组件采用接口设计,允许我们灵活选择与替换。理解这些组件的作用与关系,有助于我们使用Feign的源码进行定制化开发。
七、验证
-
FeignConfiguration相关验证(同时验证配置生效范围和加载顺序、修改默认Contract对启动的影响、实现RequestInterceptor接口方式来添加请求头的顺序问题)
-
case1:添加一个全局配置,添加一个feignClient不做任何引用,期望配置生效;
-
case2:添加一个全局配置,内容为空,再添加一个非全局配置,添加一个feignClient不做任何引用,期望配置不生效;
-
case3:分别添加一个全局和非全局的配置,添加一个feignClient不做任何引用,期望全局配置生效,非全局配置不生效;
-
case4:分别添加一个全局和非全局的配置,添加一个feignClient引用非全局配置,期望全局配置生效,非全局配置也生效;
-