思维导图

img

文章已收录Github精选,欢迎Starhttps://github.com/yehongzhi/learningSummary

前言

目前在SpringCloud技术栈中,调用服务用得最多的就是OpenFeign,所以这篇文章讲一下OpenFeign,希望对大家有所帮助。

一、构建工程

使用Nacos作为注册中心,不会搭建Nacos的话,可以参考上一篇注册中心的文章。
首先父工程parent引入依赖。

  1. <dependencyManagement>
  2. <dependencies>
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-dependencies</artifactId>
  6. <version>Finchley.SR1</version>
  7. <type>pom</type>
  8. <scope>import</scope>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  13. <version>0.2.2.RELEASE</version>
  14. <type>pom</type>
  15. <scope>import</scope>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-openfeign</artifactId>
  20. <version>2.0.1.RELEASE</version>
  21. <type>pom</type>
  22. <scope>import</scope>
  23. </dependency>
  24. </dependencies>
  25. </dependencyManagement>
  26. <dependencies>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-web</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter</artifactId>
  34. </dependency>
  35. <dependency><!-- SpringCloud nacos服务发现的依赖 -->
  36. <groupId>org.springframework.cloud</groupId>
  37. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  38. </dependency>
  39. <dependency>
  40. <groupId>com.alibaba.nacos</groupId>
  41. <artifactId>nacos-client</artifactId>
  42. <version>1.2.0</version>
  43. </dependency>
  44. </dependencies>

搭建提供者provider工程和消费者consumer工程。
provider工程继承父工程的pom文件,编写启动类如下:

  1. @SpringBootApplication
  2. @EnableDiscoveryClient//注册中心
  3. public class ProviderApplication {
  4. public static void main(String[] args) throws Exception {
  5. SpringApplication.run(ProviderApplication.class, args);
  6. }
  7. }

provider工程的配置文件如下:

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: provider
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: 127.0.0.1:8848
  10. service: ${spring.application.name}

提供接口,Controller如下:

  1. @RestController
  2. public class ProviderController {
  3. @RequestMapping("/provider/list")
  4. public List<String> list() {
  5. List<String> list = new ArrayList<>();
  6. list.add("java技术爱好者");
  7. list.add("SpringCloud");
  8. list.add("没有人比我更懂了");
  9. return list;
  10. }
  11. }

消费者consumer工程也继承parent的pom文件,加上Feign依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. <!-- 版本在parent的pom文件中指定了 -->
  6. </dependency>
  7. </dependencies>

编写启动类,如下:

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. //开启feign接口扫描,指定扫描的包
  4. @EnableFeignClients(basePackages = {"com.yehongzhi.springcloud"})
  5. public class ConsumerApplication {
  6. public static void main(String[] args) throws Exception {
  7. SpringApplication.run(ConsumerApplication.class, args);
  8. }
  9. }

环境搭建完成后,接下来讲两种实现使用方式。

二、声明式

这种很简单,消费者consumer工程增加一个ProviderClient接口。

  1. @FeignClient(name = "provider")
  2. //会扫描指定包下,标记FeignClient注解的接口
  3. //会根据服务名,从注册中心找到对应的IP地址
  4. public interface ProviderClient {
  5. //这里跟提供者接口的URL一致
  6. @RequestMapping("/provider/list")
  7. String list();
  8. }

然后再用消费者工程的ConsumerController接口来测试。

  1. @RestController
  2. public class ConsumerController {
  3. //引入Feign客户端
  4. @Resource
  5. private ProviderClient providerClient;
  6. @RequestMapping("/consumer/callProvider")
  7. public String callProvider() {
  8. //使用Feign客户端调用其他服务的接口
  9. return providerClient.list();
  10. }
  11. }

最后我们启动提供者工程,消费者工程,注册中心,测试。
img
然后调用消费者的ConsumerController接口。
img

三、继承式

细心的同学可能发现,其实声明式会写多一次提供者接口的定义,也就是有重复的代码,既然有重复的定义,那我们就可以抽取出来,所以就有了继承式。
第一步,创建一个普通的Maven项目api工程,把接口定义在api中。
img
第二步,服务提供者工程的ProviderController实现Provider接口。

  1. @RestController
  2. public class ProviderController implements ProviderApi {
  3. public String list() {
  4. List<String> list = new ArrayList<>();
  5. list.add("java技术爱好者");
  6. list.add("SpringCloud");
  7. list.add("没有人比我更懂了");
  8. return list.toString();
  9. }
  10. }

第三步,消费者工程的ProviderClient无需定义,只需要继承ProviderApi,然后加上@FeignClient即可。

  1. @FeignClient(name = "provider")
  2. public interface ProviderClient extends ProviderApi {
  3. }

其他不用变了,最后启动服务提供者,消费者,注册中心测试一下。
img
测试成功!上面继承式的好处就在于,只需要在api工程定义一次接口,服务提供者去实现具体的逻辑,消费者则继承接口贴个注解即可,非常方便快捷。
缺点就在于如果有人动了api的接口,则会导致很多服务消费者、提供者出现报错,耦合性比较强。api工程相当于一个公共的工程,消费者和服务者都会依赖此工程,所以一般要求不能随便删api上面的接口。

四、Feign的相关配置

下面讲一下Feign的一些常用的相关配置。

4.1 请求超时设置

Feign底层其实还是使用Ribbon,默认是1秒。所以超过1秒就报错。
接下来试验一下。我在服务提供者的接口加上一段休眠1.5秒的代码,然后用消费者去消费。

  1. @RestController
  2. public class ProviderController implements ProviderApi {
  3. public String list() {
  4. List<String> list = new ArrayList<>();
  5. list.add("java技术爱好者");
  6. list.add("SpringCloud");
  7. list.add("没有人比我更懂了");
  8. try {
  9. //休眠1.5秒
  10. Thread.sleep(1500);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. return list.toString();
  15. }
  16. }

消费者调用后,由于超过1秒,可以看到控制台报错。
img
如果想调整超时时间,可以在消费者这边,加上配置:

  1. ribbon:
  2. ReadTimeout: 5000 #请求时间5秒
  3. ConnectTimeout: 5000 #连接时间5秒

为了显示出效果,我们在消费者的代码里加上耗时计算:

  1. @RestController
  2. public class ConsumerController {
  3. @Resource
  4. private ProviderClient providerClient;
  5. @RequestMapping("/consumer/callProvider")
  6. public String callProvider() throws Exception {
  7. long star = System.currentTimeMillis();
  8. String list = providerClient.list();
  9. long end = System.currentTimeMillis();
  10. return "响应结果:" + list + ",耗时:" + (end - star) / 1000 + "秒";
  11. }
  12. }

最后启动测试,可以看到,超过1秒也能请求成功。
img

4.2 日志打印功能

首先需要配置Feign的打印日志的级别。

  1. @Configuration
  2. public class FeignConfig {
  3. /**
  4. * NONE:默认的,不显示任何日志
  5. * BASIC:仅记录请求方法、URL、响应状态码及执行时间
  6. * HEADERS:出了BASIC中定义的信息之外,还有请求和响应的头信息
  7. * FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元素
  8. */
  9. @Bean
  10. public Logger.Level feginLoggerLevel() {
  11. return Logger.Level.FULL;
  12. }
  13. }

第二步,需要设置打印的Feign接口。Feign为每个客户端创建一个logger。默认情况下,logger的名称是Feigh接口的完整类名。需要注意的是,Feign的日志打印只会对DEBUG级别做出响应

  1. #与server同级
  2. logging:
  3. level:
  4. com.yehongzhi.springcloud.consumer.feign.ProviderClient: debug

设置完成后,控制台可以看到详细的请求信息。
img

4.3 Feign实现熔断

openFeign实际上是已经引入了hystrix的相关jar包,所以可以直接使用,设置超时时间,超时后调用FallBack方法,实现熔断机制。
首先在消费者工程添加Maven依赖。

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

第二步,在配置中开启熔断机制,添加超时时间。

  1. #默认是不支持的,所以这里要开启,设置为true
  2. feign:
  3. hystrix:
  4. enabled: true
  5. hystrix:
  6. command:
  7. default:
  8. execution:
  9. isolation:
  10. thread:
  11. timeoutInMilliseconds: 3000

第三步,编写FallBack类。

  1. //ProviderClient是贴了@FeignClient注解的接口
  2. @Component
  3. public class ProviderClientFallBack implements ProviderClient {
  4. @Override
  5. public String list() {
  6. return Arrays.asList("调用fallBack接口", "返回未知结果").toString();
  7. }
  8. }

第四步,在对应的Feign接口添加fallback属性。

  1. //fallback属性,填写刚刚编写的FallBack回调类
  2. @Component
  3. @FeignClient(name = "provider", fallback = ProviderClientFallBack.class)
  4. public interface ProviderClient extends ProviderApi {
  5. }

最后可以测试一下,超过设置的3秒,则会熔断,调用FallBack方法返回。
img

4.4 设置负载均衡

前面说过OpenFeign底层是使用Ribbon,Ribbon是负责做负载均衡的组件。所以是可以通过配置设置负载均衡的策略。
默认的是轮询策略。如果要换成其他策略,比如随机,怎么换呢。
很简单,改一下配置即可:

  1. #服务名称
  2. provider:
  3. ribbon:
  4. NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  5. #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
  6. #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
  7. #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
  8. #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
  9. #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略

总结

OpenFeign把RestTemplete,Ribbon,Hystrix糅合在了一起,在使用时就可以更加方便,优雅地完成整个服务的暴露,调用等。避免做一些重复的复制粘贴接口URL,或者重复定义接口等。还是非常值得去学习的。
以前我在的公司搭建的SpringCloud微服务就没有使用Feign,架构师自己写了一个AOP代理类进行服务调用,超时时间5秒写死在代码里,当时有个微服务接口要上传文件,总是超时,又改不了超时时间,一超时就调熔断方法返回服务请求超时,导致非常痛苦。
如果当时使用Feign,插拔式,可配置的方式,也许就没那么麻烦了。