目录
一、简介
二、OpenFeign使用详解
三、OpenFeign超时控制
四、OpenFeign日志增强
五、总结
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign旨在是编写Java Http客户端变得更加容易。前面在使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个借口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon, 利用Ribbon维护了Payment的服务列表信息,并且通过轮训实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
【a】pom.xml maven依赖加入:spring-cloud-starter-openfeign
springcloud2020
com.wsh.springcloud
1.0-SNAPSHOT
4.0.0
springcloud-consumer-feign-order80
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.wsh.springcloud
springcloud-api-commons
${project.version}
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
【b】application.yml配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/
【c】启动类加上@EnableFeignClients注解开启Feign功能
package com.wsh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
//开启Feign远程服务调用功能
@EnableFeignClients
public class FeignOrderServiceApplication80 {
public static void main(String[] args) {
SpringApplication.run(FeignOrderServiceApplication80.class, args);
}
}
【d】声明FeignClient
package com.wsh.springcloud.feign;
import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @Description Feign接口
* @Date 2020/8/15 21:02
* @Author weishihuai
* 说明:
* Feign是一个interface接口
* Feign的实现的过程大致如下:
* a. 首先通过@EnableFeignClients注解开启FeignClient
* b. 根据Feign的规则实现接口,并加@FeignClient注解
* c. 程序启动后,会进行包扫描,扫描所有的@ FeignClient的注解的类,并将这些信息注入到ioc容器中。
* d. 当接口的方法被调用,通过jdk的代理,来生成具体的RequestTemplate
* e. RequestTemplate在生成Request
* f. Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
* g. 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡
*/
@Component
//@FeignClient注解value属性指定Payment服务注册到Eureka上的ApplicationName
//@FeignClient注解fallback属性指定失败回调
@FeignClient(value = "SPRINGCLOUD-PAYMENT-SERVICE")
public interface PaymentServiceFeignClient {
@GetMapping(value = "/payment/get/{id}")
JsonResult getPaymentById(@PathVariable("id") Long id);
}
【e】Controller增加测试方法
package com.wsh.springcloud.controller;
import com.wsh.springcloud.common.JsonResult;
import com.wsh.springcloud.entity.Payment;
import com.wsh.springcloud.feign.PaymentServiceFeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @Description Feign控制层接口
* @Date 2020/8/15 21:02
* @Author weishihuai
* 说明:
*/
@RestController
public class OrderFeignController {
@Resource
private PaymentServiceFeignClient paymentServiceFeignClient;
@GetMapping(value = "/openfeign/consumer/payment/get/{id}")
public JsonResult getPaymentById(@PathVariable("id") Long id) {
return paymentServiceFeignClient.getPaymentById(id);
}
}
【f】测试
启动Eureka集群,服务提供者PaymentService集群以及Feign客户端,浏览器访问多次:http://localhost/openfeign/consumer/payment/get/3
可见,成功实现了远程服务调用,并且可以看到Feign默认集成了Ribbon负载均衡,交替调用8001或者8002服务。
默认Feign客户端只等待一秒钟,但是服务端处理需要超过一秒钟,导致Feign客户单不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。下面先模拟一下超时报错:
【a】在服务提供者Controller添加如下方法:
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
// 业务逻辑处理正确,但是需要耗费3秒钟
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
【b】PaymentServiceFeignClient中增加一个方法
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeout();
【c】Feign客户端Controller中加入如下方法
@GetMapping(value = "/openfeign/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
// OpenFeign客户端一般默认等待1秒钟
return paymentServiceFeignClient.paymentFeignTimeout();
}
【d】测试
浏览器访问http://localhost/openfeign/consumer/payment/feign/timeout,发现报如下图错误:
这就是Feign默认只等待一秒钟,但是我们服务提供者业务处理需要三秒钟,这样肯定就会超时。
【d】解决方法
在application.yml配置文件中配置ribbon的超时时间控制:
#设置feign客户端超时时间(OpenFeign默认支持ribbon) OpenFeign客户端一般默认等待1秒钟
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
上述配置表示Feign愿意等待五秒钟,业务处理需要三秒钟,那么这样就不会出现Read Timeout错误。重启Feign客户端,重新访问 http://localhost/openfeign/consumer/payment/feign/timeout,发现接口成功返回数据,如下图所示:
有时候,在项目中可能会频繁使用Feign跨服务调用,而我们在开发的时候可能需要详细了解具体的传参、请求头信息等,这个时候我们可以对Feign的日志进行增强。
Feign的日志级别主要有四种:
详细的配置过程主要有两步:
【a】Feign日志增强配置
package com.wsh.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description Feign日志增强配置
* @Date 2020/8/16 9:05
* @Author weishihuai
* 说明:
* Feign的日志级别主要有四种:
* NONE:默认的,不显示任何日志;
* BASIC:仅记录请求方法、URL、响应状态码以及执行时间;
* HEADERS:处理BASIC中定义的信息外,还有请求和响应头信息;
* FULL:处理HEADERS中定义的信息之外,还有请求和响应的正文以及元数据信息;
*/
@Configuration
public class FeignLogConfig {
@Bean
public Logger.Level logLevel() {
return Logger.Level.FULL;
}
}
【b】application.yml中声明Feign接口的日志级别
logging:
level:
#以debug日志级别监控PaymentServiceFeignClient这个接口
com.wsh.springcloud.feign.PaymentServiceFeignClient: debug
【c】重启Feign客户端,浏览器访问:http://localhost/openfeign/consumer/payment/get/3
观察后台日志,可见,整个Feign调用的所有请求头、请求参数等都详细输出到日志中,这对于我们排查问题有很大的帮助。
2020-08-16 09:15:08.144 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] <--- HTTP/1.1 200 (2506ms)
2020-08-16 09:15:08.145 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] connection: keep-alive
2020-08-16 09:15:08.145 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] content-type: application/json
2020-08-16 09:15:08.145 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] date: Sun, 16 Aug 2020 01:15:08 GMT
2020-08-16 09:15:08.145 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] keep-alive: timeout=60
2020-08-16 09:15:08.146 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] transfer-encoding: chunked
2020-08-16 09:15:08.146 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById]
2020-08-16 09:15:08.149 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] {"code":200,"msg":"查询成功,serverPort: 8002","data":{"pkid":3,"serial":"2020072503"}}
2020-08-16 09:15:08.150 DEBUG 10516 --- [p-nio-80-exec-4] c.w.s.feign.PaymentServiceFeignClient : [PaymentServiceFeignClient#getPaymentById] <--- END HTTP (91-byte body)
Feign的实现的过程大致如下:
以上就是Feign远程服务调用的详细使用讲解,相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。