Feign
是一个声明式WebService
客户端。使用Feign
能让编写Web Service
客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign
也支持可拔插式的编码器和解码器。Spring Cloud
对Feign
进行了封装,使其支持了Spring MVC
标准注解和HttpMessageConverters
。Feign
可以与Eureka
和Ribbon
组合使用以支持负载均衡
也就是说,Feign
是一个声明式的Web
服务客户端,让编写Web
服务客户端变得非常容易,只需创建一个接口并在接口上申明注解
Feign
旨在使编写Java Http
客户端变得更容易。前面在使用
Ribbon+RestTemplate
时,利用RestTemplate
对http
请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,==往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。==所以,Feign
在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign
的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao
接口上面标注Mapper
注解,现在是一个微服务接口上面标注一个Feign
注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon
时,自动封装服务调用客户端的开发量。
Feign
集成了Ribbon
利用
Ribbon
维护了Payment
的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon
不同的是,通过Feign
只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
我们之前调用远程服务的时候,每个客户端都需要写一个
RestTemplate
,
但是这样还是不够精简,现在我们需要对其分层解耦,要面向接口编程,我们服务端所具有的功能,在客户端也都有与之对应的一个调用入口与之对应,那么我们何不直接在客户端中定义一个入口直接与服务端的接口进行预先绑定,在使用的时候,只需要调用自身的接口来和服务端接口一一配对调用的目的?这也就是微服务的面向接口编程,也是
Feign
的职责
Feign | OpenFeign |
---|---|
Feign 是Spring Cloud 组件中的一个轻量级RESTful 的HTTP 服务客户端Feign 内置了Ribbon ,用来做客户端负载均衡,去调用服务注册中心的服务。Feign 的使用方式是:使用Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 |
OpenFeign 是Spring Cloud 在Feign 的基础上支持了SpringMVC 的注解,如@RequesMapping 等等。OpenFeign 的@Feignclient 可以解析SpringMVC 的@RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudDemoartifactId>
<groupId>com.phz.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>OpenFeignConsumerOrder8008artifactId>
<dependencies>
<dependency>
<groupId>com.phz.springcloudgroupId>
<artifactId>CloudAPIartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8008
#这里只把feign做客户端用,不注册进eureka
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: false
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
/**
* @author PengHuAnZhi
* @createTime 2021/2/10 10:01
* @projectName SpringCloudDemo
* @className OrderMain8008.java
* @description TODO
*/
@SpringBootApplication
@EnableFeignClients
public class OrderMain8008 {
public static void main(String[] args) {
SpringApplication.run(OrderMain8008.class, args);
}
}
只需要保证与指向的服务名称下的方法一致即可
/**
* @author PengHuAnZhi
* @createTime 2021/2/10 10:04
* @projectName SpringCloudDemo
* @className PaymentFeignService.java
* @description TODO
*/
@Component
@FeignClient(value = "eureka-payment-service")
public interface PaymentFeignService {
@PostMapping(value = "/payment/insert")
CommonResult<Integer> insert(@RequestBody Payment payment);
@GetMapping(value = "/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
/**
* @author PengHuAnZhi
* @createTime 2021/2/10 10:10
* @projectName SpringCloudDemo
* @className OrderFeignController.java
* @description TODO
*/
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
可以看到
Feigh
自带负载均衡功能,因为它也集成了Ribbon
OpenFeign
默认等待一秒钟,若超时,则会报错,在测试的时候可以在服务端写一个延时程序模拟一个长流程调用的复杂业务,只要超过一秒即可。
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e){
e.printStackTrace();
} finally {
return serverPort;
}
}
客户端绑定
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
客户端调用
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
//openfeign-ribbon 客户端默认等待1S
return paymentFeignService.paymentFeignTimeout();
}
可以看到超时报错的,
OpenFeign
本身的负载均衡控制是由Ribbon
实现的,它先天集成了Ribbon
,所以就应该在配置文件中配置Ribbon
的超时控制
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的实际
ConnectTimeout: 5000
再次测试,产品经理交代的
10W
优化工作处理好了
Feign
提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign
中Http
请求的细节,说白了就是对Feign
接口调用情况进行监控和输出
URL
、响应状态码及执行时间;BASIC
中定义的信息之外,还有请求和响应的头信息;HEADERS
中定义的信息之外,还有请求和响应的正文及元数据。/**
* @author PengHuAnZhi
* @createTime 2021/2/10 10:58
* @projectName SpringCloudDemo
* @className FeignConfig.java
* @description TODO
*/
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
logging:
#feign日志以什么级别监控那个接口
level:
com.phz.service.PaymentFeignService:
debug