一、网关简介
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去用。
这样的架构,会存在着诸多的问题:
每个业务都会需要鉴权、限流、权限校验、跨域等逻辑,如果每个业务都各自为战。自己造轮子实现一遍,会很蛋疼,完全可以抽出来,放到一个统一的地方去做。如果业务量比较简单的话,这种方式前期不会有什么问题,但随着业务越来越复杂,比如淘宝、亚马逊打开一个页面可能会涉及到数百个微服务协同工作,如果每一个微辰务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效。后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务,随着业务变的越来越复杂,后期需要进行拆分成多个微服务这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼。
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户说提供统一服务,一些与业务本身功能天关的公共逻辑可以在这里实现。诸如认证、鉴权、监控、路由转发等等,添加上API网关之后,系统的架构图变成了如下所示:
1、什么是Spring Cloud Gateway
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Sping Cloud cGateway是Sping Clou官方推出的第二代网关框架,定位于取代NeificZul。
相比Zul来说,Spring Cloud Gateway提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway是由WebFlux + Netty + Reactor实现的响应式的API网关。它不能在传统的servlet容器中工作,也不能构建成war包。
Sping Cloud Gateway旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Fiter的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
官网文档: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
Spring Cloud Gateway 功能特征
基于Spring Framework 5, Project Reactor和 Spring Boot 2.0进行构建;动态路由:能够匹配任何请求属性;支持路径重写;集成 Spring Cloud 服务发现功能(Nacos、Eruka) ;可集成流控降级功能(Sentinel、Hystrix) ;可以对路由指定易于编写的Predicate(断言)和Filter (过滤器); 2、核心概念
路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言(predicates)
Java3中的断言函数,SpringCloud Gateway中的断言函数类型是Sping5.0框架中的SeverieExctange。断言函数允许开发者去定义匹酷t request中的任何信息,比加如请求头和参数等。
过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理
3、工作原理
Spring Cloud Gateway的工作原理跟Zuul的差不多,最大的区别就是Gateway的Filter只有pre和post 两种。
客户端向Spring Cloud Gateway发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关Web处理程序,此时处理程序运行特定的请求过滤器链。过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后执行逻辑。所有pre过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行post过滤器逻辑。
二、Spring Cloud Gateway快速开始 1、环境搭建【基本环境搭建-实现路由】
注意:会和spring-webmvc的依赖冲突,需要排除spring-webmvc
org.springframework.cloudspring-cloud-starter-gateway
设置配置文件
server: port: 8088 spring: application: name: api-gateway # gateway的配置 cloud: gateway: routes: - id: order_route #路由的唯一标识,路由到order uri: http://localhost:8020 #需要转发的地址 #断言规则 用于路由规则的匹配 predicates: - Path=// # http://localhost:8088/order-serve/order/add 路由到↓ # http://localhost:8020/order-serve/order/add filters: - StripPrefix=1 # 转发之前,去掉第一次的路径 # http://localhost:8020/order/add #- id: stock_route
访问:http://localhost:8088/order-serve/order/add
2、集成Nacos
com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discovery server: port: 8088 spring: application: name: api-gateway # gateway的配置 cloud: gateway: routes: - id: order_route #路由的唯一标识,路由到order uri: lb://order-service # 需要转发的地址 lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 order-service服务名 #断言规则 用于路由规则的匹配 predicates: - Path=/order-serve/** # http://localhost:8088/order-serve/order/add 路由到↓ # http://localhost:8020/order-serve/order/add filters: - StripPrefix=1 # 转发之前,去掉第一次的路径 # http://localhost:8020/order/add #- id: stock_route # 配置Nacos nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos
重新运行并访问:
http://localhost:8088/order-service/order/add
设置约定集成nacos
server: port: 8088 spring: application: name: api-gateway cloud: # gateway的配置 gateway: discovery: locator: enabled: true #是否启动自动识别nacos服务 #配置Nacos nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos
重新启动项目
访问服务名对应的地址:http://localhost:8088/order-service/order/add
重新启动项目
访问:http://localhost:8088/order/add
调整时间
重新运行项目
http://localhost:8088/order/add
3、路由断言工厂(Route Predicate Factories)配置
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
作用:当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发,如果匹配失败就返回404
类型:内置,自定义
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:
基于Datetime类型的断工厂此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数, 判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收- 个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
- After=2023-10-19T09:07:00.660+08:00[Asia/Shanghai]
基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中 - RemoteAddr=192.168.1.1/24
基于Cookie的断言工厂 CookieRoutePredicateFactory:接收两个参数, cookie 名字和一个正则表达式。判断请求cookie是否具有给定名称且值与正则表达式匹配。 -Cookie=chocolate, ch.
基于Header的断言工厂HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。
- Header=X-Request-Id, \d+
重新启动项目
利用API工具发送请求
把Headers当中的请求参数取消重新发起请求,会报错
基于Host的断言工厂HostRoutePredicateFactory:接收一个参数, 主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
基于Method请求方法的断言工厂MethodRoutePredicateFactory:接收一个参数, 判断请求类型是否跟指定的类型匹配。
重新启动测试
通过GET发送请求
通过POST发起请求
基于Query请求参数的断言工厂 设置必须传递参数为name的参数 - Query=name
重新启动发起请求,没有设置name参数报错
设置name参数
设置指定参数
- Query=name,xushu|zhuge
设置name的参数只能是xushu或者zhuge否则断言失败
发送请求报错
将参数改为xushu请求成功,改为zhuge也请求成功
基于路由权重的断言工厂WeightRoutePredicateFactory:接收-个[组名 权重],然后对于同-一个组内的路由按照权重转发
routes: -id: weight_ route1 uri: host1 predicates: -Path=/ product/** -Weight=group3,1 -id: weight_ route2 uri: host2 predicates: - Path=/ product/** -Weight= group3, 9 4、自定义路由断言工厂
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑。
在apply方法中可以通过exchange. getRequest()倒ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息。
1、必须是Spring组件bean
2、类必须加上RoutePredicateFactory作为结尾
3、必须继承AbstractRoutePredicateFactory
4、必须声明静态的内部类 声明属性来接受配置文件当中配置的断言信息
5、需要结合shortcutFieldOrder进行绑定
6、通过apply方法进行逻辑判断 true 就是匹配成功 false就是匹配失败
注意:命名需要以RoutePredicateFactory结尾
@Component public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory
{ public CheckAuthRoutePredicateFactory() { super(CheckAuthRoutePredicateFactory.Config.class); } @Override public List shortcutFieldOrder() { return Arrays.asList("name"); } @Override public Predicate apply(CheckAuthRoutePredicateFactory.Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { if(config.getName().equals("xushu")){ return true; } return false; } }; } // 用于接收配置文件中 断言的信息 @Validated public static class Config { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } } 重新启动项目
访问:http://localhost:8088/order/add
修改对应的断言名称
查询启动
http://localhost:8088/order/add
5、过滤器工厂( GatewayFilter Factories)配置
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayilter-factories
5.1、添加请求头参数 filters: - AddRequestHeader=X-Request-color,red @RequestMapping("/header") public String header(@RequestHeader("X-Request-color") String color){ return color; }
重新启动两个项目
http://localhost:8088/order/header
5.2、添加请求参数 - AddRequestParameter=color,blue @RequestMapping("/parameter") public String parameter(@RequestParam("color") String color){ return color; } http://localhost:8088/order/parameter
5.3、为匹配的路由统一添加前缀 servlet: context-path: /mall-order - PrefixPath=/mall-order #添加前缀对应微服务需要配置context-path 重新启动两个项目
访问:http://localhost:8088/order/add
现在访问:http://localhost:8020/mall-order/order/add
然而访问8082必须携带前缀
http://localhost:8020/mall-order/order/add
5.4、配置重定向 - RedirectTo=302, https://www.baidu.com
访问:http://localhost:8088/order/add
重定向到了百度
5.5、自定义状态码 - SetStatus= 404
访问:http://localhost:8088/order/add
虽然访问成功了,但是返回的状态码为404
6、自定义过滤器工厂继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
创建CheckAuthGatewayFilterFactory
/*** */ @Component public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory { public CheckAuthGatewayFilterFactory() { super(Config.class); } @Override public List shortcutFieldOrder() { return Arrays.asList("value"); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String name=exchange.getRequest().getQueryParams().getFirst("name"); if(StringUtils.isNotBlank(name)){ if(config.getValue().equals(name)){ return chain.filter(exchange); } else { // 返回404 exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); return exchange.getResponse().setComplete(); } } // 正常请求 return chain.filter(exchange); } }; } public static class Config { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } } } - CheckAuth=xushu 7、全局过滤器(Global Filters)
局部过滤器和全局过滤器的区别
局部:针对某个路由,需要在路由中进行配置
全局:针对所有路由请求,一旦定义就会投入使用
GlobalFilter接口和GatewayFilter有一样的接口定义, 只不过,GlobalFilter 会作用于所有路由。
@Component public class LogFilter implements GlobalFilter { Logger log= LoggerFactory.getLogger(this.getClass()); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info(exchange.getRequest().getPath().value()); return chain.filter(exchange); } } 重新启动:http://localhost:8088/order/add?name=xushu
8、Reactor Netty访问日志 要启用Reactor Netty访问日志,请设置
-Dreactor.netty.http.server.accessLogEnabled=true
它必须是Java系统属性,而不是Spring Boot属性。
您可以将日志记录系统配置为具有单独的访问日志文件。以下示例创建一个Logback配置:
access_log.log%msg%n 8.1、Gateway跨域配置(CORS Configuration) 通过ym|配置的方式
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
globalcors: cors-configurations: '[/**]': # 允许跨域访问的资源 allowedOrigins:"*"#跨域允许的来源 例如:www.smsm.com allowedMethods: - GET - POST - PUT 模拟跨域请求,设置发起请求的页面
Title
访问网页
allowedOrigins:"localhost:8088"#跨域允许的来源 例如:www.smsm.com
再次访问访问网页(抛出跨域异常)
设置*运行所有的来源访问
Spring自带的跨域方式
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); //允许运行method config.addAllowedOrigin("*"); //允许的来源 config.addAllowedHeader("*"); //允许的请求头参数 // 允许访问的资源 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**",config); return new CorsWebFilter(source); } }