1.概述
Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
为啥改造使用网关
前提条件:
- Consul:版本1.5.0。
- Spring boot:版本2.1.5。
- Spring cloud:版本Greenwich.SR1。
- Redis:版本5.0.5。
三大核心概念
- 1、Route(路由) 路由是由构建万股干的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
- 2、Predicate(断言) 参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求与断言相匹配则进行路由
- 3、Filter(过滤) 指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由钱或者而之后对请求进行修改
gateway负载特点
- 服务实例均衡分配:微服务负载均衡确保来自客户端的请求均匀分布到不同的服务实例上,以防止某些实例过载而其他实例处于空闲状态。
- 多种负载均衡策略:微服务负载均衡可以采用不同的策略,如轮询、随机、最少连接等,以选择目标服务实例。每种策略有其用途,根据具体情况选择合适的策略。
- 健康检查:负载均衡器定期检查每个服务实例的健康状态,以确定哪些实例可以接收请求。如果某个实例不健康或不可用,负载均衡器将停止将请求路由到该实例。
- 自动扩展:微服务系统的负载均衡应该支持自动扩展。当系统负载增加时,可以自动添加新的服务实例以处理更多请求。这有助于应对流量波动和系统的横向扩展。
- 故障转移:如果某个服务实例出现故障,负载均衡器应该自动将流量转移到其他健康的实例,以确保系统的可用性。
- 会话粘附:在某些情况下,需要确保同一客户端的多个请求被路由到同一服务实例,以维护会话一致性。这称为会话粘附,一些负载均衡器支持此功能。
网关作用
- 路由功能:网关可以根据目标地址的不同,选择最佳的路径将数据包从源网络路由到目标网络。它通过维护路由表来确定数据包的转发方向,并选择最优的路径。
- 安全控制(统一认证授权):网关可以实施网络安全策略,对进出的数据包进行检查和过滤。它可以验证和授2权来自源网络的数据包,并阻止未经授权的访问。防火墙是一种常见的网关设备,用于过滤和保护网络免受恶意攻击和未经授权的访问。
- 协议转换:不同网络使用不同的通信协议,网关可以进行协议转换,使得不同网络的设备可以互相通信。例如,例如将 HTTPS 协议转换成 HTTP 协议。
- 网络地址转换(NAT):网关还可以执行网络地址转换,将内部网络使用的私有IP 地址转换为外部网络使用的公共 IP 地址,以实现多台计算机共享一个公共 IP 地址出去上网。
依赖及配置代码展示
@Bean
@Order(-1) // 确保它比默认的错误处理器优先级高
public ErrorWebExceptionHandler myExceptionHandler() {
return new JsonErrorWebExceptionHandler();
}
public class JsonErrorWebExceptionHandler implements ErrorWebExceptionHandler {
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
// 设置响应体等
return exchange.getResponse().writeWith(Mono.just(...));
}
}
//跨域配置
@Configuration
public class CorsConfig {
private static final String MAX_AGE = "18000L";
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
// 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders(); // 获取请求头
ServerHttpResponse response = ctx.getResponse(); // 获取响应对象
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); // 获取请求方式对象
HttpHeaders headers = response.getHeaders(); // 获取响应头
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins)
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods)
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 允许在请求中携带cookie(相当于yml中的allowCredentials)
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); // 允许在请求中携带的头信息(相当于yml中的allowedHeaders)
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE); // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge)
if (request.getMethod() == HttpMethod.OPTIONS) { // 直接给option请求反回结果
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx); // 不是option请求则放行
};
}
}
//创建启动类
package com.atguigu.ceshi.apigateway;
@SpringBootApplication
@EnableDiscoveryClient # 加上这个
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
// pom.xml
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
//application.yml
server:
port: 8888
spring:
profiles:
active: dev # 环境设置
application:
name: cloud-gateway #服务名
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_route #payment_route 路由的id,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
#cloud-payment-service为eureka注册的服务名 lb是url的协议,表示启用Gateway的负载均衡功能
uri: lb://cloud-payment-service #微服务提供服务的路由地址()
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #payment_route2 路由的id,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
@Configuration
public class GateWayConfig {
/**
* 配置了一个Id为route-name的路由规则,方访问地址http://localhost:8888
* /guonei是会自动转发到地址
*/
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_xwb", r -> r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
return routes.build();
}
/**
* Gateway的Filter过滤连
* 总的全局过滤器,档在所有微服务前面,进行校验
*/
package com.atguigu.guli.infrastructure.apigateway.filter;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("全局过滤器");
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
//校验用户必须登录
AntPathMatcher antPathMatcher = new AntPathMatcher();
if(antPathMatcher.match("/api/**/auth/**", path)) {
List tokenList = request.getHeaders().get("token");
//没有token
if(null == tokenList) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
//token校验失败
Boolean isOK = JwtHelper.checkToken(tokenList.get(0));
if(!isOK) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
}
//放行
return chain.filter(exchange);
}
//定义当前过滤器的优先级,值越小,优先级越高
@Override
public int getOrder() {
return 0;
}
private Mono out(ServerHttpResponse response) {
JsonObject message = new JsonObject();
message.addProperty("success", ResultCodeEnum.LOGIN_AUTH.getSuccess());
message.addProperty("code", ResultCodeEnum.LOGIN_AUTH.getCode());
message.addProperty("data", "");
message.addProperty("message", ResultCodeEnum.LOGIN_AUTH.getMessage());
byte[] bytes = message.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
//输出http响应
return response.writeWith(Mono.just(buffer));
}
}
// 其他微服务
server:
port: 8090
spring:
application:
name: testService01
cloud:
nacos:
server-addr: 127.0.0.1:8082
discovery:
group: testGroup
namespace: Dev
sentinel:
transport:
dashboard: 127.0.0.1:8080
//其他微服务启动类
@SpringBootApplication
@EnableDiscoveryClient // nacos注册中心配置
public class TestServiceDemo01Application {
public static void main(String[] args) {
SpringApplication.run(TestServiceDemo01Application.class, args);
}
}
gateway运行流程-草图
过滤流程
@Component
//@Order(-1) //过滤器的优先级,越小越高
public class AuthorizeFilter implements GlobalFilter , Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap queryParams = request.getQueryParams();
//2.获取参数中的authorization参数
String authorization = queryParams.getFirst("authorization");
//3.判断参数值是否等于admin
if("admin".equals(authorization)) {
//4.是,放行
return chain.filter(exchange);
}
//5.否,拦截
//5.1设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
改造示例:
查看linux的nacos是否启动,如果没有启动先启动(nacos之前文档有)
一、新建gateway子工程
子工程
二、引入依赖
下面开始吐槽这个依赖引入的坑,很耗时间去解决,各种报错。
注意坑点(希望下次我遇到时,可以快速解决报错吧):
- 1、gateway模块的pom不要去继承父工程的pom,父工程的pom依赖太多,极大可能会导致运行报错,新建gateway子工程后,pom父类就采用默认的spring-boot-starter-parent即可。
- 2、在gateway自己的pom文件引入依赖,如下:
截图展示清晰
- 3、在gateway自己的pom文件不要引用MVC的依赖包,不然会报错。
- 4、网上根据报错查了很多解决方案,比如在pom文件里额外添加feign的依赖,或者更换cloud和springboot的版本,或者排除什么依赖,结果都会出现“解决当前问题,出现新的问题”。
如上解决方案,本人可行。(再次总结需要注意点:不要集成父工程的pom,不要引入MVC的依赖,只引入nacos-discovery和gateway依赖即可)。
三、改造gateway子模块
依赖问题解决后,就可以成功运行gateway模块了,前提是nacos已经开启了。然后开始改造gateway,使之与其他模块构成关联,具体如下:
- 在gateway模块启动类上加上@EnableDiscoveryClient注解。
- 修改yml配置文件,可以交给nacos管理,此处为了方便,直接写在本地yml配置里,如果不知道怎么使用nacos上的配置文件。接着说,修改yml文件后,结果如上图
此处配置将当前模块跑到nacos注册中心,然后开启gateway的注册中心路由功能
四、演示开启nacos注册中心路由功能效果
gateway可以通过routes和nacos的路由功能实现路由功能,现在展示一下nacos的路由功能效果。通过如下方法开启的路由功能:
gateway: discovery: locator: enabled: true #开启注册中心路由功能
五、演示自定义路由配置效果
上面第四点演示了默认通过注册中心实现网关路由的功能,下面第五点演示不通过第四点,而是自己通过routes配置自定义的路由策略,如下:
注意:若要使用自定义配置,则“不能开启注册中心的路由功能”,否则自定义的策略会失效,学习时给我坑惨了,百度也没找到问题,解决注释了开启注册中心的路由功能的配置,自定义的配置就生效了。
先写一个配置,再进行描述,配置如下:
yml
四个属性:
- id:可以理解为是这组配置的一个id值,请保证他的唯一的,可以设置为和服务名一致。
- uri:可以理解为是通过条件匹配之后需要路由到(跳转,重定向)到的新的服务地址。
- predicates:可以理解为是编写条件,满足条件才进行uri。
- filters:可以理解为是在路由前对请求的地址进行额外的其他操作,例如拼接或者裁减等。
六、启动主启动类
启动主类后就会看到 gateway被注册到nacos中了
然后就可以通过我们的gateway服务访问我们nacos中的服务了.示例到此就结束了.
常用方法
- Forward Routing Filter:用于路由请求到后端服务。
- LoadBalancerClient Filter:通过LoadBalancerClient执行负载均衡请求。
- AddRequestHeader Filter:在请求中添加头信息。
- AddRequestParameter Filter:在请求中添加查询参数。
- RewritePath Filter:重写请求路径,用于修改请求的路径。
- SetStatus Filter:设置HTTP响应状态码。
- AddResponseHeader Filter:在响应中添加头信息。
- Hystrix Filter:用于Hystrix断路器的支持。
- WebSockets Filter:用于WebSocket代理。
- ModifyResponseBody Filter:修改响应体内容。
- PreserveHostHeader Filter:保留原始主机头信息。
- RequestRateLimiter Filter:实现请求速率限制。