远程调用
介绍
我们之前学习Nacos的时候,通过RestTemplete也可以实现"远程调用"(就是订单微服务调用商品微服务的那个例子),那种远程调用的方式,需要自己手动编码,因此被称为"编程式Rest客户端",这样实现袭来需要自己编码,有些许麻烦
我们接下来学习的OpenFeign是**"声明式Rest客户端"只需要使用注解声明:请求地址、请求方式、携带数据、返回方式等就可以便捷的使用远程调用**

基础使用
我们还是以之前那个订单远程调用商品微服务的例子来举例
1.先引入依赖(可以在service大包里面引入,因为后续的所有微服务都需要使用远程调用)

2.在订单启动类上声明@EnableFeignClients来让该微服务启用时开启OpenFeign的客户端使得有相应的Feign远程调用功能
3.Feign客户端的声明(核心重点)
先在订单微服务下声明接口

接口上声明@FeignClient(value = "product-service")注解
注解用于启动Feign的客户端,让其有"发送请求"的功能,其中的value参数填写的时需要远程调用的微服务名(Nacos里面定义好的名称)

随后接口内声明注解和相应方法:
接口:@GetMapping("/product/{id}")作用是顺着刚刚上面Feign想要调用的微服务,拼接url并且发送(注意: @GetMapping注解只有声明在controller下才是接收Get请求,如果是像这样声明GetMapping那就是发送一个Get请求**)** 一个Get请求,
完整的请求为:http://service-product/product/{id}
方法:声明了刚刚发送请求返回的数据类型(Product),并且声明了发送请求的相关参数

完整代码
java
/ @FeignClient用于启用OpenFeign客户端,可以实现远程调用 注解的 value 属性值必须和Nacos里注册的服务名一致
@FeignClient(value = "product-service")
public interface ProductFeignClient {
//这里GetMapping注解没有在Controller下,因此它的作用并不是"接收Get请求"而是"发送Get请求",后面的注解参数是发送的请求的参数
// @PathVariable注解用于从URL中提取变量的值,然后赋给方法参数
//因此结合上面FeignClient注解,这个方法的作用是:发送一个Get请求,请求的URL是"http://service-product/product/{id}",其中{id}是URL中的变量,值是方法参数id的值,返回数据类型是Product
@GetMapping("/product/{id}")
Product getProductById(@PathVariable Long id);
}
因此,回看我们前文说的"请求地址、请求方式、携带数据、返回方式",我们在刚刚的feign接口中全部都声明好了,feign客户端会根据这些声明好的内容自动远程调用并且是负载均衡,然后返回相关数据
发送测试请求没问题

远程调用第三方服务
总体和我们上文讲的基础使用差不多,只不过第三方服务不归我们管理,因此Feign不会再去Nacos里面寻找微服务,我们在写法上会有一点点小变化
这里以课程中调用第三方微服务"墨迹天气"举例
和之前基础调用一样,我们需要先新建一个Feign的客户端接口,然后和之前一样定义好相关注解,其中value注解后面的值我们可以随便写(因为这个第三方服务并不归我们管理),所以我们可以通过后面"url参数"来指定我们访问第三方服务的域名(定义了url参数,Feign就不会取Nacos注册中心找相关名字的微服务)
接着下方的Mapping写好具体查询天气的补充路径和需要用到的参数

所以Feign发出的完整请求就是:
http://aliv18.data.moji.com/whapi/json/alicityweather/condition?token=50b53ff8dd7d9fa320d3d3ca32cf8ed1&cityId=2182
纵观来看本质上都是一样的
面试题--两种负载均衡的区别
同样是远程调用,但是我们自己写的微服务和调用第三方微服务所采用的负载均衡策略不太相同,本质上是因为调用的第三方服务不归我们自己管
客户端负载均衡
当我们远程调用(Feign/RestTemplete都可以)我们自己部署的微服务时(还是以订单服务调用商品服务举例),部署商品微服务的服务器有很多个,我们Feign就会根据负载均衡算法来自己选择一个负载适合的商品微服务进行调用

