SpringCloud基础Gateway:微服务网关

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud基础Gateway:微服务网关相关的知识,希望对你有一定的参考价值。

目录

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);

以上是关于SpringCloud基础Gateway:微服务网关的主要内容,如果未能解决你的问题,请参考以下文章

搭建SpringCloud微服务框架:SpringCloud-Gateway 服务网关处理

Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用

springcloud+gateway微服务整合swagger

重学SpringCloud系列九微服务网关-GateWay

Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用

微服务SpringCloud之GateWay路由