目录
一、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(2)定义 filter 方法的运行逻辑
添加 @Component;添加 @Order,方便多过滤器时指定顺序;网关中采用的都是基于 WebFlux(响应式编程)的 API,因此使用上与 ServletAPI 不同; @Order(-1) @Component public class AuthorFilter implements GlobalFilter { @Override public Mono(3)启动 gateway 的 application
访问 /order/queryOrderById/1/author=admin111,先使用错误的 authon 参数值;发现返回了 401; 访问 /order/queryOrderById/1/author=admin,使用正确的 authon 参数值;可以正常访问 5、过滤器链执行顺序请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
过滤器执行顺序:
每一个过滤器都必须指定一个 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;这里参数是必须传递的,因为发起的请求目标地址是要通过网关的,需要鉴权;(1)搭建网关服务时,报错 503
这是因为在 Spring Cloud 2020 版本以后,默认移除了对 Netflix 的依赖,其中就包括 Ribbon,官方默认推荐使用 Spring Cloud Loadbalancer 正式替换 Ribbon,并成为了 Spring Cloud 负载均衡器的唯一实现。
Loadbalancer 依赖: