【真会玩】- SpringCloud Netflix 实战笔记 -【SpringCloud Gateway】


在这里插入图片描述

友情提醒

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

在这里插入图片描述

SpringCloud Gateway

  • Gateway网关是一个服务,是访问内部系统的唯一入口,提供内部服务的路由中转

  • 还可以在此基础上提供如身份验证、监控、负载均衡、限流、降级与应用检测等功能。

服务搭建

在这里插入图片描述

勾选依赖

在这里插入图片描述

我们这边SpeingCloud版本和SpringBoot版本选用Hoxton.SR12 & 2.3.12.RELEASE

修改POM文件中spring-boot.versionspring-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类中新增以下测试接口

@GetMapping("/gateway/testGateway")
public String testGateway() {
  return "success";
}

咱们启动服务测试一下配置了Path路径断言的效果

启动一个Eureka-ServerFeign-ConsumerGateway这几个服务即可

  • 请求http://localhost:6666/gateway/testGateway接口

在这里插入图片描述

如果把接口中/gateway去掉会出现什么情况

@GetMapping("/testGateway")
public String testGateway() {
return "success";
}

可能是这样的

在这里插入图片描述

也可能是这样的

在这里插入图片描述

那怎么样才能不带前缀请求呢?

  • 配置filters
  • StripPrefix=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-ConsumerGateway服务,调用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信息

20221006_000422_edit

在这里插入图片描述

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

在这里插入图片描述

负载均衡

集成Eureka后我们就不用手动指定具体的URI地址进行转发了,可以自动拉取注册服务列表,在根据规则配置uri则可以对具体的服务做负载均衡

  • 添加Eureka Client依赖
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

添加&修改Eureka相关配置,这边我们修改一下之前随便配置的idPath等配置项为贴近具体服务见名知意的值,当咱们用到项目里面肯定也不能随便配一下就完了,肯定是要根据一些具体的规则去配置

  • 读取配置时,以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-ServerFeign-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次,所以占比与配置相吻合,当然,数据不可能百分百刚好准确,肯定会有些许误差的,我这边也是一个大概值,想要更精准的数据需要用压力测试工具进行大请求量的测试才能得出更接近标准的值

20221006_084105

限流

令牌桶 &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相关配置全部注释掉,只开启一个路由规则即可

  • HostCookie断言注释掉

  • 权重配置注释掉,否则可能会有部分请求无返回

  • #{@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

在这里插入图片描述
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倪倪N

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值