spring cloud gateway微服务网关的简单使用

一、spring cloud gateway微服务网关简介

      gateway官方文档地址

       为什么需要网关? 在微服务架构中,每个服务是一个独立运行的组件,每个服务都会完成特定的功能,例如订单服务、评论服务、库存服务。假设客户端发起一个请求,我们所有的服务端都需要一个认证的程序,认证客户端来的请求是否是认证过得,例如登录。这样我们的各个微服务就会做重复的工作,所以我们为了解决类似的重复工作问题,我们就引入一个微服务网关。

      网关可以做些什么呢?网关可以做:授权、日志、限流、路由等工作。网关服务有:openResty(Nginx+lua)、Kong、Tyk、Zuul、Spring cloud gateway。

    我们该如何选择网关服务呢?通常网关服务需要有这些特性: 高稳定性、高性能、高安全性、可扩展性。

    spring cloud gateway是spring官方提供的组件,目的是为了取代zuul。它的核心是:spring webflux、Reactor。

二、spring cloud gateway的使用

1、pom.xml依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

    引入gateway依赖后我们就可以使用网关了。gateway的核心概念其实就是路由,路由(Route)又包含了:ID、Predicate、Filter。请求会先进入断言,断言拦截判断是否可以进入路由(true/false),true才继续往下进入filter。

2、断言(Predicate)

具体大家可以查询java8里面的predicate(断言、函数式接口)、Consumer/BiConsumer。关于断言此处就不多做赘述了,大家可以看Predicate深入介绍,这位博主写的比较清楚。

3、filter——拦截器

    filter有两类:GlobalFilter、RouteFilter,全局拦截器不需要我们在配置文件中配置,它是针对全局生效的;RouteFilter需要我们在配置文件中配置,如以下配置:

spring:
  cloud:
    gateway:
      routes:
        - predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1 #设置跳过前缀gateway,如果需要跳过多个可以设置2/3对应数字就行
          uri: http://localhost:7070/

    Filter拦截有两种方式:PreFilter前置过滤、PostFilter后置过滤。针对请求进入到服务端的请求进行过滤,是PreFilter前置过滤;针对服务端处理完毕的返回的报文进行过滤是PostFilter后置过滤。

filter里面可以做授权认证、限流、添加标记报文和一些自定义的操作,更详细的大家可以看:方志朋博客方志朋CSDN博客

gateway实现限流

网关的限流是通过redis来实现的,所以需要引入redis依赖。引入redis依赖后,我们在配置文件中添加相应的配置,再实现“keyResolver”接口,自定义针对哪一个资源来进行限流的判断,具体如下:

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring:
  cloud:
    gateway:
      routes:
        - id: ratelimiter-route #配置限流路由
          predicates:
            - Path=/ratelimiter/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter #配置限流name属性:RequestRateLimiter
              args: #以下是限流里面配置的参数
                deny-empty-key: true #配置限流基于某个标记判断,true为基于
                keyResolver: '#{@ipAddressKeyResolver}' #配置限流bean的名称,bean名称默认首字母小写
                redis-rate-limiter.replenishRate: 1 #设置每秒允许请求数是1个
                redis-rate-limiter.burstCapacity: 2 #设置并发容量(令牌桶容量)是2个
          uri: lb://order-service #配置lb(loadBalance)负载均衡分发,从eureka发现服务,分发服务。
package com.example.gatewayservice8085;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class IpAddressKeyResolver implements KeyResolver {

    //设置针对IP作为一个进行限流请求的key
    public Mono<String> resolve(ServerWebExchange exchange) {
        //通过just生成一个Mono对象
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()) ;
    }
}

gateway实现负载均衡

