带你玩转OpenFeign 从入门到精通
Feign是啥玩意?
一句话:Feign-伪装
就像调用业务层一样优雅 同时又是基于rest风格的请求方式
以前通过http调用其他服务:
缺点:如果接口地址改变了 需要跟着一起修改 以及代码风格不优雅 restTemplate应该用来对接外部系统的http客户端
@Autowired
private RestTemplate restTemplate;
//配置请求地址
// private static final String SERVICE_PROVIDER = "http://localhost:8081";
private static final String SERVICE_PROVIDER = "http://sxw-provider-01";
//跨服务根据列表查询
@GetMapping("/list")
public List<Depart> listHandle() {
String url = SERVICE_PROVIDER + "/provider/depart/list/";
List list = restTemplate.getForObject(url, List.class);
return list;
}
Feign调用
请注意 这里是调用其他服务 是不是感觉像是再调用自己的服务一样优雅 不需要关注提供者服务 并且Feign提供了非常多的功能 比如:服务降级、集群访问、负载均衡....
//消费者Controller,对外提供接口
@RequestMapping("/consumer/feign/depart")
@RestController
public class DepartFeignController {
@Autowired
private DepartFeignService feignService;
//根据id查询
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) {
return feignService.getDepartById(id);
}
}
使用Feign
1.导入依赖
如果你的架构基于注册中心 需要导入注册中心相关依赖 这里不详细展开
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.定义feign接口 注意是一个接口 需要加EnableClient注解 添加提供者的名字或者url地址 集群 使用postmapping等springmvc的一些注解
//provider 服务提供者的名字 application.name=xxx
@FeignClient("provider")
public interface DepartFeignService {
@GetMapping("/provider/depart/get/{id}")
Depart getDepartById(@PathVariable("id")int id);
}
3.启动类增加开启类 EnableFeign 指定feign的包
@SpringBootApplication
@EnableDiscoveryClient
//开启Feign 扫描这个包下的Feign客户端
@EnableFeignClients("com.sxw.feign")
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
4.消费者定义一个Controller进行测试 通过feign调用提供者
//消费者Controller,对外提供接口
@RequestMapping("/consumer/feign/depart")
@RestController
public class DepartFeignController {
@Autowired
private DepartFeignService feignService;
//根据id查询
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) {
return feignService.getDepartById(id);
}
}
5.消费者配置文件
#这是我的消费者的配置文件 nacos启动了集群
server:
port: 8999
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848,127.0.0.1:8847,127.0.0.1:8849
application:
name: sxw-consumer-feign
6.请求浏览器 查看结果
http://127.0.0.1:8999/consumer/feign/depart/get/1
使用完后的问题
问题1 feign没有实现类,哪来的?接口是不能创建对象的?
问题2 为什么会使用到springmvc的注解
Feign超时配置
1.消费者配置文件添加以下
#...其他配置不动
#feign配置
feign:
client:
config:
default:
#连接超时时间
connectTimeout: 5000
#数据读取超时时间
readTimeout: 5000
2.提供者增加线程睡眠 测试一下 5秒 设置方法需要执行6秒
public Depart getDepartById(int id) {
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(repository.existsById(id)) {
Depart one = repository.getOne(id);
one.setName(departName);
return one;
}
Depart depart = new Depart();
depart.setName("port:" + port);
return depart;
}
3.重启服务 访问浏览器 http://127.0.0.1:8999/consumer/feign/depart/get/1 消费者服务报错
Feign配置负载均衡
怎么实现的
一句话 直接内置了 Ribbon。在导入OpenFeign 依赖后无需专门导入 Ribbon 依赖。
使用
需求:提供者创建3个组成一个集群 通过负载均衡器ribbon对提供者进行请求的负载均衡
1.修改提供者的接口返回 方便看得更清楚 把端口号返回出来
@Value("${server.port}")
private int port;
@Override
public Depart getDepartById(int id) {
if(repository.existsById(id)) {
Depart one = repository.getOne(id);
one.setName(departName);
return one;
}
Depart depart = new Depart();
depart.setName("port:" + port);
return depart;
}
2.启动多个提供者 组成集群
3.访问消费者 http://127.0.0.1:8999/consumer/feign/depart/get/10 访问三次
4.得出结论 feign集成了ribbon 以及默认的策略是轮询
更改负载均衡策略
1.配置文件配置:
配置文件配置的好处可以针对具体微服务进行单独配置负载均衡策略
#服务名
provider:
ribbon:
#随机 针对provider这个服务 Feign客户端采用随机的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2.java配置类方式
作为一个配置类起到全局的配置作用
//定义全局负载均衡策略
@Bean
public IRule loadBalanceRule(){
return new RandomRule();
}
自定义负载均衡器
好处:可以根据自己的业务和实际情况来定义负载均衡策略 比如配置黑白名单等
需求:可以根据自己的业务和实际情况来定义负载均衡策略 比如配置黑白名单等
1.编写类 继承Irule类
public class MyRibbonRule implements IRule {
private ILoadBalancer lb;
private List<Integer> excludePorts;
public MyRibbonRule() {
}
public MyRibbonRule(List<Integer> excludePorts) {
this.excludePorts = excludePorts;
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer() {
return lb;
}
/**
* 目标:自定义负载均衡策略:从所有可用的provider中排除掉指定端口号的provider,剩余
* provider进行随机选择
* 实现步骤:
* 1.获取到所有Server
* 2.从所有Server中排除掉指定端口的Server后,剩余的Server
* 3.从剩余Server中随机选择一个Server
*/
@Override
public Server choose(Object key) {
// 1.获取到所有Server
List<Server> servers = lb.getReachableServers();
// 2.从所有Server中排除掉指定端口的Server后,剩余的Server
List<Server> availableServers = this.getAvailableServers(servers);
// 3.从剩余Server中随机选择一个Server
return this.getAvailableRandomServers(availableServers);
}
private List<Server> getAvailableServers(List<Server> servers) {
// 若没有指定要排除的port,则返回所有Server
if (excludePorts == null || excludePorts.size() == 0) {
return servers;
}
List<Server> aservers = servers.stream()
// filter()
// noneMatch() 只有当流中所有元素都没有匹配上时,才返回true,只要有一个匹配上了,则返回false
.filter(server -> excludePorts.stream().noneMatch(port ->
server.getPort() == port))
.collect(Collectors.toList());
return aservers;
}
private Server getAvailableRandomServers(List<Server> availableServers) {
// 获取一个[0,availableServers.size())的随机数
int index = new Random().nextInt(availableServers.size());
return availableServers.get(index);
}
}
2.配置自定义负载均衡器
//这样配置后 你会发现 8991端口的服务永远都不会被访问到了
@Bean
public IRule loadBalanceRule(){
ArrayList<Integer> arrayList = new ArrayList();
arrayList.add(8991);
return new MyRibbonRule(arrayList);
}
3.测试 自行测试~
ribbon其他的内置的负载均衡策略
(1) RoundRobinRule
轮询策略:Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮(代码中写死的,不能修改)。若还未找到,则返回 null。
(2) RandomRule
随机策略:从所有可用的 provider 中随机选择一个。
(3) RetryRule
重试策略:先按照 RoundRobinRule 策略获取 server,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
(4) BestAvailableRule
最可用策略:选择并发量最小的 provider,即连接的消费者数量最少的 provider。其会遍历服务列表中
的每一个server,选择当前连接数量 minimalConcurrentConnections 最小的server。
(5) AvailabilityFilteringRule
可用过滤算法:该算法规则是过滤掉处于熔断状态的 server 与已经超过连接极限的server,对剩余
server 采用轮询策略。
负载均衡器 LoadBalancer
缘由
由于 Netflix 对于 Ribbon 的维护已经暂停,所以 Spring Cloud 对于负载均衡建议使用由其自己定义的 Spring Cloud LoadBalancer
使用
1.修改配置文件
2.增加依赖
<!--spring cloud loadbalancer 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Feign和熔断器
1.配置文件开启熔断器支持
#feign配置
feign:
client:
config:
default:
#连接超时时间
connectTimeout: 5000
#数据读取超时时间
readTimeout: 5000
#开启hystrix熔断功能
hystrix:
enabled: true
2.编写fallBack处理类实现Feign接口
@Component
public class DepartFallbackService implements DepartFeignService {
@Override
public Depart getDepartById(int id) {
Depart depart = new Depart();
depart.setId(id);
depart.setName("查无此人");
return depart;
}
}
3.在FeignCleint注解指定回滚类
@FeignClient(value = "provider",fallback = DepartFallbackService.class)
public interface DepartFeignService {
}
4.测试:关闭服务提供者 直接访问http://127.0.0.1:8999/consumer/feign/depart/get/100
结果:这个结果是我们定义的降级方法
{"id":100,"name":"查无此人"}
Feign日志
有四个级别
- NONE-不做任何记录
- BASIC-只记录输出Http 方法名称、请求URL、返回状态码和执行时间
- HEADERS-记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息
- FULL-记录Request 和Response的Header,Body和一些请求元数据-
1.编写配置类
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
2.注入FeignClient注解
@FeignClient(value = "provider",
fallback = DepartFallbackService.class,configuration = FeignConfig.class)
public interface DepartFeignService {
}
3.修改配置文件
#com.sxw包下的日志级别都是debug
logging:
level:
com.sxw: debug
4.测试
Feign原理
回顾以下 springmvc的内容
springMVc通过地址绑定一个HandlerMapping.
Feign通过一个HandlerMapping逆向得到请求地址
对象创建的方式
1. Object obj = new Object 简单粗暴
2.反射
3.动态代理JDK、CGLib
源码目标
目标1 FeignCleint接口创建实现类对象的过程
目标2:FeignClient如何发出http请求的
Feign源码入口
EnableXxx 中一般用于开启某项功能,会@Import 导入一些类,这三类一般分为三种:
配置类:一般以 Configuration
结尾选择器:一般以 Selector 结尾
注册器:一般以 Registrar 结尾
源码跟进图:
总结
目标1 FeignCleint接口创建实现类对象的过程
首先扫描@FeignClient这个注解的所有接口 将注解里的属性全部提取出来放到一个BeanDedition中(这里需要有spring源码的基础) 然后交由spring进行管控.当发起请求时 通过这个Beandition通过动态代理以及反射将这个接口的实现类创建出来 至此完成了Feign接口创建类对象的过程
目标2:FeignClient如何发出http请求的
调用时通过动态代理生成代理对象执行invoke方法,对其进行http请求的发送.使用的客户端是httpUrlConnection来实现的