SpringCloud系列 - OpenFeign 远程调用(三)
目录
一、简介
二、与Feign的区别
三、远程调用方案
3.1 项目准备
3.2 方案一:使用RestTemplate
1)固定ip
2)负载均衡
3.3 方案二:OpenFeign⭐
0)提取公共实体类
1)导入依赖
2) @EnableFeignClients
3)编写远程调用客户端
4)测试调用
5)小结
四、第三方API调用
4.1 编写远程调用客户端
4.2 测试调用
4.3 小结
五、OpenFeign进阶 - 日志
5.1 配置文件
5.2 注入bean对象
5.3 测试
六、OpenFeign进阶 - 超时控制
6.1 修改超时配置
6.2 测试
七、OpenFeign进阶 - 重试配置
7.1 OpenFeign提供的重试策略
7.2 自定义重试策略
八、OpenFeign进阶 - 拦截器
8.1 全局请求拦截器
8.2 局部请求拦截器
九、OpenFeign进阶 - Fallback兜底
9.1 编写兜底数据
9.2 引入sentinel
一、简介
Spring Cloud OpenFeign 是 Spring Cloud 提供的一个声明式 HTTP 客户端,用于简化微服务之间的远程调用。
解读:什么是声明式HTTP客户端?
与之相对应的叫做编程式HTTP客户端,例如我们使用Hutool工具的HttpRequest,Apache的HttpClient,以及Sping的RestTemplate等等,这些API在进行远程调用的时候,是通过编写一长串的代码,比如设置请求头、请求方式、传递请求参数、请求、解析等。
而声明式HTTP客户端,则是字面意思,通过接口和注解声明的方式定义远程服务接口,无需手动编写 HTTP 请求代码。
二、与Feign的区别
Feign与OpenFeign的区别
类别 | Feign | OpenFeign |
起源与维护 | 由 Netflix 开发,是 Netflix OSS 的一部分,但自 2019 年起已停止维护 | 由 Spring Cloud 团队维护,作为 Feign 的替代方案,持续更新并深度集成 Spring 生态 |
作用 | Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端。Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口就可以调用注册中心的服务。 | OpenFeign 是 Spring Cloud 在Feign 的基础上支持了 SpringMVC的注解如 @RequesMapping 等等。OpenFeign 的 @FeignClient 可以解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
因此,OpenFeign 是 Feign 的升级版,更适合现代微服务开发。
三、远程调用方案
3.1 项目准备
为了更好的演示远程调用的效果,我们在前面的基础上,继续新建一个模块:service-system。
application.yml
server:port: 9000
spring:application:name: service-systemprofiles:active: devcloud:nacos:server-addr: localhost:8848discovery:namespace: ${spring.profiles.active:public}config:namespace: ${spring.profiles.active:public}---
spring:config:import:- nacos:common.yaml?group=systemactivate:on-profile: dev---
spring:config:import:- nacos:common.yaml?group=systemactivate:on-profile: test---
spring:config:import:- nacos:common.yaml?group=systemactivate:on-profile: prod
提供测试接口
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/getUser")public User getUser() {return new User("何苏三月", "男", 18, "123456@qq.com");}}
启动nacos并运行项目
3.2 方案一:使用RestTemplate
该方案和使用其他三方工具的道理是一样的,如HtppClient,这里以spring的RestTemplate作为演示,其他就不再一一赘述。
1)固定ip
edu服务中:编写接口,远程调用system服务的获取用户接口。
使用前,我们建议是使用前面教程提到的Nacos的服务发现工具NacosClient,可以快速找到目标远程接口的服务地址信息。
@Configuration
public class EduConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
@Resourceprivate DiscoveryClient discoveryClient;@Resourceprivate RestTemplate restTemplate;@GetMapping("/getRemote")public String getRemote(){List<ServiceInstance> instances = discoveryClient.getInstances("service-system");ServiceInstance serviceInstance = instances.get(0);String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/getUser";return restTemplate.getForObject(url, String.class);}
2)负载均衡
引入依赖
由于其它服务后面可能也会用到,所以建议在service父工程下引入
<!-- 负载均衡 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>
编写方法测试
@Resourceprivate DiscoveryClient discoveryClient;@Resourceprivate RestTemplate restTemplate;@Resourceprivate LoadBalancerClient loadBalancerClient;@GetMapping("/getRemoteLoadBalancer")public String getRemoteLoadBalancer(){ServiceInstance instance = loadBalancerClient.choose("service-system");String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/user/getUser";return restTemplate.getForObject(url, String.class) + " 当前服务 " + url;}
3.3 方案二:OpenFeign⭐
0)提取公共实体类
为了方便各个模块进行远程调用,我们建议将所有项目的实体类放在同一个模块下,这样不用每次都要各自新建一遍实体类。
然后在service父工程下,导入model依赖。这样每个service模块都能使用这个model包下的实体类了。
<!-- 引入model --><dependency><groupId>com.hssy</groupId><artifactId>model</artifactId><version>0.0.1</version></dependency>
下面我们可以正式开始了~
1)导入依赖
<!-- 远程调用 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
<!-- 服务发现 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- 配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- 负载均衡 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- 引入model --><dependency><groupId>com.hssy</groupId><artifactId>model</artifactId><version>0.0.1</version></dependency><!-- 远程调用 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2) @EnableFeignClients
开启远程调用客户端
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class EduApplication {public static void main(String[] args) {SpringApplication.run(EduApplication.class, args);}
}
3)编写远程调用客户端
@FeignClient(value = "service-system") // feign客户端
public interface SystemFeignClient {@GetMapping("/user/getUser")public User getUser();
}
4)测试调用
下面,我们就可以写一个接口来测试这个远程调用能否用
@Resourceprivate SystemFeignClient systemFeignClient;@GetMapping("/getRemoteFeign")public User getRemoteFeign(){return systemFeignClient.getUser();}
5)小结
说明:远程调用的操作应该是在调用方完成的,被调用方无需处理,除非被调用方也有调用需求。
步骤 | 说明 |
1 | 导入依赖 spring-cloud-starter-openfeign |
2 | 开启@EnableFeignClients注解 |
3 | 编写远程调用客户端。 说明:
|
4 | 进行调用:在需要调用的地方注入刚才写的远程调用客户端进行使用即可 |
5 | OpenFeign默认以轮询的方式负载均衡 |
四、第三方API调用
如果是第三方的API,OpenFeign也一样能进行调用,而不需要再使用HttpClient这些工具了。
例如:我从某网站找到一个查询天气的API,下面进行测试看看
4.1 编写远程调用客户端
@FeignClient(value = "service-weather", url = "https://route.showapi.com")
public interface WeatherFeignClient {@GetMapping("/9-2")String getWeather(@RequestParam("appKey") String appKey, @RequestParam("area") String area);}
4.2 测试调用
@Resourceprivate WeatherFeignClient weatherFeignClient;@GetMapping("/queryWeatherFeign")public String queryWeatherFeign(){return weatherFeignClient.getWeather("E53C00b070ce4E--------65af35993c", "重庆");}
4.3 小结
第三方接口的调用同样可以使用OpenFeign。使用方法和前一章节讲解的一样。
需要注意的是:在编写远程调用客户端时,@FeignClient注解需要自定义一个服务名称,然后必须使用url属性添加目标接口的域名地址,其余不变。
五、OpenFeign进阶 - 日志
通过OpenFeign进行远程调用时,默认情况下不会输出调用相关的日志信息(如请求路径、参数、响应状态等)。实际上,OpenFeign内置了详细的日志功能,支持记录请求和响应的关键细节,无需开发者手动实现日志逻辑。
开启方式十分简单,首先日志配置文件,然后注入bean即可。
5.1 配置文件
logging:level:com.hssy.feign: debug
5.2 注入bean对象
@Configuration
public class EduConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}
注意这个Logger是feign包下的
5.3 测试
重启发送请求测试一下,发现打印了很多日志信息
六、OpenFeign进阶 - 超时控制
远程调用可能因网络延迟、服务端处理阻塞或资源不足等问题导致响应缓慢甚至完全不可用。若调用方长时间等待无响应(例如超过5秒未返回结果),可能引发线程阻塞、资源耗尽等连锁反应,最终演变为系统雪崩等灾难性后果。
避免这种问题的最简单方式就是引入超时控制。
举个例子
调用方在调用天气服务时,限时等待(如5s)。
- 如果5s内返回了,则能获取到正确结果
- 超时,则中断调用
- 要么返回错误信息
- 要么返回兜底数据(后续结合sentinel细讲)
超时类型
- 连接超时 connectTimeout :建立连接超时了。openfeign默认10 s
- 读取超时 readTimeout:连接建立后,迟迟不返回数据超时了。openfeign默认 60 s
6.1 修改超时配置
为了看起来更清晰,我们单独创建一个application-feign.yml文件,单独配置feign相关信息。
不过要注意的是在application.yml中需要使用spring.profiles.include将文件包含进去。
spring:cloud:openfeign:client:config:# 默认配置dafault:connect-timeout: 5000read-timeout: 20000# 指定服务名称相关配置 service-system:connect-timeout: 3000read-timeout: 5000
6.2 测试
接下来,我们给system模块的获取用户信息接口延迟起来。
重启项目在发起远程调用看看。
发现5s后直接报错,说明默认超时时间修改成功了。
七、OpenFeign进阶 - 重试配置
远程调用超时失败后,还可以进行多次尝试。如果某次成功返回,则结束。如果多次依然失败则结束调用,返回错误。
7.1 OpenFeign提供的重试策略
openfeign默认不重试,但提供了默认重试策略
OpenFeign提供的重试策略
配置该策略
测试
调用system服务中的获取用户接口
我们发现idea控制台打印了五次错误信息后请求才结束,页面才响应500。说明重试生效了。
然后测试远程调用第三方api, 我们把url给改成错误的,模拟请求失败情况
结果发现并没有重试,一次就返回了。
说明我们配置的策略生效了。
7.2 自定义重试策略
注入重试bean,可以传入自定义参数。
@Configuration
public class EduConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}@Beanpublic Retryer retryer() {return new Retryer.Default(1, 2, 2);}
}
配置文件不要设置重试对应的类,这样默认调用任何模块都会按bean配置要求进行重试。
测试看看效果
这样重试两次不行就直接报错了。
其他模块也是一样。
八、OpenFeign进阶 - 拦截器
OpenFeign在发起请求之前,可以对远程请求进行拦截。同样的在响应回数据之前,也可以对响应数据进行拦截。
这里的拦截是发生在远程调用的过程中的,而不是所谓我们前面讲的springmvc中的拦截器。不过实现原理底层有相似之处而已。
如下图:
8.1 全局请求拦截器
实现RequestInterceptor接口即可。
记得加上@Component注解,OpenFeign会去扫描该组件,将它配置上去实现请求拦截功能。
package com.hssy.interceptor;import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;import java.util.UUID;@Component
public class XTokenRequestInterceptor implements RequestInterceptor {/*** 请求拦截器* @param requestTemplate 请求模板*/@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("XTokenRequestInterceptor ......");requestTemplate.header("X-Token", UUID.randomUUID().toString());}
}
然后我们记得将获取用户信息的远程接口睡眠给取消掉,进行测试。
8.2 局部请求拦截器
某些情况下,我们编写的某个拦截器并不需要给所有的远程调用接口都进行请求拦截。
只有特定的接口或服务才需要,那么就不能进行全局拦截,而是局部拦截。
实现方式也很简单,就是不要使用@Component注解。然后在需要拦截的远程调用客户端的@FeignClient注解中配置configuration属性。例如:
@FeignClient(value = "service-system", configuration = XTokenRequestInterceptor.class)
九、OpenFeign进阶 - Fallback兜底
如果远程调用失败,默认会返回错误信息。但是,有些情况下,我们不希望直接看到错误信息,而是及时没有返回结果,也给一个默认值,或者从缓存中拿一些值。
当然,我们也可以在业务中去 try-catch 捕获这个异常,当发生异常时,给它赋一个默认值。
OpenFeign其实也给我们提供了一个fallback兜底功能,不需要我们在业务代码中手动去捕获异常。
9.1 编写兜底数据
具体实现方法如下:
@Component
public class SystemFeignClientFallback implements SystemFeignClient {@Overridepublic User getUser() {return new User("默认用户", "未知", 0, "未知");}
}
正常调用的情况
远程调用失败的情况
把睡眠又加上,这样超时后肯定就失败了,看看能不能返回我们fallback中的兜底数据。
发现并没有返回兜底数据,还是页面报错呀!
还记得前面我们说的吗,要使用兜底显示,还需要使用到sentinel。
下面我们快速整合一下sentinel实现效果。具体sentinel将在下一章讲解,尽情期待!
9.2 引入sentinel
在service父工程下,引入依赖
<!-- 熔断限流 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
开启feign的sentinel功能
再测试,两次尝试获取失败后,就返回了兜底数据!
本节课结束~