gateway的负载均衡是通过eureka来实现的,我们的网关程序中需要引入eureka依赖,添加相应的配置,具体如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: lb-route
          predicates:
            - Path=/lb/**
          filters:
            - StripPrefix=1
          uri: lb://order-service #需要转发的话,有一个叫lb的协议,既loadBalance-负载均衡分发,从eureka发现服务,分发服务,这就是一个全局路由。
      discovery:
        locator: #配置路由解析器locator
          enabled: true
          lower-case-service-id: true #配置以小写的方式进行匹配
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka

gateway动态路由及持久化

实现动态路由及路由的持久化,我们需要依赖redis和actuator,具体实现如下:

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

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    timeout: 6000ms
    jedis:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 10
        min-idle: 5
management:
  endpoints:
    web:
      exposure:
        include: "*"

package com.example.gatewayservice8085;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

//通过redis实现动态路由持久化
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private static final String GATEWAY_ROUTE_KEY = "gateway_dynamic_route";

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {//从redis中获取全部路由信息
        List<RouteDefinition> routeDefinitionList = new ArrayList<>();

        redisTemplate.opsForHash().values(GATEWAY_ROUTE_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 -> {//定义一个routeDefinition用flatMap进行数据的转化
            redisTemplate.opsForHash().put(GATEWAY_ROUTE_KEY,routeDefinition.getId(),JSON.toJSONString(routeDefinition));//存入redis
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {//通过路由id删除指定路由
        return routeId.flatMap(id->{
            if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTE_KEY,id)){
                redisTemplate.opsForHash().delete(GATEWAY_ROUTE_KEY,id);
                return Mono.empty();
            }
            //如果没有数据,需要通过Mono.defer定义一个Mono返回。
            return Mono.defer(()->Mono.error(new Exception("routeDefinition not found"+routeId)));
        });
    }
}

动态添加路由:比如我们动态添加一个id是baidu_route的路由,我们可以发送POST请求到http://localhost:8085/actuator/gateway/routes/baidu_route,同时携带下面的JSON请求参数(注意我们请求链接最后的“baidu_route”和我们请求参数中的id要一致):

{
  "id": "baidu_route",
  "predicates": [{
    "name": "Path",
    "args": {"_genkey_0":"/baidu"}
  }],
  "filters": [{
      "args":{
          "_genkey_0":1
      },
      "name":"StripPrefix"
  }],
  "uri": "https://www.baidu.com",
  "order": 0
}

通过http://localhost:8085/actuator/gateway/refresh我们可以刷新路由信息

通过http://localhost:8085/actuator/gateway/routes我们可以查看我们的路由信息

通过发送DELETE请求到http://localhost:8085/actuator/gateway/routes/baidu_route我们可以删除持久化的路由(同样请求链接最后的“baidu_toute”要和我们要删除的路由id保持一致)

三、通过demo来学习

  本项目建立在上一篇文章spring cloud config配置中心的基础上,同样在springcloudnetflix项目下新建spring项目。

1、pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>spring-cloud-netflix</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>gateway-service-8085</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-service-8085</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、application.yml配置文件

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: config-route
          predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1 #设置跳过前缀gateway,如果需要跳过多个可以设置2/3对应数字就行
            - Define=Hello Aron
          uri: http://localhost:7070/
        - id: cookie-route
          predicates:
#            - Cookie=name,aron
            - Path=/define/** #Path 和Auth 是and的关系,如果Path通过,Auth不通过的话,不会进入filters.
            - Auth=Authorization,token #Auth=key,value是key/value形式
          filters:
            - StripPrefix=1
          uri: https://www.gupaoedu.com
        - id: lb-route
          predicates:
            - Path=/lb/**
          filters:
            - StripPrefix=1
          uri: lb://order-service #需要转发的话,有一个叫lb的协议,既loadBalance-负载均衡分发,从eureka发现服务,分发服务,这就是一个全局路由。
        - id: ratelimiter-route #配置限流路由
          predicates:
            - Path=/ratelimiter/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter #配置限流name属性:RequestRateLimiter
              args: #以下是限流里面配置的参数
                deny-empty-key: true #配置限流基于某个标记判断,true为基于
                keyResolver: '#{@ipAddressKeyResolver}' #配置限流bean的名称,bean名称默认首字母小写
                redis-rate-limiter.replenishRate: 1 #设置每秒允许请求数是1个
                redis-rate-limiter.burstCapacity: 2 #设置并发容量(令牌桶容量)是2个
          uri: lb://order-service #配置lb(loadBalance)负载均衡分发,从eureka发现服务,分发服务。
      discovery:
        locator: #配置路由解析器locator
          enabled: true
          lower-case-service-id: true #配置以小写的方式进行匹配
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    timeout: 6000ms
    jedis:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 10
        min-idle: 5
server:
  port: 8085
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka
management:
  endpoints:
    web:
      exposure:
        include: "*"

3、项目代码

(1)、自定义Auth断言

package com.example.gatewayservice8085;

import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
//自定义断言类名命名规则必须是:name+'RoutePredicateFactory',既后缀必须是‘RoutePredicateFactory’,
// 它会去筛选、截取作为我们的predicate的key
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {

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

    private static final String NAME_KEY = "name";//name就是我们Config中的name,对应我们配置文件中的key,例如Auth=key,value 的key.

    private static final String VALUE_KEY = "value";//value就是我们Config中的value,对应我们配置文件中的value,例如Auth=key,value 的value.

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

    public Predicate<ServerWebExchange> apply(final Config config) {

        boolean hasValue = !StringUtils.isBlank(config.getValue());
        //如果header中携带了某个值,进行header的判断
        return (exchange->{
            HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
            List<String> headerList = httpHeaders.get(config.getName());
            if (headerList == null || headerList.isEmpty()){
                return false;
            }else {
                return hasValue ? headerList.stream().anyMatch((value)->{
                   return value.equals(config.getValue());
                }) : false;
            }

        });
    }

    //内部配置类
    public static class Config {
        private String name;

        private String value;

        public String getName() {
            return name;
        }

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

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}

(2)、自定义路由拦截器(RouteFilte)

package com.example.gatewayservice8085;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

//自定义RouteFilter
@Component
public class DefineGatewayFilterFactory extends AbstractGatewayFilterFactory<DefineGatewayFilterFactory.Config> {

    private static final String NAME_KEY = "name";
    Logger logger = LoggerFactory.getLogger(DefineGatewayFilterFactory.class);

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

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

    @Override
    public GatewayFilter apply(Config config) {
        //Filter 里面分为pre post。pre:客户端请求进来;post:服务端返回过去.
        return ((exchange, chain) -> {  //chain表示过滤器链
            logger.info("[pre] Filter Request,请求进来写自己的业务逻辑,name:"+config.getName());

            //then表示接受一个变量;filter表示前面的处理结束,then表示接受前面的处理结果继续处理
            return chain.filter(exchange).then(Mono.fromRunnable(()->{ //Mono.fromRunnable表示链处理完成后的回调
                logger.info("[Post]: Response Filter——链路处理完毕返回");
            }));
        });
    }

    //内部配置类
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

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

(3)、设置限流—自定义针对哪个资源进行限流判断,此处针对IP限流

package com.example.gatewayservice8085;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class IpAddressKeyResolver implements KeyResolver {

    //设置针对IP作为一个进行限流请求的key
    public Mono<String> resolve(ServerWebExchange exchange) {
        //通过just生成一个Mono对象
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()) ;
    }
}

(4)、通过redis实现动态路由的持久化

package com.example.gatewayservice8085;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

//通过redis实现动态路由持久化
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private static final String GATEWAY_ROUTE_KEY = "gateway_dynamic_route";

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {//从redis中获取全部路由信息
        List<RouteDefinition> routeDefinitionList = new ArrayList<>();

        redisTemplate.opsForHash().values(GATEWAY_ROUTE_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 -> {//定义一个routeDefinition用flatMap进行数据的转化
            redisTemplate.opsForHash().put(GATEWAY_ROUTE_KEY,routeDefinition.getId(),JSON.toJSONString(routeDefinition));//存入redis
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {//通过路由id删除指定路由
        return routeId.flatMap(id->{
            if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTE_KEY,id)){
                redisTemplate.opsForHash().delete(GATEWAY_ROUTE_KEY,id);
                return Mono.empty();
            }
            //如果没有数据,需要通过Mono.defer定义一个Mono返回。
            return Mono.defer(()->Mono.error(new Exception("routeDefinition not found"+routeId)));
        });
    }
}

四、总结

    至此,spring-cloud-gateway就学习完毕了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值