服务端负载均衡
我们远程调用(Feign/RestTemplete都可以)第三方服务,由于第三方服务不归我们管,我们并不知道该服务有哪些服务器,哪些服务器的负载适合我们,因此这些都由服务端(这里的第三方)来管理,第三方只会暴露一个调用该服务的接口供所有人,当大量请求打到第三方时,由第三方的服务器做负载均衡,让这些大量打进来的请求合理的分布打到不同服务器实现负载均衡
进阶配置
详细日志输出
如果我们想看到Feign发送请求具体发送了什么,通过什么方式发送的,我们就可以做下面相关配置
yml里面声明feign包下的日志全部为debug级别
配置类中构造logger的bean对象 
可以看到返回的结果多出了debug级别的
超时控制
介绍
在讲这个配置之前我们先来了解什么是"超时控制"
场景:由订单服务调用商品服务中商品服务的连接问题/业务处理问题导致"服务雪崩"(若不清楚什么是"服务雪崩,请移步到我们最初的导学博客:微服务--分布式基础-CSDN博客")
OpenFeign自然考虑到了这个问题,因此使用Feign远程调用时,若相应时间过长,则会直接返回错误信息,防止服务器资源的占用

"超时时间"时OpenFeign自己内定好的(如下图)
连接超时时间默认为10s(建立连接时间过长触发)
读取超时时间默认为60s(业务数据返回时间过长出发)

我们来简单的实践一下Feign默认的超时控制:
写一段简单的休眠代码,product微服务会休眠10000s不返回数据给order

我们发送创建订单的请求可以看到这里一直在等待返回结果

60s过后,返回了超时的错误信息
修改配置
上面简单介绍了超时控制,下面我们来简单介绍如何修改它的配置
需要我们在配置文件中修改配置,不过我们之前配置文件写了太多东西,比较乱,因此我们这里再次引入我们创建的feign配置文件

然后来到我们自己建的application-feign.yml里面,编写如下配置
在config层级下编写Feign的默认客户端的配置和指定客户端的配置
于是我们把service-product的Feign客户端的连接超时时间改为3000ms,读取超时时间为5000ms
我们再回去把休眠时间设置为4s,看一下我们发请求是否可以得到结果
得到了结果
当我们设置为休眠5s,看看结果

很显然,5s大于等于我们的读取超时时间5000ms(也就是5s),因此返回了超时错误信息,证明了我们修改配置Feign的配置是有效成功的

重试策略--Retryer
前文我们讲了设置最大超时时间,有时候可能是网络不好,因此超时了我们多发几次请求即可,除了手动刷新,我们也可以让Feign采用"重试策略",当出现超时错误的时候不会立即返回结果,而是重新再次发送请求
简单介绍一下它的重试机制
机制介绍
如下图所示(官方默认的重试策略),当远程调用请求出现超时,此时会等100ms(period参数)然后再次发送请求,若仍然出现超时,那么下次发送请求就要在这次失败后等待100ms+1.5的时间再发送,依次类推,时间会随着次数的增多变大,但是最大时间不会超过指定的duration参数也就是1s(图中的"1")。 下图中的maxAttempts参数是"总共请求次数",这个数是包括一开始发送请求的那次的,这里设置为5,也就是说如果一开始失败,那么会重试4次
配置
springcloud官方建议关于Feign对于重试机制Retryer的配置是直接把Retryer注册到官方容器中,Feign在使用远程调用的时候会自动寻找配置的Retryer然后采用我们所配置的重试机制
(当然我们也可以在yml文件中进行配置,这里不演示了)
下面我们来实践一下
来到我们的order配置类,注册Retryer给容器,Retryer.Default是采用Retryer默认的重试策略,也就是我们上文介绍的机制(间隔实践100ms,每次1.5倍率,最高不超过1s,一共5次)
如果想自己指定重试策略,可以自己改动方法,或是在使用原有Default的基础上改变参数

