SpringCloud基础(五)Gateway:微服务网关

本文详细介绍了Spring Cloud Gateway作为微服务网关的功能,包括路由配置、断言(Predicate)和过滤器(Filter)的使用,如Path、Query、Method、Header、Cookie断言,以及限流、重试、添加请求头等过滤器。同时,文章讲解了如何实现自定义断言和过滤器,以及负载均衡和动态路由配置,其中重点提到了利用Redis实现动态路由的持久化。
摘要由CSDN通过智能技术生成

目录

1、网关作用 

2、基本使用

3、配置说明

3.1、Predicate 断言

3.1.1、系统断言

3.1.2、自定义断言

3.2、Filter 过滤

3.2.1、AddRequestParameterGatewayFilterFactory:添加请求头

3.2.2、RequestRateLimiterGatewayFilterFactory:限流

3.2.3、RetryGatewayFilterFactory:重试

3.2.4、自定义过滤器

 4、实现负载均衡

5、动态路由实现

5.1、内存中的路由缓存

5.2、持久化路由


1、网关作用 

在微服务场景下,前端访问不同服务的时候,需要做重复的工作,如鉴权,日志等功能,为了解决统一问题,就增加微服务网关进行整合,如限流,鉴权,缓存,熔断,日志,协议转换。

2、基本使用

依照之前的项目进行构建,依旧采用spring脚手架,但需要注意的是,spring cloud gateway和web-starter的jar包是冲突的,主要因为gateway采用的框架和mvc有冲突,因此需要在父pom中移除web-starter。

 构建完成后,记得删除父依赖中的web-starter。

之后进行环境配置,这次采用yml的方式进行配置。

你问我为什么不用properties?

我不会(微笑)

依旧要配置服务名称,端口号和eureka地址,除此之外,需要配置gateway。

在这里有一个大致的说明,详细配置会在后面进行讲解。

