spring|SpringCloud——Gateway(服务网关的学习和使用)

什么是Spring Cloud Gateway Spring Cloud Gateway不仅提供了统一的路由方式,并且还基于Filter链的方式提供了网关基本的功能,提供一种简单而有效的方法来路由到API,并为它们提供了跨领域的关注点,例如:安全性,监控/指标,限流等等。
什么是服务网关 API Gateway 是出现在系统边界上的一个面向API的,串行集中式的强管服务,边界可以理解为企业级的防火墙,主要起到 隔离外部访问与内部系统的作用,API网关是一个服务器,是系统对外的唯一接口。API网关封装了系统内部架构,为每个客户端定制的API,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的业务功能。API网关并不是微服务场景中必须的组件。
为什么要使用网关
微服务的应用可能部署在不同的机房,不同的地区,不同的域名下,此时客户端(浏览器、手机,软件)想要访问对应的服务,都需要知道机器的具体的IP或者域名URL,当微服务实例众多时,对于客户端来说太复杂难以维护了,此时就有了网关,客户端相应的请求直接发送到网关,由网关根据请求标识解析判断出具体的微服务地址,再把请求转发到微服务实例,这其中的记忆功能就全部交由网关来操作了。
网关解决了什么问题
统一接入:为各种无线应用提供了统一接入服务,高性能,高并发,高可靠性,负载均衡,容灾切换(异地灵活)
协议适配:前端系统(http,http2)后端业务系统(RPC),长、短连接支持,根据前端请求路由至相应的SOA服务并执行,返回结果给前端
安全防护:和安全部合作,IP黑名单,URL黑名单,风控防刷,防恶意攻击等
流量监控:服务降级,熔断,路由-(异地多活中的应用)
网关应该具有以下功能:
性能:API高可用,负载均衡,容错机制
安全:权限身份认证,脱敏,流量清洗,后端签名(保证全链路可行调用),黑名单(非法调用的限制)
日志:日志记录,一旦涉及分布式,全链路跟踪必不可少
缓存:数据缓存。
监控:记录请求响应数据,API耗时分析,性能监控。
限流:流量控制,错峰流控,可以定义多种限流规则。
灰度:线上灰度部署,可以减少风险。
路由:动态路由规则
Gateway实现API网关 路由(Route):路由是网关最基础的部分,路由信息由ID,目标URL,一组断言和一组过滤器组成,如果断言路由为真,则说明请求的URL的配置匹配
断言(Predicate):Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于HTTP Request中的任何信息,比如请求头和参数等;
过滤器(Filter):一个标准的Spring Web Filter。Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter 和Global Filter。过滤器将会对请求和响应进行处理
工作原理
客户端向Spring Cloud Gateway发出请求,再由网关处理程序Gateway Handler Mapping 映射确定与请求相匹配的路由,将其发送到网关Web处理程序 Gateway Web handler,该处理程序通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器由虚线分割的原因是,过滤器可以在发送代理请求之前和之后进行运行逻辑。所有pre过滤器逻辑均被执行。然后发出代理请求,发出代理请求之后,将运行post过滤器逻辑
环境搭建
kts文件

import org.jetbrains.kotlin.gradle.tasks.KotlinCompileplugins { id("org.springframework.boot") version "2.3.7.RELEASE" id("io.spring.dependency-management") version "1.0.10.RELEASE" kotlin("jvm") version "1.6.0" kotlin("plugin.spring") version "1.6.0" }group = "com" version = "0.0.1-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_1_8repositories { mavenCentral() }dependencies { implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies implementation("org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR12") // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway implementation("org.springframework.cloud:spring-cloud-starter-gateway:2.2.10.RELEASE") }tasks.withType { useJUnitPlatform() }tasks.withType { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "1.8" } }

这里Gateway不支持低版本的kotlin,我这里试了,1.6.0是可以的,需要注意的是,在使用Gateway时,不能导入web的依赖
application.yml
server: port: 9001spring: application: name: gateway-servercloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Path=/product/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后

启动类无需加注解
路由规则
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Path=/product/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后

Path
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Path=/product/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后

Query
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Query=token#匹配请求参数包含token的请求 #- Query=token,abc.#匹配请求参数包含token 并且其参数值满足正则表达式abc.的请求

Method
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Method=GET#匹配任意的GET请求

Datetime
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - After=2022-08-02T20:20:20.000+08:00[Asia/Shanghai] #匹配中国伤害事件2022-08-02 20:20:20之后的请求

RemoteAddr
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - RemoteAddr=192.168.10.13/0#匹配远程地址请求是RemoteAddr的请求,0表示子网掩码

Header
cloud: gateway: routes:#路由规则 - id: product-service#路由ID uri: http://localhost:7070/#目标URI,路由到微服务地址 predicates:# 断言 - Header=X-Request-Id, \d+#匹配请求头包含X-Request-Id 并且其值匹配正则表达式 \d+ 的请求

动态路由(服务发现的路由规则) 面向服务的路由,根据serviceId自动从注册中心获取服务地址并转发请求
添加依赖
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery implementation("org.springframework.cloud:spring-cloud-starter-consul-discovery:2.2.8.RELEASE") // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator implementation("org.springframework.boot:spring-boot-starter-actuator:2.6.6")

动态获取URI
server: port: 9001spring: application: name: gateway-servercloud: consul: # 注册中心地址 host: localhost port: 8500 # 服务提供者信息 discovery: register: true#是否注册 instance-id: ${spring.application.name}-01#注册实例id(必须唯一) service-name: ${spring.application.name}#服务名称 port: ${server.port}#服务端口 prefer-ip-address: true#是否使用ip 地址注册 ip-address: ${spring.cloud.client.ip-address}# 服务请求IPgateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/product/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后

