springcloud组件 ----- gateway网关

路由配置

搭建环境

  • 导入依赖
<dependency>
   <groupId>org.springframewoark.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 配置启动类
@SpringBootApplication
public class GatewayServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServerApplication.class,args);
    }

}
  • 编写配置文件
server:
  port: 8888 #端口
spring:
  application:
    name: api-gateway-service #服务名称

注意:启动的时候会报错,那是因为父工程中spring-boot-starter-web包与gateway工程中的包重复了,将gateway中的依赖包去除或者将父工程中的包去除(如果将父工程的包去除需要在用到这个包的所有子工程中重新引入),就可以了

路由规则

断言:路由条件
参考:https://blog.csdn.net/qq_38233650/article/details/98038225

动态路由

  • 添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 配置文件
#eureka配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8999/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册ip
  • 修改配置文件
spring:
  application:
    name: api-gateway-service #服务名称
  #配置springcloudGateway的路由
  cloud:
    gateway:
      routes:
      #配置路由:路由id,路由到微服务的uri,断言(判断条件)
      - id: product-service
        #uri: http://127.0.0.1:9001
        uri: lb://product-service #lb:// 根据微服务名称从注册中心拉取服务请求路径
        predicates:
        #product不能随便写,一定要能接收到的接口,如:有一个服务提供者的uri为:
        #http://127.0.0.1:9001/product/getProduct/1,下面- Path的参数就得写,/product/**
        - Path=/product/**

其实只修改了uri

  • 如果喜欢http://127.0.0.1:8888/product-service/product/getProduct/1的形式,可以这样配置
predicates:
	- Path=/product-service/**
filters: #配置路由过滤器
	- RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写过滤器,在yml中 $ 写为 $\
  • 配置自动的根据微服务名称进行路由转发
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启根据服务名称自动转发
          lower-case-service-id: true #微服务名称以小写形式呈现

访问的时候就可以:http://127.0.0.1:8888/product-service/product/getProduct/1

过滤器

过滤器的生命周期

Spring Cloud Gateway的Fliter的声明周期不像Zuul的那么丰富,它只有两个:“pre"和"post”(和zuul一样)

过滤器类型

/**
 * 自定义全局过滤器
 *      实现 globalfilter,ordered接口
 * */
@Component
public class LoginFilter implements GlobalFilter,Ordered {
    /**
     * 执行过滤器中的业务逻辑
     * */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
        return chain.filter(exchange);//继续向下执行
    }

    /**
     * 指定过滤器的执行顺序,返回值越小,执行优先级越高
     * */
    @Override
    public int getOrder() {
        return 0;
    }
}

统一鉴权

/**
     * 执行过滤器中的业务逻辑
     *      对请求桉树中的access-token进行判断
     *          如果存在此参数:代表已经认证成功
     *          如果不存在此参数:代表认证失败
     *      ServerWebExchange:相当于请求和响应上下文(zuul中的RequestContext)
     * */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        //2.判断是否存在
        if(token == null) {
            //3.如果不存在:认证失败
            System.out.println("没有登录");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();//请求结束
        }
        //4.如果存在:继续执行
        return chain.filter(exchange);//继续向下执行
    }

网关限流

计数器算法

其实就是维护一个单位时间内的访问计数器,如果访问量超过阈值,则限流
缺点:假如我时按照一个小时间隔算的,如果我在10分钟的时候就达到了上限,那么剩下的时间将会全部访问都被拒绝

漏桶算法

在这里插入图片描述
队列大小,假如队列设置的容量为100,当第101个到来时,就会直接拒绝

令牌桶算法

令牌桶是对漏桶算法的改进,增加了允许一定程度的突发调用
在这里插入图片描述
原理:每隔一段时间向桶中存放一个令牌,当请求过来的时候,必须拿到一个令牌才可以进行处理,否则直接拒绝或者等待,另外桶设置一个最大容量

限流方法

基于filter的限流

SpringCloudGateway官方提供了基于令牌桶的限流支持。基于其内置的过滤工厂RequestRateLimiterGatewayFilterFactory实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。
(1) 环境搭建

  • 安装redis(略)
  • 导入redis的依赖:基于reactive的redis依赖
<!-- 监控依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
 <!-- redis依赖 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
 </dependency>

(2)修改网关中的application.yml

