SpringCloud下基于OpenFeign的服务调用实践
Feign是一个声明式的Web Service客户端,而OpenFeign则是Spring Cloud在Feign的基础上增强了对Spring MVC注解的支持。其提供了比RestTemplate更加优雅、便捷的服务调用方式
服务提供者
服务提供者payment的部分实现如下,可以看到其暴露了两个接口 pay/hello1、pay/hello2 用于给消费者调用
@RestController
@RequestMapping("pay")
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/hello1")
public String hello1(@RequestParam String name) {
String msg = "[Payment Service-"+ serverPort +"]: " + name;
return msg;
}
@PostMapping("/hello2")
public Person hello2(@RequestBody Person person) {
person.setServicePort( serverPort );
String uuid = UUID.randomUUID().toString();
person.setId( uuid );
return person;
}
}
服务消费者
对于服务消费者order而言,首先需要引入OpenFeign依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
为了调用payment服务,我们直接定义接口即可。特别地,该接口需要添加@Component注解。具体地,用@FeignClient注解的name、path属性分别定义所调用服务的服务名、url路径。对于接口中方法,可以通过@RequestMapping等注解定义所调用服务具体的url路径。对于传参,类似于Controller层参数绑定一样,通过@RequestParam、@PathVariable、@RequestBody等注解绑定参数。不同的是,对于@RequestParam、@PathVariable而言,需要通过value属性显式设置参数名。示例如下
@Component
@FeignClient(name = "payment", path = "pay" )
public interface PaymentService {
@GetMapping("/hello1")
// 需要通过@RequestParam注解的value属性显式指定参数名
String method1(@RequestParam(value = "name") String name);
@PostMapping("/hello2")
Person method2(@RequestBody Person person);
}
然后,我们就可以直接注入该接口paymentService来进行服务调用,示例如下
@RestController
@RequestMapping("order2")
public class OrderController2 {
@Autowired
private PaymentService paymentService;
@GetMapping("/test1")
public String test1(@RequestParam String name) {
String msg = paymentService.method1(name) ;
String result = "[Order Service test1]: " + msg;
return result;
}
@GetMapping("/test2")
public String test2(@RequestParam String name, @RequestParam Integer age) {
Person person = Person.builder()
.name( name )
.age( age )
.build();
person = paymentService.method2(person);
String result = "[Order Service test2]: " + person;
return result;
}
}
最后,需要在启动类上添加 @EnableFeignClients 注解,如下所示
@SpringBootApplication
// 使用Consul作为注册中心
@EnableDiscoveryClient
// 启用Feign客户端
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
测试
至此,我们对于服务提供者payment分别在8004、8005、8006创建三个实例,对于服务消费者order创建一个运行在82端口的实例。并通过Consul作为注册中心。测试结果如下,符合预期。且进一步说明OpenFeign默认支持Ribbon,实现了负载均衡
超时配置
OpenFeign调用服务时,默认超时配置为1秒。故可通过 feign.client.config.
# 配置名为payment的OpenFegin客户端超时参数
feign:
client:
config:
payment:
# 建立连接超时阈值, Unit: ms
connectTimeout: 10000
# 读取资源超时阈值, Unit: ms
readTimeout: 10000
日志配置
OpenFeign特别提供了日志功能,实现了对服务接口调用的监控。具体有下述四种日志级别
NONE:默认的,不显示任何日志 BASIC:显示请求方法、URL、状态码及执行时间 HEADERS:除了BASIC中定义的信息之外,还会显示请求头、响应头 FULL:除了HEADERS中定义的信息之外,还会显示请求体、响应体及元数据
这里,我们利用Java配置类定义OpenFeign的日志级别
package com.aaron.SpringCloud1.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenFeignLogConfig {
/**
* 配置OpenFeign日志级别
* @return
*/
@Bean
public Logger.Level openFeignLogLevel() {
return Logger.Level.FULL;
}
}
然后,将我们期望监控的OpenFeign接口的日志级别设为DEBUG即可,如下所示
logging:
level:
# 将PaymentService接口的日志级别设为DEBUG
com.aaron.SpringCloud1.order.service.PaymentService: DEBUG
测试效果,如下所示,符合预期
服务降级
OpenFeign不仅支持Ribbon实现负载均衡,与此同时其还依赖了Hystrix以支持服务降级。故在服务消费者order中实现PaymentService接口,以提供该接口的降级类。实现相应方法的降级逻辑
@Service
public class PaymentFallBackService implements PaymentService {
@Override
public String method1(String name) {
return "invalid";
}
@Override
public Person method2(Person person) {
person.setId( null );
person.setServicePort("-1");
return person;
}
}
然后在PaymentService接口上,通过@FeignClient注解的fallback属性指定其相应的降级类
@Component
@FeignClient(name = "payment", path = "pay", fallback = PaymentFallBackService.class)
public interface PaymentService {
@GetMapping("/hello1")
String method1(@RequestParam(value = "name") String name);
@PostMapping("/hello2")
Person method2(@RequestBody Person person);
}
对于order服务的配置文件,做相应调整。首先调整OpenFeign中payment服务的超时配置(connectTimeout、readTimeout)为10s,然后将 feign.hystrix.enabled 设为true,使能OpenFeign的Hystrix功能。最后设置Hystrix的相关配置。具体地,我们配置全局默认的超时时间为3s。当然我们也可以针对某个方法设置单独的超时配置以覆盖全局默认,其中配置项的名称规则为 接口名#方法名(参数类型) 。例如这里设置PaymentService接口method2方法的超时时间为5s,使用的配置项名称为PaymentService#method2(Person)
feign:
client:
config:
payment:
# 建立连接超时阈值, Unit: ms
connectTimeout: 10000
# 读取资源超时阈值, Unit: ms
readTimeout: 10000
# 启用OpenFeign的Hystrix功能
hystrix:
enabled: true
hystrix:
command:
# 配置全局默认的超时时间
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
# 配置PaymentService接口method2方法的超时时间
PaymentService#method2(Person):
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
现在我们通过调用order服务接口来进行验证,可以看到正常情况下,通过OpenFeign调用的服务均未发生降级
现在我们对payment服务进行改造,通过Thread.sleep来模拟业务耗时,如下所示
@RestController
@RequestMapping("pay")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/hello1")
public String hello1(@RequestParam String name) {
String msg = "[Payment Service-"+ serverPort +"]: " + name;
// 模拟业务耗时
try{ Thread.sleep(4000); } catch (Exception e) {}
return msg;
}
@PostMapping("/hello2")
public Person hello2(@RequestBody Person person) {
person.setServicePort( serverPort );
String uuid = UUID.randomUUID().toString();
person.setId( uuid );
// 模拟业务耗时
try{ Thread.sleep(8000); } catch (Exception e) {}
return person;
}
}
可以看到两个方法均进行降级,测试结果符合预期,如下所示
参考文献
Spring微服务实战 John Carnell著