服务名称转发
当有多个服务的时候,不可能一个一个的去写,使用SpringBoot约定大于配置
server: port: 9001spring: application: name: gateway-servercloud: consul: # 注册中心地址 host: localhost port: 8500 # 服务提供者信息 discovery: register: true#是否注册 instance-id: ${spring.application.name}-01#注册实例id(必须唯一) service-name: ${spring.application.name}#服务名称 port: ${server.port}#服务端口 prefer-ip-address: true#是否使用ip 地址注册 ip-address: ${spring.cloud.client.ip-address}# 服务请求IPgateway: discovery: locator: # 是否与服务发现组件结合,通过 serviceId 转发到具体的实例 enabled: true# 是否开启基于服务发现的路由规则 lower-case-service-id: true#是否将服务名称转为小写

spring|SpringCloud——Gateway(服务网关的学习和使用)
文章图片

这里本来的访问地址是localhost:9001/product/one/lalal,然后在其中加上服务名localhost:9001/service-provider/product/one/lalal
过滤器 网关过滤器GatewayFilter
Path路径过滤器: 可以实现URL的重写,通过重写URL可以实现隐藏实际路径提高安全性,易于用户记忆和键入,易于被搜索引擎收录等优点
RewritePathGatewayFilterFactory (支持正则) 将不能进去的换一下,就进去了
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/product/**, /api-gateway/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /api-gateway/product/1 重写为 /product/1 - RewritePath=/api-gateway(?>/?.*),$\{segment}

PrefixPathGatewayFilterFactory 将不够的加一下,就可以继续访问了
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /1 重写为 /product/1 - PrefixPath=/product

StripPrefixGatewayFilterFactory 删除多余的进行访问
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /api/123/product/1 重写为 /product/1 - StripPrefix=2

SetPathGatewayFilterFactory 对访问的路径进行匹配修改
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/api/product/{segment}#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /api/product/1 重写为 /product/1 - SetPath=/product/{segment}

Parameter参数过滤器
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path= /api-gateway/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /api-gateway/product/1 重写为 /product/1 - RewritePath=/api-gateway(?>/?.*),$\{segment} # 在下游请求中添加 flag=1 - AddRequestParameter=flag, 1

Status状态过滤器
gateway: routes:#路由规则 - id: service-provider#路由ID uri: lb://service-provider#lb:// 根据服务名称从注册中心获取服务请求地址 predicates:# 断言 - Path=/api-gateway/**#匹配对应URL的请求,将匹配到的请求追加在目标URI之后 filters:#网关过滤器 # 将 /api-gateway/product/1 重写为 /product/1 - RewritePath=/api-gateway(?>/?.*),$\{segment} # 任何情况下,响应的HTTP请求状态都将设置为404 - SetStatus=404

全局过滤器GlobalFilter
全局过滤器不需要再配置文件中配置,作用在所有的路由上
自定义过滤器
自定义网关过滤器
package com.gateway.filterimport org.springframework.cloud.gateway.filter.GatewayFilter import org.springframework.cloud.gateway.filter.GatewayFilterChain import org.springframework.core.Ordered import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Monoclass CustomGatewayFilter : GatewayFilter,Ordered {/** * 过滤器业务逻辑 */ override fun filter(exchange: ServerWebExchange?, chain: GatewayFilterChain?): Mono { println("自定义网关过滤器被执行") return chain!!.filter(exchange) }/** * 过滤器执行顺序,数值越小,优先级越高 */ override fun getOrder(): Int { return 0 } }

在上述文件中写过滤规则就可以了
import com.gateway.filter.CustomGatewayFilter import org.springframework.cloud.gateway.route.RouteLocator import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration@Configuration class GatewayRoutesConfiguration {@Bean fun routeLocator(builder:RouteLocatorBuilder):RouteLocator{ return builder.routes().route { r -> r .path("/product/**") .uri("lb://product-service") .filters(CustomGatewayFilter()) .id("product-service") } .build()} }

自定义全局过滤器
import org.springframework.cloud.gateway.filter.GatewayFilterChain import org.springframework.cloud.gateway.filter.GlobalFilter import org.springframework.core.Ordered import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono@Component class CustomGlobalFilter : GlobalFilter , Ordered {/** * 过滤器业务逻辑 */ override fun filter(exchange: ServerWebExchange?, chain: GatewayFilterChain?): Mono { println("自定义网关过滤器被执行") return chain!!.filter(exchange) }/** * 过滤器执行顺序,数值越小,优先级越高 */ override fun getOrder(): Int { return 0 } }

在全局网关过滤器中通过token判断用户是否登录,完成一个统一鉴权案例
网关限流 为什么要限流:
用户增长过快;因为某个热点事件,竞争对象爬虫,恶意的请求
限流算法 计数器算法
漏桶算法
令牌桶算法(gateway使用)
spring|SpringCloud——Gateway(服务网关的学习和使用)
文章图片

Gateway限流
SpringCloudGateway 官方提供了 RequestRateLimiterGatewayFilterFactory 过滤器工厂,使用Redis加Lua脚本实现了令牌桶算法
高可用网关 实现高可用,数据的冗余备份,服务的失效转移
需要使用Nginx+网关进行集群配置,Nginx进行轮询或者别的方式,进行网关的选择
【spring|SpringCloud——Gateway(服务网关的学习和使用)】一个请求过来,首先经过Nginx的一层负载,到达网关,然后由网关负载到真实后端,若后端有问题,网关会进行重试访问,多次访问后返回失败,可以通过熔断或服务降级立即返回结果。由于是负载均衡,网关重试时不一定会访问到出错的后端。

    推荐阅读