1.Gateway是在Spring生态系统之上构建的API网关服务,基于Spring,Spring Boot和Project Reactor等技术。
2.Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
鉴权
流量控制
熔断
日志监控
反向代理
Gateway和Zuul区别
1.SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul
2.SpringCloud Gateway是基于Spring WebFlux框架实现的
3.Spring WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,提升了网关性能
Spring Cloud Gateway基于Sping Framework(支持Spring WebFlux),Project Reactor和Spring Boot进行构建,具有如下特性:
动态路由
可以对路由指定Predicate(断言)和Filter(过滤器)
集成Hystrix的断路器功能
集成Spring Cloud服务发现功能
请求限流功能
支持路径重写
核心组件
一句话:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为tue则匹配该路由
一句话:对HTTP请求中的所有内容(例如请求头或请求参数)进行匹配,如果请求与断言相匹配则进行路由
简单举例,比如配置路径,-Path=member/get/**
#断言,路径相匹配的进行路由转发,如果Http请求的路径不匹配,则不进行路由转发
Filter过滤
1、一句话:使用过滤器,可以在请求被路由前或者之后对请求进行处理
2、你可以理解成,在对Http请求断言匹配成功后,可以通过网关的过滤机制,对Http请求处理
How It Works工作机制(文档里面有)
1.客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler.
2.Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
3.过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。
4.Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
5.在"Post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
一句话说:路由转发+执行过滤器链
Routing路由
搭建Gateway微服务-应用实例
代码实现
- 参考member-service-consumer-80创建e-commerce-gateway-20000(具体步骤参考以前)
new/module/maven/e-commerce-gateway-20000
- 修改pom.xml,部分内容可以从member-service-consumer-80的pom.xml拷贝
1.不要引入spring-boot-starter-web和spring-boot-starter-actuator,否则会出现冲突
- 因为gateway是一个服务网关,不需要web...这些依赖,引入还会导致无法启动
<dependencies>
<!--引入网关场景启动器:gateway-starter;使用版本仲裁-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引入eureka-client场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--1.不要引入spring-boot-starter-web和spring-boot-starter-actuator,否则会出现冲突-->
<!--2.因为gateway是一个服务网关,不需要web...这些依赖,引入还会导致无法启动-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--给当前项目自己用-->
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.stein.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
- 创建yaml配置文件
- e-commerce-gateway-20000/src/main/resources 右键 new/file 输入文件名application.yml
- eureka的配置参考之前consumer-80的
#提供服务的端口
server:
port: 20000
spring:
application:
# 模块名:
name: e-commerce-gateway-20000
cloud:
gateway:
routes:
- id: member_route01 #路由的id,程序员自己配置,要求唯一
#url=uri+Path
#uri可以是非本地网络,如www.baidu.com
uri: http://localhost:10000
#断言,匹配不成功则返回404
predicates:
- Path=/member/query/** #这儿特别容易出错。-后面的空格;缩进位置等
#仿照query,复制下来,修改下id和Path,然后测试
- id: member_route02 #路由的id,程序员自己配置,要求唯一
#url=uri+Path
#uri可以是非本地网络,如www.baidu.com
uri: http://localhost:10000
#断言,匹配不成功则返回404
predicates:
- Path=/member/save #这儿特别容易出错。-后面的空格;缩进位置等
# 配置Eureka-Client端
eureka:
instance:
hostname: e-commerce-service #先这样配置,后面再改
client:
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
register-with-eureka: true #貌似默认就是true
fetch-registry: true #貌似默认
- 创建主启动类:java/com/stein/springcloud/GatewayApplication.class
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication20000 {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication20000.class,args);
}
}
- 测试
启动e-commerce-eureka-server-9001
启动member-service-provider-10000
启动e-commerce-gateway-20000
浏览器:
- http://eureka9001.com:9001/ ;通过页面查看9001是否正常运行
- (通过网关访问)http://localhost:20000/member/query/1
Postman测试添加(走网关)
- 在使用postman时,需要使用json格式发送数据,否则会报400错误
二说Gateway路由配置
除了上面那样在yaml中配置(常用方式),还可以通过编写配置类来注入(了解即可)。
文档Example26.Application.java
创建位置:com.stein.springcloud.gatewayconfig
踩坑记录:这个配置类一定要放在springcloud目录下面,之前创建的时候没注意,放到了springcloud的上一层,导致报错:Could not autowire.No beans of 'RouteLocatorBuilder'type found。排查半天。
原因应当是对@Bean的扫描范围导致的。
@Configuration
public class GatewayConfiguration {
@Bean
public RouteLocator route04(RouteLocatorBuilder builder){
return builder.routes().route("member_route04", r->r.path("/member/query/**")
.uri("http://localhost:10000")).build();
}
@Bean
public RouteLocator route05(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
return routes.route("member_route05",r->r.path("/member/add/**")
.uri("http://localhost:10000")).build();
}
}
配置要么使用配置类,要么使用yaml,不要两者都弄,不然很混乱。建议yaml,简洁易懂。
动态路由
将之前的服务,改为由eureka动态的提供。
- 修改uri
- 启用DiscoveryClient服务发现,默认开启。此处显式调用。
cloud:
gateway:
discovery:
locator:
#Flag that enables DiscoveryClient gateway integration
#启用DiscoveryClient服务发现。没有配置也能使用。
enabled: true
routes:
- id: member_route01 #路由的id,程序员自己配置,要求唯一
#url=uri+Path
#uri可以是非本地网络,如www.baidu.com
#uri: http://localhost:10000;改为负载均衡。注意服务名要小写
uri: lb://member-service-provider
注意事项和细节
配置好动态路由后Gateway会根据注册中心上微服务名,为请求创建动态路由,实现动态路由功能
使用的lb协议支持负载均衡-轮询算法
配置自己的负载均衡算法
- 改为随机均衡算法。创建位置:com.stein.springcloud.gatewayconfig
@Configuration
public class RibbonRule {
@Bean
public IRule turnRule(){
return new RandomRule();
}
}
Predicate/断言
一句话:
Predicate就是一组匹配规则,当请求匹配成功就执行对应的Route,匹配失败,放弃处理/转发
Route Predicate Factories 文档。学到这儿,突然有种开窍的感觉。需要结合文档来学习。老师讲的课是线性的,也不便于复习。但是一个文档,就是一本使用手册,很有条理性,便于总结和查询,以前都没发现文档的重要性。
- Spring Cloud Gateway包括许多内置的Route Predicate.工厂,所有这些Predicate都与HTTP请求的不同属性匹配,可以组合使用,
- Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route.
- 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,组合关系是and
按时间断言。以下3个都是按照时间来断言的。
- After Route Predicate
- Before Route Predicate
- Between Route Predicate
可以使用这样的方法来获取本地的时间格式
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
可以得到中国区的格式:2023-04-21T16:18:23.116+08:00[Asia/Shanghai]
使用方式如下:
spring:
cloud:
gateway:
routes:
- id: member_route01
uri: lb://member-service-provider
predicates:
- Path=/member/query/** #这儿特别容易出错。-后面的空格;缩进位置等
#需要注意你是配置在哪个predicates里面的
- After=2023-04-22T16:18:23.116+08:00[Asia/Shanghai]
其余Before和Between的格式:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
The Cookie Route Predicate Factory
- Cookie=user, stein
关键字key(user),值value(stein)可以是regular express正则表达式
- 测试使用postman
使用Get方法,Headers里面添加Cookie,user=stein,然后在地址栏进行查询。如果一致则断言成功;否则404.
The Header Route Predicate Factory
The Host Route Predicate Factory
- Host= **.stein.**
测试:
方法一:使用postman
在Headers里面添加属性Host,值为www.stein.com之类的符合host条件的即可
方法二:通过host文件建立映射
找到C:\Windows\System32\drivers\etc\hosts文件,添加一条映射,
127.0.0.1 www.stein.com ,使用www.stein.com访问网页,也可正常登录
5. The Method Route Predicate Factory
- Method=GET,POST
只允许指定的方式访问。用逗号分隔,表示or或者
6. The Path Route Predicate Factory
- Path=/member/query/**,/member/add
支持多路径,可以将多个路径合并,用逗号“,”分隔,表示or或者
7. The Query Route Predicate Factory
- Query=email, [\w-]+@([A-Za-z0-9]+\.)+[A-z]+
支持正则表达式。
测试:注意这儿的格式,“?”添加内容key=value
地址栏输入:http://localhost:20000/member/query/10?email=stein@163.com 查看是否正确
8. The RemoteAddr Route Predicate Factory
- RemoteAddr=127.0.0.1/24
这儿的地址是用来限制请求访问的主机的ip的,而不是提供服务的服务器的。
其次后面的/24表示子网掩码(subnet mask)。而不是说0~24的。老韩这儿不严谨。。
测试:
浏览器地址栏输入:http://localhost:20000/member/query/10 发现不能进入
换成:http://127.0.0.1:20000/member/query/10 就可以成功
然后尝试改了下hosts文件,还是不能成功,不知道是哪儿出了问题。。求解答
9. The Weight Route Predicate Factory
- Weight=group1, 8
在多个routes中使用。group表示分组,数字相同代表同一组。逗号“,”后面的8代表分配服务的权重,表示它要承担80%的负载。
Filter/过滤器
分类:
GatewayFilter (31种)属于内置的
GlobalFilter 第三方的,更方便,用的比较多
需求:如果请求参数user=stein,pwd=123456则放行,否则不能通过验证
创建文件夹:com.stein.springcloud.filter
@Component //注入到容器中
public class CustomerGatewayFilter implements GlobalFilter, Ordered {
//过滤的核心业务方法。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("------CustomerGatewayFilter被执行------");
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
String user = queryParams.getFirst("user");
String pwd = queryParams.getFirst("pwd");
if(!("stein".equals(user) && "123456".equals(pwd))){
//设置回应
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
//账户正确,则通过过滤链放行
return chain.filter(exchange);
}
//用于设置优先级,数字越小优先级越高
@Override
public int getOrder() {
return 0;
}
}
测试:
浏览器输入:http://localhost:20000/member/query/2?user=stein&pwd=123456 进行测试,正确则进行访问,错误报406错误,自己设置的NOT_ACCEPTABLE就是406错误。
注意:
之前Predicate里面设置的断言,不需要的就注销了,以免在断言层面阻断了,就看不到filter的效果。