案例1:环境搭建
可以复制 Service-A9001 改成Gateway-C9009
》1:创建工程导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
注意
SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容,需要从工程的pom.xml中删除 web框架,在需要web框架的模块中添加
》2:配置启动类
package com.dev1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayC9009 {
public static void main(String[] args) {
SpringApplication.run(GatewayC9009.class,args);
}
}
》3:编写配置文件
server:
port: 9009
spring:
application:
name: Gateway-C9009
cloud:
gateway:
routes:
- id: route1
uri: http://127.0.0.1:9001
predicates:
- Path=/person/**
# - id: route2
# uri: http://127.0.0.1:9002
# predicates:
# - Path=/order-service/**
(1)id :我们自定义的路由 ID,保持唯一
(2)uri :目标服务地址
(3)predicates :路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默
认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
(4)filters :过滤规则,暂时没用
上面这段配置的意思是,配置了一个 id 为 persson-service的路由规则,当访问网关请求地址person 开头时,会自动转发到地址: http://127.0.0.1:9001/ 。
配置完成启动项目即可在浏览器访问进行测试,当我们访问地址 http://localhost:9009/person/find/2 时会展示页面展示如下
案例2:路由规则
在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件
匹配到对应的路由
#路由断言之后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://xxxx.com
#路由断言之前匹配
predicates:
- After=xxxxx
#路由断言之前匹配
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://xxxxxx.com
predicates:
- Before=xxxxxxx
#路由断言之间
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://xxxx.com
predicates:
- Between=xxxx,xxxx
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://xxxx.com
predicates:
- Cookie=chocolate, ch.p
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://xxxx.com
predicates:
- Header=X-Request-Id, \d+
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
#路由断言Method匹配,匹配的是请求的HTTP方法
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://xxxx.com
predicates:
- Method=GET
#路由断言匹配,{segment}为可变参数
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Path=/foo/{segment},/bar/{segment}
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含
foo,并且foo的值匹配ba.)
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://xxxx.com
predicates:
- Query=baz 或 Query=foo,ba.
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位
数即255.255.255.0
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
案例3:动态路由
和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并
访问。
(1)添加注册中心依赖
在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
( 2)配置动态路由
修改 application.yml 配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名称
server:
port: 9009
spring:
application:
name: Gateway-C9009
cloud:
gateway:
routes:
- id: person-service
uri: lb://Service-A9001
predicates:
- Path=/person/**
eureka:
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
uri : uri以 lb: //开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
测试
http://localhost:9009/person/find
案例3:重写转发路径
在SpringCloud Gateway中,修改Path为/person-service/**
则
# http://127.0.0.1:9009/person-service/person/find/1 转发到
# http://127.0.0.1:9001/person-service/person/find/1
但后者不存在,只有
# http://127.0.0.1:9001/person/find/1 可以访问
(1)修改配置文件
修改 application.yml ,将匹配路径改为 /person-service/**
重新启动网关,我们在浏览器访问
http://127.0.0.1:9009/person-service/person/find/1
会抛出404。这是由于路由转发规则默认转发到
http://127.0.0.1:9001/person-service/person/find/1
(2) 添加RewritePath重写转发路径
修改 application.yml ,添加重写规则。
server:
port: 9009
spring:
application:
name: Gateway-C9009
cloud:
gateway:
routes:
- id: service-1 #编号唯一
uri: lb://Service-A9001
predicates:
- Path=/person-service/**
filters:
- RewritePath=/person-service/(?<segment>.*), /$\{segment}
路径变化如下
http://127.0.0.1:9009/person-service/person/find/1 转发到
http://127.0.0.1:9001/person/find/1 可以访问
案例4:微服务名称转发
http://localhost:9009/service-a9001/person/find/1 转发到
http://127.0.0.1:9001/person/find/1 可以访问
配置如下
server:
port: 9009
spring:
application:
name: Gateway-C9009
cloud:
gateway:
routes:
- id: service-1 #编号唯一
uri: lb://Service-A9001
predicates:
- Path=/person-service/**
filters:
- RewritePath=/person-service/(?<segment>.*), /$\{segment}
discovery:
locator:
lower-case-service-id: true
enabled: true
案例5:全局过滤器搭建
》1:自定义一个全局过滤器,实现 globalfilter , ordered接口
@Component
public class LoginFilter implements GlobalFilter,Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
return chain.filter(exchange); //继续向下执行
}
/**
* 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
*/
public int getOrder() {
return 0;
}
}
》2:访问测试
http://localhost:9009/service-a9001/person/find/1
案例6:鉴权中心
(1)自定义全局过滤器需要实现 GlobalFilter和Ordered接口。
(2)在 filter方法中完成过滤器的逻辑判断处理
(3)在 getOrder方法指定此过滤器的优先级,返回值越大级别越低
(4)ServerWebExchange 就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实例和响应实例等等。一个请求中的request,response都可以通过 ServerWebExchange 获取
(5)调用 chain.filter 继续向下游执行
/**
* 自定义一个全局过滤器
* 实现 globalfilter , ordered接口
*/
@Component
public class LoginFilter implements GlobalFilter,Ordered {
/**
* 执行过滤器中的业务逻辑
* 对请求参数中的access-token进行判断
* 如果存在此参数:代表已经认证成功
* 如果不存在此参数 : 认证失败.
* ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
*/
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
//1.获取请求参数access-token
String token = exchange.getRequest().getQueryParams().getFirst("access-token");
//2.判断是否存在
if(token == null) {
//3.如果不存在 : 认证失败
System.out.println("没有登录");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); //请求结束
}
//4.如果存在,继续执行
return chain.filter(exchange); //继续向下执行
}
/**
* 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
*/
public int getOrder() {
return 0;
}
}
测试
http://localhost:9009/service-a9001/person/find/1?access-token=abc
http://localhost:9009/service-a9001/person/find/1
案例7:网关限流(Filter)
SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。
》1:准备redis服务器
Redis-x64-4.0.2.3.zip
redisclient-客户端工具.zip
打开监控
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8bV6Ayv-1668667456838)(index_files/0cd2f043-5763-4fd2-bfd7-40042988f566.png)]
》2:导入 redis的依赖
首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:
<!--监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
》3:修改application.yml配置文件
server:
port: 9009
spring:
application:
name: Gateway-C9009
redis:
host: localhost
pool: 6379
database: 0
cloud:
gateway:
routes:
- id: service-1 #编号唯一
uri: lb://Service-A9001
predicates:
- Path=/person-service/**
filters:
- name: RequestRateLimiter
args:
# 使用SpEL从容器中获取对象
key-resolver: '#{@pathKeyResolver}'
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的上限
redis-rate-limiter.burstCapacity: 3
- RewritePath=/person-service/(?<segment>.*), /$\{segment}
在 application.yml 中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:
burstCapacity ,令牌桶总容量。
replenishRate ,令牌桶每秒填充平均速率。
key-resolver ,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
》4: 配置KeyResolver
为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。
package com.dev1;
@Configuration
public class KeyResolverConfiguration {
/**
* 编写基于请求路径的限流规则
* //abc
* //基于请求ip 127.0.0.1
* //基于参数
*/
@Bean
public KeyResolver pathKeyResolver() {
//自定义的KeyResolver
return new KeyResolver() {
/**
* ServerWebExchange :
* 上下文参数
*/
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getPath().toString());
}
};
}
}
测试的时候使用以下地址不停刷新
http://localhost:9009/person-service/person/find/1?access-token=abc
不要使用
http://localhost:9009/service-a9001/person/find/1?access-token=abc
》5:基于请求参数或者ip限流的配置
增加
package com.dev1;
@Configuration
public class KeyResolver2Configuration {
/**
* 基于请求参数的限流
* 请求 abc ? userId=1
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getQueryParams().getFirst("userId")
//exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
);
}
}
修改
key-resolver: '#{@userKeyResolver}'
大括号中就是我们的限流 Key,这边是IP,本地的就是localhost
timestamp: 存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者
Instant.now().getEpochSecond()
tokens: 存储的是当前这秒钟的对应的可用的令牌数量
Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情况,比如:
对不同接口的限流
被限流后的友好提示
这些可以通过自定义RedisRateLimiter来实现自己的限流策略,这里我们不做讨论