当前位置: 首页 > news >正文

[SpringCloud] Feign 与 Gateway 简介

目录

一、Feign 简介

1、RestTemplate 远程调用中存在的问题

2、定义和使用 Feign 客户端

3、Feign 自定义配置

4、Feign 性能优化

5、Feign 最佳实践

6、Feign 使用问题汇总

二、Gateway 网关简介

1、搭建网关服务

2、路由断言工厂

3、路由的过滤器配置

4、全局过滤器

5、过滤器链执行顺序

6、网关的 cors 跨域配置

7、Gateway 网关使用问题汇总


一、Feign 简介

Feign 是 Netflix 开发的声明式、模板化的 Http 客户端,Feign 可以帮助我们更快捷、优雅地调用Http API。

  • Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。
  • SpringCloud 对 Feign 进行了增强,使 Feign 支持了 Spring MVC 注解,并整合了 Ribbon 和 Eureka,集成了服务发现和负载均衡,从而让 Feign 的使用更加方便。

1、RestTemplate 远程调用中存在的问题

该远程调用的代码有以下问题:

  • 代码可读性差,编程体验不统一;
  • 参数复杂,URL 难以维护;

2、定义和使用 Feign 客户端

在 SpringCloud 中,使用 Feign 非常简单,创建一个 Interface,并在接口上添加一些注解,代码就完成了。 

(1)引入依赖

  • 给需要远程调用的模块,引入 openfeign 起步依赖;

(2)添加 @EnableFeignClients 注解

  • 给需要远程调用的启动类,添加 @EnableFeignClients 注解;

(3)编写 Feign 客户端

  • 添加 @FeignClient 和 @GetMapping 注解; 

上述注解声明了如下信息:

  • 服务名称:user-service;
  • 请求方式:get;
  • 请求路径:/user/{userId};
  • 请求参数:String userId;
  • 返回值类型:User;

(4)OrderService 远程调用过程

该远程调用过程,就好像平常处理请求的逆过程:

  • OrderService 将 userId 传递给接口中的方法;
  • queryUserById 方法再将参数赋值给请求路径中的占位符 {userId};
  • 然后再通过 get 请求向 UserController 发送请求;
  • 最终获得 User 对象信息;

3、Feign 自定义配置

Spring 虽然帮我们做好了配置,但是也允许我们修改默认配置。

配置 Feign 日志有 2 种方式:

(1)application/bootstrap 配置文件方式

  • 注意:logging.level 属性值需要改为 trace; 

(2)Java 代码方式

4、Feign 性能优化

Feign 底层客户端实现:

  • URLConnection:默认使用,java 原生 jdk,性能较差,不支持连接池;
  • Apache HttpClient:支持连接池;(推荐)
  • OKHttp:支持连接池;(推荐)

对 Feign 的性能优化,最重要的一点就是对底层实现的改变。

因此优化 Feign 的性能主要包括:

  • 使用连接池代替默认的 URLConnection;
  • 日志级别,最好用 basic 或 none;(因为开启日志也需要占用较多资源)

下面以 HttpClient 为例子,说明性能优化的步骤:

(1)引入依赖

  • httpClient 已经被 Spring 管理好版本了,不需要指定版本;

(2)修改 application 配置文件

  • 将 feign.httpclient.enabled 的属性值改为 true,表明底层使用 httpclient;

5、Feign 最佳实践

最佳实践是指,企业开发过程中,总结设计缺点,得出的一种相对比较好的使用方式。

通常会用到 2 种方式,各有利弊,按自己的需求使用:

  • 继承:给消费者的 FeignClient 和提供者的 controller 定义统一的父接口,然后实现接口;
  • 比如 order 模块要调用 user 模块的服务,就定义 UserClient 接口,继承 UserAPI;
  • 缺点:耦合度高;
  • 优点:遵从了契约;

  • 抽取:将 FeignClient 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用;
  • 缺点:消费者可能会引入很多用不到的 api;
  • 优点:耦合度低,层次分明;

