Spring Cloud OpenFeign 是声明式的服务调用组件,它整合了 Ribbon 和 Hystrix,拥有负载均衡和服务容错的功能。一开始这个组件不叫这个名字,一开始就叫 Feign,但是 Netflix 中的组件现在已经停止开源工作,OpenFeign 是 Spring Cloud 团队在 Netflix Feign 的基础上开发出来的声明式服务调用组件。
新建Spring Boot项目,添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
在 application.properties 中添加配置
#当前服务的名字
spring.application.name=eureka
#端口号(Eureka后台管理端端口)
server.port=1111
#默认情况下,Eureka Server也是一个普通的微服务,所以当它还是注册中心时,就有两层身份:
#1.注册中心;2.普通服务。默认当前eureka server自动把自己注册到注册中心中,
# 通过eureka.client.register-with-eureka=false设置不注册
eureka.client.register-with-eureka=false
#表示是否从Eureka Server上获取注册信息
eureka.client.fetch-registry=false
启动类上添加注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
新建Spring Boot项目,添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
在 application.properties 中添加配置
#服务名
spring.application.name=provider
#服务端口号
server.port=1113
#注册中心url地址
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
创建Controller类,提供/hello接口给其他服务调用
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/hello")
public String hello(){
return "hello world port:"+port;
}
}
新建Spring Boot项目,添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
在启动类上添加==注解@EnableFeignClients ==来启用 Feign 的客户端功能
@SpringBootApplication
@EnableFeignClients
public class OpenfeignApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignApplication.class, args);
}
}
在 application.properties 中添加配置,使项目注册到 Eureka 上:
spring.application.name=openfeign
server.port=4000
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
定义接口,使用@FeignClient注解,指定要调用的服务为provider
@FeignClient(value = "provider")
public interface HelloService {
/**
* 请求provider的“/hello接口”
* @return
*/
@GetMapping("/hello")
public String hello();
}
创建controller,使用上面的open feign接口
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(){
return helloService.hello();
}
}
服务提供者provider提供以下接口:
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/hello")
public String hello(){
return "hello world port:"+port;
}
@GetMapping("/test_get")
public String hell02(String name){
System.out.println(new Date()+"-"+name);
return "Hello "+name;
}
/**
* 测试post,接收key/value形式的参数
*/
@PostMapping("/user1")
public User addUser(User user){
return user;
}
/**
* 测试post,接收json形式的参数
*/
@PostMapping("/user2")
public User addUser2(@RequestBody User user){
return user;
}
/**
* 测试put,接收key/value形式的参数
* @param user
*/
@PutMapping("/user1")
public void updateUser(User user){
System.out.println(user);
}
/**
* 测试put,接收json格式的参数
* @param user
*/
@PutMapping("/user2")
public void updateUser2(@RequestBody User user){
System.out.println(user);
}
/**
* 测试delete,接收key/value
* @param id
*/
@DeleteMapping("/user1")
public void deleteUser1(Integer id){
System.out.println(id);
}
/**
* 测试delete,在路径上直接写参数
* @param id
*/
@DeleteMapping("/user2/{id}")
public void deleteUser2(@PathVariable Integer id){
System.out.println(id);
}
@GetMapping("/user3")
public void getUserByName(@RequestHeader("name") String name) throws UnsupportedEncodingException {
System.out.println("get header property of name:"+ URLDecoder.decode(name,"UTF-8"));
}
}
使用OpenFeign,方法参数一定要绑定参数名(凡是 key/value 形式的参数,一定要标记参数的名称。),如果通过header来传递参数,中文需要转码。不同参数类型传递示例:
@FeignClient(value = "provider")
public interface HelloService {
/**
* 请求provider的“/hello接口”
* @return
*/
@GetMapping("/hello")
public String hello();
/**
* 请求带key/value的方法
* @param name
* @return
*/
@GetMapping("/test_get")
public String hello2(@RequestParam("name") String name);
/**
* 请求“/user2”接口,参数为json格式的
* @param user
* @return
*/
@PostMapping("/user2")
public User insertUser(@RequestBody User user);
/**
* 测试“/user2/{id}”
* 注意@PathVariable("id")
* @param id
*/
@DeleteMapping("/user2/{id}")
public void deleteUserById(@PathVariable("id") Integer id);
@PutMapping("/user1")
public void updateUser(@RequestParam("id") Integer id,@RequestParam("username") String username,@RequestParam("password") String password);
/**
* @RequestHeader的用法
* 测试注解在Header中传递参数
* @param name
*/
@GetMapping("/user3")
public void getUserByName(@RequestHeader("name") String name);
}
在HelloController中进行调用测试
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(){
return helloService.hello();
}
/**
* 测试各类请求的远程调用
*/
@GetMapping("/test_param")
public void testParam() throws UnsupportedEncodingException {
User user = new User();
user.setId(1);
user.setUsername("zhang2");
user.setPassword("321");
User user1 = helloService.insertUser(user);
helloService.deleteUserById(2);
helloService.updateUser(2,"Ethan","321");
helloService.getUserByName(URLEncoder.encode("不会吧","UTF-8"));
System.out.println(user1);
}
}
OpenFeign 中,我们可以通过配置日志,来查看整个请求的调用过程。日志级别一共分为四种:
OpenFeign日志的四种级别,可以通过Bean来配置:
@SpringBootApplication
@EnableFeignClients
public class OpenfeignApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignApplication.class, args);
}
@Bean
Logger.Level loggerLevel(){
return Logger.Level.FULL;
}
}
最后,需要在applicaiton.properties中开启日志级别:
logging.level.top.javahai.openfeign=debug
其中top.javahai.openfeign是包名,重启OpenFeign测试是否打印。
OpenFeign请求的数据压缩配置
# 开启请求的数据压缩
feign.compression.request.enabled=true
# 开启响应的数据压缩
feign.compression.response.enabled=true
# 压缩的数据类型
feign.compression.request.mime-types=text/html,application/json
# 压缩的数据下限,2048 表示当要传输的数据大于 2048 时,才会进行数据压缩
feign.compression.request.min-request-size=2048
在Open Feign中可以使用Hystrix实现服务容错和降级的功能。首先在application.properties中配置开启Hystrix
#开启Hystric
feign.hystrix.enabled=true
首先定义服务降级的方法,实现HelloService。
@Component
@RequestMapping("/error")
public class HelloServiceFallback implements HelloService {
@Override
public String hello() {
return "error";
}
@Override
public String hello2(String name) {
return "error2";
}
@Override
public User insertUser(User user) {
return new User();
}
@Override
public void deleteUserById(Integer id) {
System.out.println("error");
}
@Override
public void updateUser(Integer id, String username, String password) {
System.out.println("error");
}
@Override
public void getUserByName(String name) {
System.out.println("error");
}
}
在HelloService中配置服务降级类
@FeignClient(value = "provider",fallback = HelloServiceFallback.class)
public interface HelloService {
}
或者通过配置FallbackFactory来实现服务降级
@Component
public class HelloServiceFallbackFactory implements FallbackFactory<HelloService> {
@Override
public HelloService create(Throwable throwable) {
return new HelloService() {
@Override
public String hello() {
return "error";
}
@Override
public String hello2(String name) {
return "error2";
}
@Override
public User insertUser(User user) {
return null;
}
@Override
public void deleteUserById(Integer id) {
}
@Override
public void updateUser(Integer id, String username, String password) {
}
@Override
public void getUserByName(String name) {
}
};
}
}
然后HelloService 中进行配置:
@FeignClient(value = "provider",fallbackFactory = HelloServiceFallbackFactory.class)
public interface HelloService {
}
常用全局配置如下:
hystrix:
command:#用于控制HystrixCommand的行为
default:
execution:
isolation:
strategy:THREAD#控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略
thread:
timeoutInMilliseconds:1000#配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
interruptOnTimeout:true#配置HystrixCommand执行超时的时候是否要中断
interruptOnCancel:true#配置HystrixCommand执行被取消的时候是否要中断
timeout:
enabled:true#配置HystrixCommand的执行是否启用超时时间
semaphore:
maxConcurrentRequests:10#当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝
fallback:
enabled:true#用于控制是否启用服务降级
circuitBreaker:#用于控制HystrixCircuitBreaker的行为
enabled:true#用于控制断路器是否跟踪健康状况以及熔断请求
requestVolumeThreshold:20#超过该请求数的请求会被拒绝
forceOpen:false#强制打开断路器,拒绝所有请求
forceClosed:false#强制关闭断路器,接收所有请求
requestCache:
enabled:true#用于控制是否开启请求缓存
collapser:#用于控制HystrixCollapser的执行行为
default:
maxRequestsInBatch:100#控制一次合并请求合并的最大请求数
timerDelayinMilliseconds:10#控制多少毫秒内的请求会被合并成一个
requestCache:
enabled:true#控制合并请求是否开启缓存
threadpool:#用于控制HystrixCommand执行所在线程池的行为
default:
coreSize:10#线程池的核心线程数
maximumSize:10#线程池的最大线程数,超过该线程数的请求会被拒绝
maxQueueSize:-1#用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue
queueSizeRejectionThreshold:5#用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数
实例配置只需要将全局配置中的default换成与之对应的key即可。
hystrix:
command:
HystrixComandKey:#将default换成HystrixComrnandKey
execution:
isolation:
strategy:THREAD
collapser:
HystrixCollapserKey:#将default换成HystrixCollapserKey
maxRequestsInBatch:100
threadpool:
HystrixThreadPoolKey:#将default换成HystrixThreadPoolKey
coreSize:10
配置文件中相关key的说明
Open Feign 整合了 Ribbon 实现负载均衡,在Open Feign 中可以直接使用 Ribbon 的配置。
#服务请求连接超时时间(毫秒)
ribbon.ConnectTimeout=1000
#服务请求处理超时时间(毫秒)
ribbon.ReadTimeout=3000
#对超时请求启用重试机制
ribbon.OkToRetryOnAllOperations=true
#切换重试实例的最大个数
ribbon.MaxAutoRetriesNextServer=1
# 切换实例后重试最大次数
ribbon.MaxAutoRetries=1
#修改负载均衡策略
ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
最后一点负载均衡策略配置的选择,Ribbon提供了如下几种负载均衡策略:
与全局配置的区别就是Ribbon节点挂在服务(FeignClient的name属性)名称下面的,例如指定对provider服务提供的接口单独配置:
#服务请求连接超时时间(毫秒)
provider.ribbon.ConnectTimeout=1000
参考:Spring Cloud OpenFeign:基于 Ribbon 和 Hystrix 的声明式服务调用