这个配置的意思是,所有localhost:8087/gateway/**的访都会被转接到localhost:8083/**上,StripPrefix的作用是忽略path后的几个路由字段,这里的1,就是忽略/gateway。

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1
          uri: http://localhost:8083/
server:
  port: 8087
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka

启动完成后,尝试访问:http://localhost:8087/gateway/default,即可得到和访问http://localhost:8083/default一样的结果。

3、配置说明

可以看到,上文中有几个很重要的配置

  1. Route:路由,包含predicate,
  2. Predicate:断言
  3. Filter:过滤器

3.1、Predicate 断言

Predicate的断言和java8的并无区别,简单来说,就是和配置好的字符进行匹配,如果符合,就会进入到Filter中进行过滤。

3.1.1、系统断言

除了path,还有很多其他的断言实现,这些断言实现只要一起配置,就可以同时生效。

  1.  Path:进行路径匹配,如- Path=/gateway/**
  2. Query:进行正则匹配,或者参数与参数值匹配,如- Query=/name,lily,需要http://localhost:8087/gateway/default?name=lily,增加参数name=lily之后,就可以正常访问了。
  3. Method:访问方式,post或get,如:-Method=Get
  4. Header:访问请求头限制,形式类似Query,也是key,value形式的,正则表达式同样适用
  5. Cookie:访问Cookie限制,形式类似Query,也是key,value形式的,正则表达式同样适用

3.1.2、自定义断言

通过查看gateway包内的文件,可以看到,想要自定义断言,只需要继承AbstractRoutePredicateFactory抽象类,但需要注意的是,类的名称一定是xxx+RoutePredicateFactory,xxx是任意一个名字,这将作为你的自定义断言名称,后半部分不能更改,因为系统会默认加载后面为RoutePredicateFactory的类作为断言。

AuthRoutePredicateFactory 类必须被IOC托管,所以增加@Component注解。

继承AbstractRoutePredicateFactory类,实现对应方法。

添加内部类Config作为传递参数的类,并加入到AbstractRoutePredicateFactory的泛型定义中,super实现去掉参数,将Config内部类作为指定参数。

shortcutFieldOrder方法,是作为简化配置的方式实现的,意思是将配置后的参数名称直接作为name,不需要额外声明。

apply是核心断言方法,返回值为Boolean类型,这里的方法是需要在Header中添加设置好的参数名,如果没有得到,就返回错误。

@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {

    public AuthRoutePredicateFactory() {
        super(Config.class);
    }

    private static final String NAME_KEY = "name";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(NAME_KEY);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        String name = config.getName();
        return serverWebExchange -> {
            HttpHeaders httpHeaders = serverWebExchange.getRequest().getHeaders();
            List<String> strings = httpHeaders.get(name);
            return !(strings==null) && (strings.size() > 0);
        };
    }

    public static class Config{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

在配置中,增加Auth=Authorization

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: route1
          predicates:
            - Path=/gateway/**
            - Method=Get
            - Auth=Authorization
          filters:
            - StripPrefix=1
          uri: http://localhost:8083/
server:
  port: 8087
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka

增加完成后,需要在Postman中进行测试

 

 可以看到,如果没有添加对应的参数Authorization,就无法进入断言。

3.2、Filter 过滤

下面列举一些常见的过滤器

3.2.1、AddRequestParameterGatewayFilterFactory:添加请求头

这个来自于类AddRequestParameterGatewayFilterFactory,其中GatewayFilterFactory是固定后缀,AddRequestParameter作为name来简化配置,作用是限定header中要有一对键值对。

        - id: add_request_paraemter
          uri: http://localhost:8083/
          filter:
            #在Header中增加一对键值对,name和lily
            - AddRequestParameter=name,lily

3.2.2、RequestRateLimiterGatewayFilterFactory:限流

这个来自于类RequestRateLimiterGatewayFilterFactory,其中GatewayFilterFactory是固定后缀。

限流器使用了redis作为计数器,使用令牌桶进行限流。

  1. burstCapacity ,令牌桶上限 。
  2. replenishRate ,令牌桶填充平均速率,单位:秒。
  3. keyResolver ,限流键解析器 Bean 对象名字 ,使用 SpEL 表达式,从 Spring 容器中获取 Bean 对象
- id: request_rate_limiter
  uri: http://localhost:8083/
  predicates:
    - Path=/limiter/**
  filters:
    - StripPrefix=1
    - name: RequestRateLimiter
      args:
        keyResolver: '#{@ipAddressKeyResolver}'
        redis-rate-limiter.replenishRate: 1
        redis-rate-limiter.burstCapacity: 2
        

增加配置redis地址:

spring:
  application:
    name: spring-cloud-gateway
  redis:
    host: 127.0.0.1

增加继承KeyResolver的类,这个类的作用就是参数KeyResolver的限流地址

@Component
public class IpAddressKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

在pom文件中添加redis依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

3.2.3、RetryGatewayFilterFactory:重试

这个来自于类RetryGatewayFilterFactory,其中GatewayFilterFactory是固定后缀,其常见参数有四个:

private int retries = 3;
private List<Series> series;
private List<HttpStatus> statuses;
private List<HttpMethod> methods;
  1. retries,重试次数,默认为3
  2. status,重试状态码,取值来自HttpStatus
  3. series,重试状态码范围,取值来自Series
  4. methods,重试方法类型,取值来自HttpMethod

如果不用复数形式,可以直接使用数值进行设置

- id: retry_route
  uri: http://localhost:8083/
  predicates:
    - Path=/retry/**
  filters:
    - StripPrefix=1
    - name: Retry
      args:
        retries: 3
        statuses: SERVICE_UNAVAILABLE
        methods: GET
        series: SERVER_ERROR

3.2.4、自定义过滤器

可以通过类似定义断言的方式自定义过滤器。

继承AbstractGatewayFilterFactory,实现类MyDefineGatewayFilterFactory。

打印的两个日志意味着前置拦截和后置拦截。

@Component
public class MyDefineGatewayFilterFactory extends AbstractGatewayFilterFactory<MyDefineGatewayFilterFactory.Config> {

    Logger logger = LoggerFactory.getLogger(MyDefineGatewayFilterFactory.class);

    public MyDefineGatewayFilterFactory() {
        super(Config.class);
    }

    private static final String NAME_KEY = "name";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(NAME_KEY);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            logger.info("pre My Filter Request, Name:" + config.getName());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                String statusCode = exchange.getResponse().getStatusCode().toString();
                logger.info("post statusCode is, code" + statusCode);
            }));
        });
    }

    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

 设置配置文件环境。

- id: my_define
  uri: http://localhost:8083/
  predicates:
    - Path=/define/**
  filters:
    - StripPrefix=1
    - MyDefine=lily

访问路径:http://localhost:8087/define/default

 ReactiveLoadBalancerClientFilter不同于自定义的前置后置过滤器,它是全局过滤器,无需配置,自动生效,它实现的是GlobalFilter, Ordered,因此自定义全局过滤器时也可以这样。

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    Logger logger = LoggerFactory.getLogger(MyGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            logger.info("MyGlobalFilter is starting");
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

 无需配置,直接执行任意一个请求,即可发现打印出的日志

 4、实现负载均衡

想要实现负载均衡,只需要特殊的配置:

将uri的地址配置成lb(load balance)+服务名称即可,即:lb://spring-cloud-xxx

discovery配置的lower-case-service-id是服务ID是否小写,enable是否启动

配置完成后,既可达成负载均衡。

spring:
  application:
    name: spring-cloud-gateway
  redis:
    host: 127.0.0.1
  cloud:
    gateway:
      routes:
        - id: gateway_route
          uri: lb://spring-cloud-user-provide
          predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1
      discovery:
        locator:
          lower-case-service-id: true
          enabled: true

5、动态路由实现

5.1、内存中的路由缓存

gateway组件本身就提供了一些能改变路由配置的接口,但是需要添加配置:

management:
  endpoints:
    web:
      exposure:
        include: "*"

这里的*表示任何路由都可以访问,但是这样的访问很不安全,不过这里只是演示。

根据官方文档,根据访问类型不同,访问/actuator/gateway/routes达成的效果是不同的。

get方式可以得到当前全部的gateway配置:http://localhost:8087/actuator/gateway/routes

post方式可以根据routeid增加或修改路由配置:

这里是body的内容:

{
    "uri":"https://www.baidu.com",
    "predicates":[{
        "name":"Path",
        "args":{
            "pattern":"/baidu/**"
        }
    }],
    "filters":[{
        "name":"StripPrefix",
        "args":{
            "_genkey_0":1
        }
    }]
}

 执行完成后,还需要执行方法进行刷新,方式也是post:http://localhost:8087/actuator/gateway/refresh

这时新配置才会生效。

访问:http://localhost:8087/baidu/s

就可以发现,已经跳转到百度网页上了。

但是这样的方式是将新的配置缓存到内存上,只要重启服务,就会失效,我们需要新的持久化的配置方式。

5.2、持久化路由

其实上文中提到的全部可执行url,都存在于类GatewayControllerEndpoint之中,这个类起到的作用就是controller的作用。

在它的父类之中,有修改和保存路由的方法:

    @PostMapping({"/routes/{id}"})
    public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {
        return Mono.just(route).doOnNext(this::validateRouteDefinition).flatMap((routeDefinition) -> {
            return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> {
                r.setId(id);
                log.debug("Saving route: " + route);
                return r;
            })).then(Mono.defer(() -> {
                return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build());
            }));
        }).switchIfEmpty(Mono.defer(() -> {
            return Mono.just(ResponseEntity.badRequest().build());
        }));
    }

可以看到,这个保存路由的基本方法,就是routeDefinitionWriter.save,这是一个接口,有一个实现类,那么其实只要自定义一个实现类,就可以实现动态路由的持久化。

protected RouteDefinitionWriter routeDefinitionWriter;

使用redis作为持久化的数据存储方式,加入对应的jar包

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

增加新的自定义路由访问方式。

将类托管到IOC中,自动注入RedisTemplate,重写方法,包括获取,保存和删除,都依靠redis进行。

@Component
public class MyRedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private final static String GATEWAY_KEY = "GATEWAY_ROUTE";

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> routeDefinitionList = new ArrayList<>();
        redisTemplate.opsForHash().values(GATEWAY_KEY).stream().forEach(route -> {
            routeDefinitionList.add(JSON.parseObject(route.toString(), RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitionList);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            redisTemplate.opsForHash().put(GATEWAY_KEY, routeDefinition.getId(), JSON.toJSONString(routeDefinition));
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (redisTemplate.opsForHash().hasKey(GATEWAY_KEY, id)) {
                redisTemplate.opsForHash().delete(GATEWAY_KEY, id);
                return Mono.empty();
            }
            return Mono.defer(() -> (Mono.error(new Exception())));
        });
    }
}

RouteDefinition是用来保存路由信息的类,此处展示部分内容

@Validated
public class RouteDefinition {
    private String id;
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    @NotNull
    private URI uri;
    private Map<String, Object> metadata = new HashMap();
    private int order = 0;

    public RouteDefinition() {
    }
}
GatewayAutoConfiguration起到自动装配的作用。
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
    return new PropertiesRouteDefinitionLocator(properties);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值