我个人更喜欢第二种抽取的方式,下面使用这种方式做一个例子。

(1)创建 feign-api 模块

  • 在 feign-api 模块中引入 feign 的起步依赖;

  • 将消费者模块中对提供者模块所需的 Client、Pojo,以及 Config 类都移动到 feign-api 中;

(2)引入 feign-api 模块

  • 在消费者 order-service 模块中,引入 feign-api 模块;

(3)消除报错

  • 将 order-service 模块内原来依赖 pojo、config、client 的类,修改为 feign-api 模块内的类;

  • 由于 order-service 模块的 application 启动类无法扫描到 feign-api 模块下的 Client 的 Bean 对象,因此会出现如下报错:

  • 需要在 @EnableFeignClients 中加入 clients 属性;

6、Feign 使用问题汇总

(1)Did you forget to include spring-cloud-starter-loadbalancer?

出错原因: 

  • SpringCloud Feign 在 Hoxton.M2 RELEASED 版本之后不再使用 Ribbon,而是使用 spring-cloud-loadbalancer,所以在不引入 spring-cloud-loadbalancer 的情况下会报错。

参考了很多解决方法,主要是要区别版本问题:

包含 RELEASE 关键字的版本中,只有下面这样操作才能使用:

  • 首先排除 ribbon 的起步依赖,排除位置在消费者依赖的服务提供者的 dependency;
  • 添加 loadBalancer 依赖;

而在 2021 及更新版本中(也就是没有 RELEASE 关键字的版本),只需要如下操作:

  • 引入 loadBalancer 依赖;
  • 不需要排除 ribbon 的起步依赖;

二、Gateway 网关简介

为什么需要网关?

  • 如果没有网关,那么所有人都可以访问我们所有的服务,但是有很多服务其实是不能对外公开的。
  • 需要使用网关进行身份验证,通过后才能访问敏感服务。
  • 一切请求需要先通过网关,才能到微服务。

因此,网关有如下功能:

  • 身份认证和权限校验;
  • 服务路由、负载均衡;(也就是能知道将请求送到哪个微服务)
  • 限制请求流量;

SpringCloud 提供了 2 个组件实现网关功能:

  • gateway;(新版本)
  • zuul;(早期版本)

Zuul 是基于 Servlet 的实现,属于阻塞式编程。

而 SpringCloudGateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备更好的性能。

1、搭建网关服务

(1)创建 gateway 模块

  • 创建一个新的模块专门用于网关:gateway-module,需要 SpringBoot 启动类;
  • 引入 SpringCloudGateway 起步依赖;
  • 引入 Nacos 服务发现依赖;

(2)编写路由配置以及 Nacos 地址

  • gateway 中的 routes 属性之前的配置,目的是为了让 gateway 将服务注册到 nacos;
  • gateway 中的 routes 属性之后的配置,指明了可以使用的 service 路由;
  • id:表示这个路由的名字,与其他属性无关;
  • uri:表示路由目标地址,其中 lb 表示负载均衡,后面跟着服务的 name;
  • predicates:指定路由规则,符合规则就可以放行;

(3)测试网关功能

  • 启动 gateway 的 application;
  • 发起请求:localhost:10010/order/queryOrderById/1;
  • 这个请求中,order-service 模块会调用 user-service 的服务;

  • 访问失败,说明我们的网关起作用了,因为我们没有为网关配置 order-service 的路由;

  • 添加对 order-service 的服务的路由后,就可以从 gateway 访问 order 的服务了;

2、路由断言工厂

路由断言工厂 Route Predicate Factory,作用是:

  • 我们在配置文件中写的 predicates 断言规则只是字符串,这些字符串会被 Predicate Factory 读取并解析为路由判断的条件。
  • 每种规则都有各自的断言工厂去解析,比如 Path 有 PathRoutePredicateFactory 断言工厂;

下面以 After 举一个例子:

(1)添加断言规则 After

  • After 断言规则,规定在这个时间之后,才能进行访问; 