spring:
  redis:
    host: localhost
    pool: 6379
    database: 0
  cloud:
    gateway:
      routes:
        filters:
        - name: RequestRateLimiter
          args:
            #使用SpEL从spring容器中获取对象
            key-resolver: '#{@pathKeyResolver}'
            #令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 1
            #令牌桶上限
            redis-rate-limiter.burstCapacity: 3

(3)配置redis中key的解析器KeySesolver
接下来我们就要在spring中注入一个 pathKeyResolver 对象

@Configuration
public class GatewayPathKeyResolver {
    /**
     * 编写基于请求路径的限流规则
     * //基于请求路径
     * //基于请求ip
     * //基于参数
     * */
    @Bean
    public KeyResolver pathKeyResolver(){
        return exchange -> Mono.just(
                //基于请求路径
                //exchange.getRequest().getPath().toString()
                //基于参数
                exchange.getRequest().getQueryParams().getFirst("userId")
                //基于ip
                //exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
        );
    }

}

基于Sentinel的限流

Sentinel1.6.0引入了Sentinel API Gateway Adapter Common模块,此模块中包含网关限流的规则和自定义API的实现体和管理逻辑

  • GatewayFlowRule:网关限流规则,针对API Gateway的场景定制的限流规则,可以针对不同route或者自定义的API分组进行限流,支持针对请求中的参数、Header、来源IP等进行定制化的限流。
  • ApiDefinition:用户自定义的API定义分组,可以看座是一些URL匹配组合。比如我们可以定义一个API叫my_api,请求path模式为/foo/**和/baz/**的都归到my_api这个API分组下面。限流的时候可以针对这个自定义的API分组维度进行限流。
    (1)环境搭建
    导入Sentinel的响应依赖
<dependency>
  <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>

(2)编写配置类

/**
 * sentinel的限流配置
 * */
@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer){
        this.serverCodecConfigurer = serverCodecConfigurer;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
    }


    /**
     * 配置限流异常处理器:SentinelGatewayBlockExceptionHandler
     * */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
    }

    /**
     * 配置限流过滤器
     * */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter(){
        return new SentinelGatewayFilter();
    }

    /**
     * 配置初始化的限流参数
     *      用于指定资源的限流规则
     *          1.资源名称(路由id)
     *          2.配置统计时间
     *          3.配置限流的阈值
     * */
    @PostConstruct
    public void initGatewayRules(){
        Set<GatewayFlowRule> rules = new HashSet<>();
//        rules.add(
//          new GatewayFlowRule("product-service")//资源名称
//                .setCount(1)//限流阈值
//                .setIntervalSec(1L)//统计时间窗口,单位是秒,默认为1秒
//        );
        rules.add(new GatewayFlowRule("product_api")//分组名称
                .setCount(1)//限流阈值
                .setIntervalSec(1L)//统计时间窗口,单位是秒,默认为1秒
        );

        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 限流降级
     * */
    @PostConstruct
    public void initBlockHandlers(){
        GatewayCallbackManager.setBlockHandler((serverWebExchange,throwable) ->{
            Map map = new HashMap();
            map.put("code",001);
            map.put("message","对不起,接口限流了");
            return ServerResponse.status(HttpStatus.OK).
                    contentType(MediaType.APPLICATION_JSON_UTF8).
                    body(BodyInserters.fromObject(map));
        });
    }

    /**
     * 自定义API限流分组
     *      1.定义分组
     *      2.对小组配置限流规则
     * */
    @PostConstruct
    public void initCustomizedApis(){
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("product_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                    //以/product-service/product/开头的所有url
                    add(new ApiPathPredicateItem().setPattern("/product-service/product/**")
                        .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        ApiDefinition api2 = new ApiDefinition("comsumer_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                    //完全匹配/comsumer-service/demo的所有url
                    add(new ApiPathPredicateItem().setPattern("/comsumer-service/demo")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }


}

这些配置也可以直接在sentinel后台管理界面设置

网关的高可用

结构:
在这里插入图片描述
所以,我们这里需要的工具是一个ngnix,两到三台gateway

  • 启动两台gateway
    我这里使用上面的gateway,然后复制以下,修改一下端口
  • 下载,配置ngnix
    (1)下载:http://nginx.org/en/download.html
    (2)配置,找到解压目录下的/conf/nginx.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    #集群配置
    upstream gateway{
        server 127.0.0.1:8888;
        server 127.0.0.1:8889;
    }

    server {
        listen       80;
        server_name  localhost;

        #127.0.0.1
        location / {
            proxy_pass http://gateway;
        }
    }
}

访问:http://localhost/product-service/product/getProduct/2
大功告成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值