一、Feign简介
Feign是Netflix开发的⼀个轻量级RESTful的HTTP服务客户端(⽤它来发起请求,远程调⽤的),是以Java接⼝注解的⽅式调⽤Http请求,⽽不⽤像Java中通过封装HTTP请求报⽂的⽅式直接调⽤,Feign被⼴泛应⽤在Spring Cloud 的解决⽅案中。 类似于Dubbo,服务消费者拿到服务提供者的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。
常规的RestTemplate请求存在拼接url restTmplate.getForObJect比较模版化,硬编码等不便之处。
- Feign可帮助我们更加便捷,优雅的调⽤HTTP API:不需要我们去拼接url然后呢调⽤ restTemplate的api,在SpringCloud中,使⽤Feign⾮常简单,创建⼀个接⼝(在消费者–服务调⽤⽅这⼀端),并在接⼝上添加⼀些注解,代码就完成了
- SpringCloud对Feign进⾏了增强,使Feign⽀持了SpringMVC注解(OpenFeign)
本质:封装了Http调⽤流程,更符合⾯向接⼝化的编程习惯,类似于Dubbo的服务调⽤ Dubbo的调⽤⽅式其实就是很好的⾯向接⼝编程
二、 Feign配置应⽤
Feign = RestTemplate+Ribbon+Hystrix
- 消费者引⼊Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 消费者启动类使⽤注解@EnableFeignClients添加Feign⽀持 @EnableFeignClients,注意:此时去掉Hystrix熔断的⽀持注解@EnableCircuitBreaker即可包括引⼊的依赖,因为Feign会⾃动引⼊
- 创建Feign接⼝
// name:调⽤的服务名称,和服务提供者yml⽂件中spring.application.name保持⼀致
@FeignClient(name="lagou-service-resume")
public interface ResumeFeignClient {
//调⽤的请求路径
@RequestMapping(value = "/resume/openstate/{userId}",method=
RequestMethod.GET)
public Integer findResumeOpenState(@PathVariable(value = "userId")
Long userId);
}
注意:
1、 @FeignClient注解的name属性⽤于指定要调⽤的服务提供者名称,和服务提供者yml⽂件中 spring.application.name保持⼀致
2、 接⼝中的接⼝⽅法,就好⽐是远程服务提供者Controller中的Hander⽅法(只不过如同本地调⽤了),那么在进⾏参数绑定的时,可以使⽤@PathVariable、@RequestParam、@RequestHeader等,这也是OpenFeign对SpringMVC注解的⽀持,但是需要注意value必须设置,否则会抛出异常
- 使⽤接⼝中⽅法完成远程调⽤
@Autowired
private ResumeFeignClient resumeFeignClient;
@Test
public void testFeignClient(){
Integer resumeOpenState =
resumeFeignClient.findResumeOpenState(1545132l);
System.out.println("=======>>>resumeOpenState:" + resumeOpenState);
}
三、Feign对负载均衡的⽀持
Feign 本身已经集成了Ribbon依赖和⾃动配置,因此我们不需要额外引⼊依赖,可以通过 ribbon.xx 来进 ⾏全局配置,也可以通过服务名.ribbon.xx 来对指定服务进⾏细节配置配置(参考之前,此处略) Feign默认的请求处理超时时⻓1s,有时候我们的业务确实执⾏的需要⼀定时间,那么这个时候,我们就需要调整请求处理超时时⻓,Feign⾃⼰有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
- Ribbon设置
#针对的被调⽤⽅微服务名称,不加就是全局⽣效
lagou-service-resume:
ribbon:
#请求连接超时时间
#ConnectTimeout: 2000
#请求处理超时时间
#ReadTimeout: 5000
#对所有操作都进⾏重试
OkToRetryOnAllOperations: true
####根据如上配置,当访问到故障请求的时候,它会再尝试访问⼀次当前实例(次数由
MaxAutoRetries配置),
####如果不⾏,就换⼀个实例进⾏访问,如果还不⾏,再换⼀次实例访问(更换次数由
MaxAutoRetriesNextServer配置),
####如果依然不⾏,返回失败信息。
MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第⼀次调⽤
MaxAutoRetriesNextServer: 0 #切换实例的重试次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载
策略调整
四、Feign对熔断器的⽀持
- 在Feign客户端⼯程配置⽂件(application.yml)中开启Feign对熔断器的⽀持
# 开启Feign的熔断功能
feign:
hystrix:
enabled: true
注意:
1、 开启Hystrix之后,Feign中的⽅法都会被进⾏⼀个管理了,⼀旦出现问题就进⼊对应的回退逻辑处理
2、 针对超时这⼀点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最 ⼩值来进⾏的,即处理时⻓超过最短的那个超时时间了就熔断进⼊回退降级逻辑
- ⾃定义FallBack处理类(需要实现FeignClient接⼝)
- 在@FeignClient注解中关联2)中⾃定义的处理类
@FeignClient(value = "lagou-service-resume",fallback =
ResumeFallback.class,path = "/resume") // 使⽤fallback的时候,类上的
@RequestMapping的url前缀限定,改成配置在@FeignClient的path属性中
//@RequestMapping("/resume")
public interface ResumeServiceFeignClient {
五、Feign对请求压缩和响应压缩的⽀持
Feign ⽀持对请求和响应进⾏GZIP压缩,以减少通信过程中的性能损耗。通过下⾯的参数 即可开启请求与响应的压缩功能:
六、Feign的⽇志级别配置
Feign是http请求客户端,类似于咱们的浏览器,它在请求和接收响应的时候,可以打印出⽐较详细的⼀些⽇志信息(响应头,状态码等等),默认情况下Feign的⽇志没有开启。
- 开启Feign⽇志功能及级别
// Feign的⽇志级别(Feign请求过程信息)
// NONE:默认的,不显示任何⽇志----性能最好
// BASIC:仅记录请求⽅法、URL、响应状态码以及执⾏时间----⽣产问题追踪
// HEADERS:在BASIC级别的基础上,记录请求和响应的header
// FULL:记录请求和响应的header、body和元数据----适⽤于开发及测试环境定位问题
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
- 配置log⽇志级别为debug
logging:
level:
# Feign⽇志只会对⽇志级别为debug的做出响应
com.lagou.edu.controller.service.ResumeServiceFeignClient: debug
七、Feign核⼼源码剖析
思考⼀个问题:只定义了接⼝,添加上@FeignClient,真的没有实现的话,能完成远程请求么?不能,考虑是做了代理了。
- 断点标注 @FeginClient 接口
- 从@EnableFeignClients 正向切⼊
- import FeignClientsRegistrar
- 进入 org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions,重写BeanDefinitions,完成Bean 注入
- 接下来,我们主要追踪下另外⼀⾏主要的代码registerFeignClients(metadata, registry);定义了扫描器,主要扫描@FeginClient
- 注册客户端,给每⼀个客户端⽣成代理对象
- 所以,下⼀步,关注FeignClientFactoryBean这个⼯⼚Bean的getObject⽅法,根据经验,这个⽅法会返回我们的代理对象
- getObject 里面完成了 核心方法,包括了负载均衡的封装,最后发起请求
- 最终请求的发起使⽤的是HttpURLConnection
简单总结下:
为标注@FeginClient的接口生成代理,封装成Bean,利用FactoryBean#getObject,去包装了Ribbon,最终发起http请求。
lagouedu 笔记总结