(2)访问 order 的服务

  • 发送请求 /order/queryOrderById/1;
  • 理向情况应该是:无法访问;

  • 修改断言规则的时间为:2020-12-31,重启 gateway 模块;

  • 再次发起请求,就可以访问了;

3、路由的过滤器配置

路由过滤器是:GatewayFilter。

  • GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求微服务返回的响应做处理。

在这个过程中,过滤器就可对请求和响应做出各种各样的处理,这样往下游传递的数据中,就包含了修改后的信息。

具体有什么样的操作,就要看使用哪个过滤器工厂:

下面我们以添加一个请求头为例子:

(1)修改 application 配置文件

  • 给 routes 中的一个路由添加 filters 属性;
  • filters 中添加:- AddRequestHeader=[key],[value];

(2)编写 Controller,获取请求头信息

  • 既然在 filter 中设置了 header:orderStatus=001;
  • 那么请求传递到 Controller 后,一定可以获取到这个头信息;

  • 访问 /order/queryOrderById/1,查看控制台输出;

(3)编写默认过滤器 default-filter

如果我们想为所有的请求都添加 header 信息,也不需要给每一个路由都写上 filters。

  • 只需要在 routes 同级下,添加 default-filter 属性;
  • 给 default-filter 属性添加:- AddRequestHeader=[key],[value] 等属性值;

  • 访问 /order/queryOrderById/1,查看控制台输出;

4、全局过滤器

全局过滤器是:GlobalFilter。

  • 全局过滤器的作用:处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样,只是作用范围不同。
  • 其中,GatewayFilter 通过配置定义,运行逻辑是固定的。(官方定义)
  • 而 GlobalFilter 的运行逻辑需要自己写代码实现。(自定义)

需求

定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 参数中是否有 authorization;
  • authorization 参数值是否为 admin;
  • 如果同时满足则放行,否则拦截;

(1)定义实现类

  • 创建 AuthorFilter 实现类,实现 GlobalFilter 接口;
  • exchange:请求上下文,里面可以获取Request. Response等信息;
  • chain:用来把请求委托给下一个过滤器;
  •  Nono<Void>:返回表示当前过滤器业务结束

(2)定义 filter 方法的运行逻辑

  • 添加 @Component;
  • 添加 @Order,方便多过滤器时指定顺序;
  • 网关中采用的都是基于 WebFlux(响应式编程)的 API,因此使用上与 ServletAPI 不同;
@Order(-1)
@Component
public class AuthorFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求参数ServerHttpRequest request = exchange.getRequest();MultiValueMap<String, String> params = request.getQueryParams();// 2.获取请求参数中的 author 参数值String author = params.getFirst("author");// 3.判断是否等于 adminif (author.equalsIgnoreCase("admin")) {// 放行return chain.filter(exchange);}// 4.不相等,获取响应ServerHttpResponse response = exchange.getResponse();// 4.1.设置状态码,401表示未登录response.setStatusCode(HttpStatus.UNAUTHORIZED);// 4.2.拦截请求return response.setComplete();}
}

(3)启动 gateway 的 application

  • 访问 /order/queryOrderById/1/author=admin111,先使用错误的 authon 参数值;
  • 发现返回了 401;

  • 访问 /order/queryOrderById/1/author=admin,使用正确的 authon 参数值;
  • 可以正常访问

5、过滤器链执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。

请求路由后,会将当前路由过滤器DefaultFilterGlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

过滤器执行顺序:

  • 每一个过滤器都必须指定一个 int 类型的 order 值:order 值越小,优先级越高,执行顺序越靠前;
  • GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,由我们自己指定;
  • 路由过滤器和 defaultFilter 的 order 由 Spring 官方指定,默认是按照声明顺序从 1 递增;
  • 当过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行;

6、网关的 cors 跨域配置

由于一些页面可能需要从微服务中获取某些数据,此时需要发起 AJAX 请求,那么这就可能属于跨域请求的范畴。(一般我们的 web 页面端口都不会与微服务的相同,所以请求基本上都是跨域请求)

