目录
一、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;
- 日志级别,最好用 basic 或 none;(因为开启日志也需要占用较多资源)
下面以 HttpClient 为例子,说明性能优化的步骤:
(1)引入依赖
- httpClient 已经被 Spring 管理好版本了,不需要指定版本;
(2)修改 application 配置文件
- 将 feign.httpclient.enabled 的属性值改为 true,表明底层使用 httpclient;
5、Feign 最佳实践
最佳实践是指,企业开发过程中,总结设计缺点,得出的一种相对比较好的使用方式。
我个人更喜欢第二种抽取的方式,下面使用这种方式做一个例子。
(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?
参考了很多解决方法,主要是要区别版本问题:
在包含 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、路由的过滤器配置
在这个过程中,过滤器就可对请求和响应做出各种各样的处理,这样往下游传递的数据中,就包含了修改后的信息。
具体有什么样的操作,就要看使用哪个过滤器工厂:
下面我们以添加一个请求头为例子:
(1)修改 application 配置文件
- 给 routes 中的一个路由添加 filters 属性;
- filters 中添加:- AddRequestHeader=[key],[value];
(2)编写 Controller,获取请求头信息
- 既然在 filter 中设置了 header:orderStatus=001;
- 那么请求传递到 Controller 后,一定可以获取到这个头信息;
- 访问 /order/queryOrderById/1,查看控制台输出;
(3)编写默认过滤器 default-filter
- 只需要在 routes 同级下,添加 default-filter 属性;
- 给 default-filter 属性添加:- AddRequestHeader=[key],[value] 等属性值;
- 访问 /order/queryOrderById/1,查看控制台输出;
4、全局过滤器
全局过滤器是:GlobalFilter。
- 全局过滤器的作用:处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样,只是作用范围不同。
- 其中,GatewayFilter 通过配置定义,运行逻辑是固定的。(官方定义)
- 而 GlobalFilter 的运行逻辑需要自己写代码实现。(自定义)
(1)定义实现类
- 创建 AuthorFilter 实现类,实现 GlobalFilter 接口;
- exchange:请求上下文,里面可以获取Request. Response等信息;
- chain:用来把请求委托给下一个过滤器;
- Nono<Void>:返回表示当前过滤器业务结束
(2)定义 filter 方法的运行逻辑
- 添加 @Component;
- 添加 @Order,方便多过滤器时指定顺序;
- 网关中采用的都是基于 WebFlux(响应式编程)的 API,因此使用上与 ServletAPI 不同;
@Order(-1)
@Component
public class AuthorFilter implements GlobalFilter {
@Override
public 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.判断是否等于 admin
if (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。
请求路由后,会将当前路由过滤器和 DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
6、网关的 cors 跨域配置
由于一些页面可能需要从微服务中获取某些数据,此时需要发起 AJAX 请求,那么这就可能属于跨域请求的范畴。(一般我们的 web 页面端口都不会与微服务的相同,所以请求基本上都是跨域请求)
跨域请求不需要在微服务中处理,只需要在网关中处理即可。
跨域问题的 2 个要素:
- 浏览器、服务端:浏览器禁止请求向服务端发起跨域 ajax 请求,请求会被拦截;
- 解决方案:CORS;
(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
Loadbalancer 依赖: