文章目录
友情提醒
- 文末获取【真会玩】- SpringCloud Netflix 实战系列
Postman在线脚本、Github仓库实战源码- 【真会玩】- SpringCloud Netflix 实战系列有概念有实战,考验动手能力,如果实战中有操作没有达到与本文同等效果的请先仔细检查个人操作与文中相关内容是否一致,如检查无误请私信或评论区留言~

SpringCloud Gateway
Gateway网关是一个服务,是访问内部系统的唯一入口,提供内部服务的路由中转
还可以在此基础上提供如身份验证、监控、负载均衡、限流、降级与应用检测等功能。
服务搭建

勾选依赖

我们这边
SpeingCloud版本和SpringBoot版本选用Hoxton.SR12 & 2.3.12.RELEASE修改
POM文件中spring-boot.version和spring-cloud.version
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
路由配置
转发指定URI
SpringCloud GateWay主要功能就是配置路由,那配置路由需要配置哪些配置项,怎么使用呢?一起来看看
predicates
断言,请求过来断定哪些请求可以匹配到uri上,可配多个
Path
路径断言,
- Path=/gateway/**当匹配到/gateway这个路径的时候,不管后面是什么路径都转发到uri配置的地址
- Path=是固定格式,不能写成- Path:/Path:
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
在
Feign-Consumer服务的FeignConsumerController类中新增以下测试接口
Feign-Consumer服务相关代码请参考【真会玩】- SpringCloud Netflix 实战笔记 -【OpenFeign代替Feign】
@GetMapping("/gateway/testGateway")
public String testGateway() {
return "success";
}
咱们启动服务测试一下配置了
Path路径断言的效果启动一个
Eureka-Server、Feign-Consumer、Gateway这几个服务即可
- 请求
http://localhost:6666/gateway/testGateway接口

如果把接口中
/gateway去掉会出现什么情况
@GetMapping("/testGateway")
public String testGateway() {
return "success";
}
可能是这样的

也可能是这样的

那怎么样才能不带前缀请求呢?
- 配置
filtersStripPrefix=1是去掉url路径中的一级前缀,也就是相当于把http://localhost:6666/gateway/testGateway中的/gateway给去掉了
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
重启
Gateway服务再次调用http://localhost:6666/gateway/testGateway接口

Query
如果是查询请求,比如根据
name查询用户信息的这种接口,那就要这样配了
- Query=name,rhys如案例中【name,rhys】->【字段名,值】,这个值可以是具体的也可以写正则匹配
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Query=name,rhys
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
在
Feign-Consumer服务的FeignConsumerController类中新增以下测试接口
@GetMapping("/testQueryPredicate")
public String testQueryPredicate(@RequestParam("name") String name) {
return name;
}
这样配置的话那就要在具体的接口后面添加查询参数了
http://localhost:6666/gateway/testGateway?name=rhys

如果不传这个
name字段,将是这样的

如果我们字段值传的不是
rhys,将是这样的

Method
也可以根据方法类型进行路由,这个断言配置决定了能够请求到
POST接口还是Get接口
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
- Query=name,rhys
- Method=get
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
改完配置重启
Gateway服务,再次调用http://localhost:6666/gateway/testQueryPredicate?name=rhys接口依然是可以请求通的

如果我们将
- Method=get配置项修改为- Method=post
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
- Query=name,rhys
- Method=post
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
在
Feign-Consumer服务的FeignConsumerController类中新增以下测试接口
@PostMapping("/testMethodPredicate")
public String testMethodPredicate(@RequestParam("name") String name) {
return name;
}
重启
Feign-Consumer和Gateway服务,调用http://localhost:6666/gateway/testMethodPredicate?name=rhys接口
- 此时为
POST请求的接口可以调用,为Get请求的接口则调不通了


Host
也可以根据请求头中主机名(域名)断言来决定哪个域名的请求可以转发到对应目标地址
- 适用于有多个网关各自拥有独立的域名,可以根据不同的域名进行不同服务的转发
- 网关1负责转发服务A相关的请求
- 网关2负责转发服务B相关的请求
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
- Query=name,rhys
- Method=post
- Host=www.rhys.vip
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
请求脚本,该脚本可直接复制导入
Postman
curl --location --request POST 'http://localhost:6666/gateway/testMethodPredicate?name=rhys' \
--header 'Host: www.rhys.vip'
重启
Gateway服务并再次调用http://localhost:6666/gateway/testMethodPredicate?name=rhys接口,别忘记在请求头中设置Host

Cookie
也可以根据 Cookie进行断言判定,根据不同Cookie响应不同用户的请求
server:
port: 6666
# 应用名称
spring:
application:
name: GatewayServer
cloud:
gateway:
#可配多条路由规则
routes:
- id: rout1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/gateway/**
- Query=name,rhys
- Method=post
- Host=www.rhys.vip
- Cookie=name,rhys
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: http://localhost:9080
重启
Gateway服务并用Postman模拟一个Cookie信息


如果
Cookie中信息和断言配置对不上则请求无法转发到目标地址

负载均衡
集成Eureka后我们就不用手动指定具体的URI地址进行转发了,可以自动拉取注册服务列表,在根据规则配置
uri则可以对具体的服务做负载均衡
- 添加
Eureka Client依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加&修改
Eureka相关配置,这边我们修改一下之前随便配置的id、Path等配置项为贴近具体服务见名知意的值,当咱们用到项目里面肯定也不能随便配一下就完了,肯定是要根据一些具体的规则去配置
- 读取配置时,以
lb://开头的uri直接走负载均衡策略
spring:
application:
name: GatewayServer
cloud:
gateway:
discovery:
locator:
enabled: true
#可配多条路由规则
routes:
- id: feignConsumer
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/feignConsumer/**
- Query=name,rhys
- Method=post
- Host=www.rhys.vip
- Cookie=name,rhys
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
# uri: http://localhost:9080
#走负载均衡策略
uri: lb://FeignConsumer
eureka:
client:
service-url:
#向eureka发起注册请求
defaultZone: http://RhysNi:123456@eureka1.com:7901/eureka/
# defaultZone: http://RhysNi:123456@eureka1.com:7901/eureka/,http://RhysNi:123456@eureka2.com:7902/eureka/,http://RhysNi:123456@eureka3.com:7903/eureka/
instance:
#查找主机
hostname: localhost
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
为了更好的展示负载均衡生效了,我们在
/testMethodPredicate接口上把端口号带上
@Value("${server.port}")
private String port;
@PostMapping("/testMethodPredicate")
public String testMethodPredicate(@RequestParam("name") String name) {
return name+" port:"+port;
}
停掉所有服务,重新启动
Eureka-Server、Feign-Consumer(多实例)、Gateway服务,如下服务列表

调用以下接口(可直接复制导入
Postman)
curl --location --request POST 'http://localhost:6666/feignConsumer/testMethodPredicate?name=rhys' \
--header 'Host: www.rhys.vip' \
--header 'Cookie: name=rhys'
可以看到当配置了
uri=lb://xxx的时候会自动对相关服务做负载均衡

当然这个负载均衡策略同样是支持自定义的,之前咱们在总结
Ribbon的时候提到过怎么自定义负载均衡策略
- 因为我们这边是在
Gateway服务对Feign-Consumer服务做负载均衡,所以我们需要在Gateway服务中新增自定义负载均规则类
public class CustomerRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object o) {
List<Server> serverList = getLoadBalancer().getReachableServers();
//TODO:实现符合项目需要的负载均衡算法
return serverList.get(new Random().nextInt(serverList.size()));
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
// TODO document why this method is empty
}
}
在
Gateway服务配置文件中指定对FeignConsumer服务做负载均衡的实现类
FeignConsumer:
ribbon:
NFLoadBalancerRuleClassName: com.rhys.gateway.rule.CustomerRule
从Debug可以看到确实可以获取到某个节点,这里获取到了
9081节点

与接口返回的端口一致,说明自定义负载均衡生效了

自定义路由规则
在
Gateway服务中添加新的路由配置类适用于项目中需要更复杂的路由规则去转发不同服务的时候
这边每一个Api其实对应的就是我们配置中的那些配置项
我们目的是当调用
http://localhost:6666/jump的时候自动跳转到百度首页
@Component
public class CustomerRoute {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/jump")
.filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1))
.uri("http://www.baidu.com/"))
.build();
}
}
当请求
http://localhost:6666/jump时就会自动转发到百度首页

自定义过滤器
过滤器的使用场景非常多,例如权限认证、限流等操作都是在过滤器中进行
自定义过滤器则需要新建过滤器类并实现
Ordered接口对各个过滤器进行排序决定过滤的优先级,另外还要实现一个GlobalFilter接口实现filter方法我们现在想要对
http://localhost:6666/jump接口进行条件过滤,让path为/jump的无法进行URL转发,返回一套自定义的信息
@Component
public class CustomerFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
RequestPath path = exchange.getRequest().getPath();
if ("/jump".equals(String.valueOf(path))) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().setRawStatusCode(HttpStatus.UNAUTHORIZED.value());
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap("被过滤器处理过了,跳转不了百度了".getBytes(StandardCharsets.UTF_8))));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
重启
Gateway服务,请求http://localhost:6666/jump接口

总结一下
- 过滤器可以有
多个,并且可以根据order设置优先级,getOrder返回的的值越小优先级越高- chain.filter(exchange); 可以把
exchange传递给下一个优先级的过滤器
权重/灰度发布
可以做灰度发布

我们配置两套路由规则
Weight可配置两个参数【分组,权重】
- 我们这边把两组
Feign-Consumer高可用服务分到FCService同一组中,做两层负载均衡,第一层根据权重算法在FCService组内进行随机
- 权重随机底层是一个数组,为我们这边配置的
FeignConsumer1权重是99%而FeignConsumer2权重是1%,在请求过来的时候会随机生成一个数没然后去匹配数组中的权重区间- 第二层交由
Ribbon在集群内做负载均衡
spring:
application:
name: GatewayServer
cloud:
gateway:
discovery:
locator:
enabled: true
#可配多条路由规则
routes:
- id: feignConsumer1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/feignConsumer/**
- Query=name,rhys
- Method=post
- Host=www.rhys.vip
- Cookie=name,rhys
- Weight=FCService,95
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: lb://FeignConsumer1
- id: feignConsumer2
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/feignConsumer/**
- Query=name,rhys
- Method=post
- Host=www.rhys.vip
- Cookie=name,rhys
- Weight=FCService,5
filters:
- StripPrefix=1
#请求过来路由匹配完成后转发的目标地址
uri: lb://FeignConsumer2
修改
FeignConsumer服务中的应用名为FeignConsumer1并启动三个节点作为高可用集群
spring:
application:
name: FeignConsumer1
修改FeignConsumer服务中的应用名为FeignConsumer2再启动三个节点作为高可用集群
spring:
application:
name: FeignConsumer2
停掉所有服务,重启启动如下图服务列表中服务
- 单实例
Feign-Consumer- 单实例
Gateway- 三实例
FeignConsumer1- 三实例
FeignConsumer2

这边请求了大概45次,一共只出现2次属于
FeignConsumer2服务的节点,45次的5%应该是2.25次,所以占比与配置相吻合,当然,数据不可能百分百刚好准确,肯定会有些许误差的,我这边也是一个大概值,想要更精准的数据需要用压力测试工具进行大请求量的测试才能得出更接近标准的值

限流
令牌桶 &Redis 限流
Redis安装教程
在
Gateway服务中引入Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
自定义执行限流算法的规则
- 我们规定让参数为
name的请求进入限流
@Component
public class RateLimitConfig {
@Bean
public KeyResolver keyResolver() {
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("name")));
}
}
在配置文件中添加下列配置
为了不影响接下来的操作,我们将之前配置的
灰度发布相关的- id: feignConsumer2相关配置全部注释掉,只开启一个路由规则即可将
Host和Cookie断言注释掉将
权重配置注释掉,否则可能会有部分请求无返回
#{@keyResolver},可以在spring容器中拿到名为keyResolver的bean
spring:
application:
name: GatewayServer
cloud:
gateway:
discovery:
locator:
enabled: true
#可配多条路由规则
routes:
- id: feignConsumer1
#请求过来断定哪些请求可以匹配到uri上,可配多个
predicates:
- Path=/feignConsumer/**
- Query=name,rhys
- Method=post
# - Host=www.rhys.vip
# - Cookie=name,rhys
# - Weight=FCService,100
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
#截取哪些规则进入限流算法
key-resolver: "#{@keyResolver}"
#发放令牌速度1个/s
redis-rate-limiter.replenishRate: 1
#总令牌数量
redis-rate-limiter.burstCapacity: 5
#请求过来路由匹配完成后转发的目标地址
# uri: http://localhost:9080
#读取配置时,以"lb://"开头的uri则直接走负载均衡策略
uri: lb://FeignConsumer1
启动
Redis并且重启Gateway服务
- 我这里使用了
Jmeter测试了一波,3s请求300次- 结果显示前5次符合配置中配的总令牌数量,用完后每秒生成一个新的令牌,只有拿到令牌的才请求成功,其余全部请求失败,这也说明我们限流成功

GoogleGuava限流器
添加lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
新建配置类
- permitsPerSecond 每秒生成令牌数
@Data
@Validated
public class Config {
@DecimalMin("0.1")
private Double permitsPerSecond;
}
自定义限流器
@Primary注解得加,标识当前customerRateLimiter为默认的Bean- 因为
RedisRateLimiter也是实现了AbstractRateLimiter抽象类,所以会冲突,我们这边不用RedisRateLimiter
@Primary
@Component
public class CustomerRateLimiter extends AbstractRateLimiter<Config> {
/**
* 每秒发一个令牌
*/
private final RateLimiter limiter = RateLimiter.create(1);
@Override
public Mono<Response> isAllowed(String routeId, String id) {
Config config = getConfig().get(routeId);
limiter.setRate(Objects.isNull(config.getPermitsPerSecond()) ? 1 : config.getPermitsPerSecond());
boolean isAllow = limiter.tryAcquire();
return Mono.just(new Response(isAllow, new HashMap<>()));
}
public CustomerRateLimiter() {
super(Config.class, "default-rate-limit", new ConfigurationService());
}
}
这里使用
Jmeter2秒请求10次,两次调用成功测试限流成功


源码&脚本获取地址
- Postman脚本:
https://www.getpostman.com/collections/8a0954746646f0dee9a6
- 脚本可直接复制到Postman使用
Link方式导入- 实战源码仓库:
https://github.com/RhysNi/SpringCloudFamilyMeals.git


4043

被折叠的 条评论
为什么被折叠?