跨域请求不需要在微服务中处理,只需要在网关中处理即可。

跨域:域名不一致就是跨域,主要包括:

  • 域名不同:www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com;
  • 域名相同,端口不同:localhost:8080 和 localhost:8081

跨域问题的 2 个要素:

  • 浏览器、服务端:浏览器禁止请求向服务端发起跨域 ajax 请求,请求会被拦截;
  • 解决方案:CORS;

需求:

  • 使用 localhost:8090 发起请求,8090 发送的是 AJAX 的 GET 请求。
  • 8090 发起的 AJAX 请求需要通过网关,在 AJAX 的目标地址中要用 10010 端口;

(1)编写 gateway 的配置文件

  • 各个属性的作用都写在注释中了;

(2)端口 8090 的 AJAX 请求

  • 这里使用 JQuery 发起 AJAX 请求,传递参数为 author=admin;
  • 这里参数是必须传递的,因为发起的请求目标地址是要通过网关的,需要鉴权;
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="JQuery-3.7.0.js"></script><script type="text/javascript">$(function() {$("#ajaxButton").click(function() {$.ajax({url:"http://localhost:10010/order/queryOrderById/1",data: {author : "admin"},type: "get",dataType: "json",success: function(data) {console.log(data);}});});});</script>
</head>
<body><a href="http://localhost:10010/order/queryOrderById/1?author=admin"> 发起普通GET请求 </a><input id="ajaxButton" type="button" value="发起 ajax 请求"/>
</body>
  • 发起请求,可以在控制台看到返回的数据;

  • 而如果我们用 <a> 来请求,是会跳转到 10010 得页面的,这就不算跨域请求了;

7、Gateway 网关使用问题汇总

(1)搭建网关服务时,报错 503

这是因为在 Spring Cloud 2020 版本以后,默认移除了对 Netflix 的依赖,其中就包括 Ribbon,官方默认推荐使用 Spring Cloud Loadbalancer 正式替换 Ribbon,并成为了 Spring Cloud 负载均衡器的唯一实现。

Loadbalancer 依赖:

相关文章:

  • 2G-GCN:Multi-person HOI Recognition in Videos
  • 2023全新小程序广告流量主奖励发放系统源码 流量变现系统
  • AI绘画 | stable-diffusion-web-ui的基本操作
  • 微信小程序:两层循环的练习,两层循环显示循环图片大图(大图显示、多层循环)
  • 基础(一):
  • postman做接口测试
  • 从项目开始学习Vue——01
  • XSAN数据恢复-存储空间架构迁移时误格式化存储系统的XSAN数据恢复案例
  • 阿里在盘古云存储系统中部署RDMA的经验谈
  • 服务器的管理口和业务口
  • 云安全-云原生k8s攻击点(8080,6443,10250未授权攻击点)
  • 交换配置命令
  • 数组反转(LeetCode)
  • 【WPF系列】- XAML语法规范
  • Qt Concurrent框架详解(QFuture、QFutureWatcher)
  • [deviceone开发]-do_Webview的基本示例
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • angular2 简述
  • angular2开源库收集
  • Computed property XXX was assigned to but it has no setter
  • Java到底能干嘛?
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Vue2 SSR 的优化之旅
  • 基于axios的vue插件,让http请求更简单
  • 设计模式走一遍---观察者模式
  • 学习笔记:对象,原型和继承(1)
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #单片机(TB6600驱动42步进电机)
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (04)odoo视图操作
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (算法)N皇后问题
  • (一)SpringBoot3---尚硅谷总结
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .NET Compact Framework 3.5 支持 WCF 的子集
  • .NET Framework杂记
  • .NET处理HTTP请求
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • /usr/bin/env: node: No such file or directory
  • @Responsebody与@RequestBody
  • [ C++ ] STL---string类的模拟实现
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [202209]mysql8.0 双主集群搭建 亲测可用
  • [AIGC] 如何建立和优化你的工作流?
  • [Bugku]密码???[writeup]
  • [BZOJ5250][九省联考2018]秘密袭击(DP)