文章目录
- SpringCloud - OpenFeign服务调用
- 0. OpenFeign概述
- 1. OpenFeign服务调用
- 2. OpenFeign超时机制
- 3. OpenFeign日志打印功能
SpringCloud - OpenFeign服务调用
0. OpenFeign概述
在Spring Cloud 中服务之间通过restful方式调用有两种方式 - RestTemplate+Ribbon 和 Feign
回想上一个博客我们在使用Eureka 和 Ribbon的时候是怎么调用注册在Eureka Server上的微服务的地址呢?
可以看到是通过拼接的方式,当然了这个例子只有一个参数 id,看起来没有这麻烦。
如果请求url有多个参数呢?:比如http://localhost:8080/find?name=lisi&age=20
那我们用RestTemplate如何调用对方的微服务呢? 可以采用如下方式:
@GetMapping("consumer/find")
public User user(String name ,String age) {
Map<String, Object> map = new HashMap<String ,Object>() {
{
put("name",name);
put("age",age);
}
};
return this.restTemplate.getForObject(PAYMENT_URL+"/find?name={name}&age={age}", User.class, paraMap);
}
是不是已经很麻烦了?
Spring Cloud为我们整合了Fegin解决上述苦恼。
Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 http 请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。
Ribbon 是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求,步骤相当繁琐。
1. OpenFeign服务调用
① 创建一个cloud-consumer-feign-order80服务消费者模块,然后修改pom文件,引入openfeign坐标依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--自定义的cloud-api-commons公共服务模块jar包-->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
② 编写application.yml文件:
server:
port: 80
# 这里只把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/
③ 编写主启动类:
@SpringBootApplication
@EnableFeignClients //激活OpenFeign客户端
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
④ 编写Fegin要调用的其他服务的接口
写这个service业务层接口,主要是80消费者去调用它时,实际上调用的是CLOUD-PAYMENT-SERVICE
生产者8001和8002微服务的controller层的该方法(轮循)。可以看到这个接口的抽象方法和生产者8001和8002 controller层的方法相同,而且连@GetMapping注解的地址也一样,只是这个方法是抽象的。
package com.atguigu.springcloud.service;
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
⑤ 在controller层中调用配置的接口方法PaymentFeignService
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentByI(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
浏览器上访问/consumer/payment/get/id地址,因为添加了@FeignClient注解的接口,Feign会根据@FeignClient上的value值(CLOUD-PAYMENT-SERVICE
),找到具体的服务向它发送/payment/get/id
的请求,从而实现远程调用生产者8001和8002的controller中的/payment/get/id
。在使用感受上,好像是调用了cloud-consumer-feign-order80项目内部的一个业务一样,无需关心实现,也不用写RestTemplate了。
⑥ 测试:
启动Eureka集群7001和7002,服务提供者8001和8002,服务消费者OrderFeignMain80。浏览器访问http://localhost/consumer/payment/get/1 ,一切正常,可以查询到数据,并且不断在8001和8002之间切换(可以实现负载均衡)。
2. OpenFeign超时机制
OpenFeign默认等待时间是1秒,超过1秒,直接报错;因为OpenFeign的底层是ribbon进行负载均衡,所以它的超时时间是由ribbon控制。
① 为了测试超时控制,我们在服务提供者的模块,即8001和8002中,添加一个controller方法,方法体内Thread.sleep(3000);停留3秒钟返回。
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
② 为了让cloud-consumer-feign-order80能调用到这个接口,需要将其定义写在PaymentFeignService.java接口中:
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
}
③ 在OrderFeignController.java中加入映射地址,用于浏览器访问:
@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);
}
//调用PaymentFeignService.java接口的该方法,实际上去调用生产者8001或8002模块的controller层的该方法
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout() throws InterruptedException {
return paymentFeignService.paymentFeignTimeout();
}
}
通过浏览器直接访问生产者8001模块http://localhost:8001/payment/feign/timeout,等待3秒钟后,可以看到结果
通过浏览器访问80消费者http://localhost/consumer/payment/feign/timeout,发现页面报错,提示Read time out。
④ Feign默认连接超时为1秒,默认读取资源超时是1秒,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错,为了避免这样的情况,有时需要设置Feign客户端的超时时间,在80模块的yml里添加配置:
# 设置feign客户端超时时间,OpenFeign默认支持Ribbon
ribbon:
# 建立连接后,读取资源所用时间5s
ReadTimeout: 5000
# 建立连接所用时间5s
ConnectTimeout: 5000
3. OpenFeign日志打印功能
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而查看Feign中Http请求细节,对Feign接口调用进行监控。
日志级别:
- NONE:默认,不显示任何日志
- BASIC:仅记录请求方法、URL、响应状态码及执行时间
- HEADERS:除了BASIC中定义的信息外,还有请求头和响应头的信息
- FULL:除了HEADERS中定义的信息之外,还有请求体和响应体的正文及元数据
① 添加一个日志配置类。这个需要配置在需要的模块,如cloud-consumer-feign-order80消费者模块:
package com.atguigu.springcloud.config;
import feign.Logger;
@Configuration
public class FeignConfig {
@Bean
Logger.Level fiegnLoggerLevel(){
return Logger.Level.FULL;
}
}
② 在yml里指定监控哪个请求接口和监控级别:一般打印Feign接口调用的日志
logging:
level:
# 指定监控哪个接口,以及监控的级别,80消费者模块中的业务接口PaymentFeignService
com.atguigu.springcloud.service.PaymentFeignService: debug
③ 测试:浏览器访问http://localhost/consumer/payment/get/1后,控制台打印日志: