简介
OpenFeign
声明性REST客户端:OpenFeign
创建用JAX-RS或Spring MVC注释修饰的接口的动态实现,OpenFeign
是Netflix的一个声明性HTTP客户端,它简化了我们与其他服务交互的方式。
OpenFeign
提供了两个重要标注@FeignClient
和@EnableFeignClients
。
@FeignClient
标注用于声明Feign客户端可访问的Web服务。
@EnableFeignClients
标注用于修饰Spring Boot应用的入口类,以通知Spring Boot启动应用时,扫描应用中声明的Feign
客户端可访问的Web服务。
OpenFeign
是通过Eureka
调用的组件并做为Eureka
的Client
端一起使用,所以提前启动Eureka
服务,请看上一篇博客
提供者
- 跟我们之前构建项目一样, 使用
idea
工具直接创建一个新的SpringBoot
项目,在选择依赖的界面勾选Cloud Discovert -> OpenFeign
依赖,创建完成后的pom.xml
配置文件内容如下:
<!--OpenFeign做为Eureka组件的Client端,必须依赖Eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在主启动类上添加
OpenFeign
的注解@EnableEurekaClient
,如下所示:
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientOneApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientOneApplication.class, args);
}
}
- controller接口如下所示:
@RestController
public class TestEurekaController {
@GetMapping("/test/eureka")
public String aaa() {
return "hello world!";
}
}
- 配置文件
server:
port: 8081
spring:
application:
name: client-one
main:
allow-bean-definition-overriding: true
#服务地址
eureka:
instance:
hostname: localhost
#心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端,不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活
lease-renewal-interval-in-seconds: 5
#心跳超时时间10s,默认90s。从client端最后一次发出心跳后,达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除
lease-expiration-duration-in-seconds: 10
client:
##不向注册中心注册自己
# register-with-eureka: false
##取消检索服务
# fetch-registry: false
##注册中心路径,如果有多个eureka server,在这里需要配置其他eureka server的地址,用","进行区分,如"http://address:8888/eureka,http://address:8887/eureka"
service-url:
defaultZone: http://localhost:8080/eureka
调用者
新建项目和依赖同上
- 在主启动类上添加
OpenFeign
的注解@EnableFeignClients
,如下所示:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OpenFeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignClientApplication.class, args);
}
}
- 新建一个接口,通过
FeignClient
调用接口,FeignClient
参数值为Eureka
注册中心的Application Name
,@GetMapping
是请求服务接口地址。代码和图如下:
@FeignClient("CLIENT-ONE")
public interface RpcService {
//这里调用无参方法,如果是有参数把提供者暴露接口方法复制过来,把mapping换成完整路径
@GetMapping("/test/eureka")
String getRpc();
}
- 配置文件
server:
port: 8082
spring:
application:
name: client-two
main:
allow-bean-definition-overriding: true
#服务地址
eureka:
instance:
hostname: localhost
#心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端,不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活
lease-renewal-interval-in-seconds: 5
#心跳超时时间10s,默认90s。从client端最后一次发出心跳后,达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除
lease-expiration-duration-in-seconds: 10
client:
##不向注册中心注册自己
# register-with-eureka: false
##取消检索服务
# fetch-registry: false
##注册中心路径,如果有多个eureka server,在这里需要配置其他eureka server的地址,用","进行区分,如"http://address:8888/eureka,http://address:8887/eureka"
service-url:
defaultZone: http://localhost:8080/eureka
运行测试
新建一个Controller代码如下:
@RestController
public class CityController {
@Autowired
private RpcService rpcService;
@GetMapping("/cities")
public String listCity(){
String body = rpcService.getRpc();
return body;
}
}
在浏览器访问:localhost:8080/cities
得到数据就是Feign
调用接口返回数据
加强配置
feign:
client:
config:
default:
# 连接超时,Feign客户端连接提供者的超时阈值
connectTimeout: 5000
# 读超时,从Feign客户端请求发出到接收到提供者响应,这段时间超时阈值
readTimeout: 3000
# 设置指定某个服务超时阈值,其优先级要高于全局
CLIENT-ONE: # 服务名称:spring.application.name
# readTimeout和connectTimeout需要同时配置,否则不生效
readTimeout: 5000
connectTimeout: 3000
# 配置服务路由策略,代码优先于配置
CLIENT-ONE:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
自定义路由策略
定义:CustomRuleBalancer类并实现IRule接口。
功能:传递一组端口,从服务列表中删除这些端口对应服务,对剩余服务进行随机访问。
注解入Bean:
@Bean
public IRule loadBalancer(){
return new CustomRuleBalancer(Collections.singletonList(8080));
}
代码:
public class CustomRuleBalancer implements IRule {
private ILoadBalancer lb;
private List<Integer> excludePorts;
public CustomRuleBalancer() {
}
CustomRuleBalancer(List<Integer> excludePorts) {
this.excludePorts = excludePorts;
}
@Override
public Server choose(Object o) {
//所有可用的servers
List<Server> servers = lb.getReachableServers();
//过滤server
List<Server> availableServers = getAvailableServers(servers);
return getAvailableRandomServers(availableServers);
}
/**
* 从所有servers中获得排队端口以外的servers
*
* @param servers
* @return
*/
private List<Server> getAvailableServers(List<Server> servers) {
if (CollectionUtils.isEmpty(excludePorts)) {
return servers;
}
return servers.stream()
.filter(f -> !excludePorts.contains(f.getPort()))
.collect(Collectors.toList());
}
/**
* 从servers中随机一个
*
* @param availableServers
* @return
*/
private Server getAvailableRandomServers(List<Server> availableServers) {
int index = new Random().nextInt(availableServers.size());
return availableServers.get(index);
}
@Override
public void setLoadBalancer(ILoadBalancer iLoadBalancer) {
this.lb = iLoadBalancer;
}
@Override
public ILoadBalancer getLoadBalancer() {
return lb;
}
}