配置好后,我们后续使用Feign远程调用就会自动采用Retryer重试机制了,接下来我们来测试一下
启动项目,发送请求,让order微服务远程调用product微服务(依旧让其服务sleep)
可以看到请求发送时依旧在等待

回到控制台,可以看到product输出了5次接收请求的日志,证明了重试机制的成功实现

拦截器--Interceptor
介绍
使用Feign远程调用其他服务,可以采用拦截器,关于远程调用的拦截器一共有两种,请求拦截器和相应拦截器
请求拦截器:作用在Feign远程调用发出请求后但是商品服务还没收到之前,拦截器会把该请求拦截下来,并且为该请求做相关修改操作(比如添加/修改 请求头、url、路径参数等其他内容.....)
相应拦截器:作用再远程服务返回数据给Feign但Feign还未收到数据时,响应拦截器把相关 数据拦截下来,可以对该数据做相关处理再返回给Feign
实际开发中,常用的是请求拦截器 ,使用场景非常多,若Feign有很多个远程调用的接口,此时需要统一修改请求内容,我们总不可能一个个去修改每一个接口,用请求拦截器对请求统一修改即可
更多的情况就是"开发规范 ",若一个参数只有在给远程服务调用的时候才会用到,我们就大可不必在定义Feign的接口定义该参数,因为这个参数和这些业务方法没关系,用业务方法传参再传到Feign这里完全没有必要,因此使用拦截器再给特定的远程服务提供相关参数才比较合适
配置
springcloud官方对于拦截器的推荐配置同上面的重试策略一样,都是推荐把他们注册到spring的容器中,Feign进行远程调用的时候就会自动找该这个拦截器配置
(同样可以在yml中配置)
我们新建Interceptor的包,在里面新建一个类,这个类要继承RequestInterceptor接口(官方请求拦截器接口)
其中接口有官方的默认方法apply,我们去实现它
这个apply方法就是用来修改请求的

其中参数template就是修改的模板,我们如果想要改变Feign发送的请求,那我们改变template模板即可,下图中我们可以看到我们可以修改请求体、请求头、参数、路径等等等等


我们就以修改请求头为例子吧,给请求头加一个Request-Id
修改模板为UUID,最后检查一下不要忘记注册拦截器

最后我们编辑一下product微服务的controller,让其在控制台打印出增加的请求头
发送请求后,控制台结果如下

兜底返回--Fallback
注意:实现兜底返回还需要Sentinel专门做熔断处理的框架才能一同实现,因此这里会稍微设计Sentinel的内容,Sentinel的具体内容我们会在下一篇文章具体讲解
我们前面介绍了超时控制,Feign为了防止服务雪崩,自己会有超时控制的管理,超时或者错误就会返回错误信息,但是一味的返回错误信息对于用户体验以及其他的业务并不是那么好,有的时候我们希望返回一些"兜底数据",是我们自己定义的默认数据或者假数据也好,用Redis缓存的数据也好,兜底返回数据可以让我们整体体验提升

下面我们就来简单实现一下Fallback吧
基础实现
在此之前先讲解一下思路吧,兜底的本质就是cloud中框架的Feign检测到请求失败然后从容器中调用一个我们提前写好的方法来执行, 因此这个"提前写好的兜底方法"返回的数据类型需要和Feign请求的保持一致并且继承我们之前定义的Feign接口(这样Feign才能检测到)
首先我们在feign包下面新建fallback包,新建fallback类来继承之前的定义的Feign接口,并且该类需要注册到spring容器中
写好方法内容(我们这里就返回默认数据回去)

然后回到Feign接口,参数栏新增fallback参数,参数内容填我们刚刚写的fallback类,这样当Feign请求失败他就会来调用这个类里面的内容
最后我们来简单配置一下Sentinel
xml里面先导入
然后再在yml配置文件中吧Sentinel的开关打开

这样就算完成了
最后我们来测试一下,启动服务,但是把Product微服务关闭,这样Order微服务就无法连接Product微服务来模拟请求错误
可以看到,返回的内容正是我们再兜底方法中定义的内容



