从zuul到springcloud gateway

为什么放弃springcloud zuul

springcloud整合的是zuul1,包内没有引入netty,而springcloud netflix停止维护,zuul2至今未整合到spring,在高并发时zuul1的BIO模型可能会出现性能瓶颈。SpringCloud 最后自己研发了一个网关替代 Zuul1,那就是 SpringCloud Gateway,它是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。有图有真相!

从zuul到springcloud gateway_第1张图片

从zuul到springcloud gateway_第2张图片

从zuul到springcloud gateway_第3张图片

 

另外,SpringCloud Gateway 具有更多特性:

  • 基于 Spring Framework 5,Project Reactor 和 SpringBoot 2.0 进行构建
  • 动态路由:能够匹配任何请求属性
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器)
  • 集成 Hystrix 的断路器功能
  • 集成 SpringCloud 服务发现功能
  • 易于编写的 Predicate(断言)和 Filter(过滤器)
  • 请求限流功能
  • 支持路径重写
  • 其它-TODO

 

 

从zuul切换到springcloud gateway

①依赖处理:需要去除依赖spring-cloud-starter-netflix-zuul,引入新依赖spring-cloud-starter-gateway,zuul间接引入springcloud hystrix也会移除,如果升级为springcloud gateway需要熔断降级,则需要单独spring-cloud-starter-netflix-hystrix。另外gateway底层使用webflux web层框架(AIO),与springmvc冲突,需要去除依赖“spring-boot-starter-web“

②不再需要@EnabelXX启动注解,gateway网关核心配置可以通过配置文件或java bean配置类实现

配置文件方式示例

spring:
  application:
    name: gateway
  profiles:
    active: @package.environment@
  cloud:
    nacos:
      config:
        server-addr: @package.nacos.addr@         #注意不要http://,nacaos使用的是raft协议
        file-extension: yml                       #后缀
        namespace: @package.environment@          #配置命名空间,默认public
        shared-configs:
          - data-id: common.yml                #配置所有工程共享的配置,注意里面的配置默认不能动态刷新,需要时可配置自动刷新
            refresh: true
        password: @package.nacos.password@        #nacos 1.3.X才支持
        username: @package.nacos.username@
        context-path: /nacos
      discovery:
        password: @package.nacos.password@
        username: @package.nacos.username@
        server-addr: @package.nacos.addr@          #注意不要http://,nacaos使用的是raft协议
        namespace: @package.environment@           #服务命名空间,默认public


    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由,uri即可支持lb://微服务名进行负载均衡调用
      routes:
      - id: user    # 路由的ID,只要唯一,建议服务名
        #uri: http://www.baidu.com     # 可以为静态的url列表(相当于绕过注册中心,调用方直连服务方)一般为lb://微服务名
        uri: lb://user
        predicates:
        - Path=/user/**              # 各种断言,这里指定3种,为与关系,java bean则可配置或关系,而且After,Before等断言配置比较麻烦,故建议java bean配置,而且java bean方式可以方便结合nacos进行动态更新
        - After=2011-12-01T13:11:37.485+08:00[Asia/Shanghai]  #电商抢购,抽奖等活动,到时间该路由才生效可以利用这个玩意
        - Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
        filters:
          - StripPrefix=1    #如http://localhost:8000/user/version/latest匹配上/user/**,但是转发到后端具体微服务能不需要改path,则可以通过改配置截去掉该前缀在转发后面的微服务

      - id: payment
        uri: lb://payment
        predicates:
        - Path=/payment/**
        filters:
          - StripPrefix=1

java bean配置类方式

这里配置了两个与关系的断言,及两个局部过滤器来修改一下reques,response(zuul过滤器需要改较多代码),局部过滤器还可以通过spring.cloud.gateway.routes.filters配置在具体路由下

@Configuration
@Slf4j
public class GatewayConfig {

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user", r -> r.path("/user/**")
                        .and()
                        .after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
                        .filters(gatewayFilterSpec -> gatewayFilterSpec
                                .filter((exchange, chain) ->{
                                    log.error(exchange.getRequest().getPath().toString());
                                    return chain.filter(exchange);},1)
                                .filter((exchange, chain) ->{
                                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                                    return chain.filter(exchange);},2)
                                .stripPrefix(1)
                               )
                        .uri("lb://user"))
                .route("payment", r -> r.path("/payment/**")
                        .and()
                        .after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
                        .filters(gatewayFilterSpec -> gatewayFilterSpec
                                .filter((exchange, chain) ->{
                                    log.error(exchange.getRequest().getPath().toString());
                                    return chain.filter(exchange);},1)
                                .filter((exchange, chain) ->{
                                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                                    return chain.filter(exchange);},2)
                                .stripPrefix(1)
                               )
                        .uri("lb://payment"))
                .build();
    }

}

所有predicates

类型 示例
After After=2017-01-20T17:42:47.789-07:00[America/Denver]
Before Before=2017-01-20T17:42:47.789-07:00[America/Denver]
Between 2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie Cookie=chocolate, ch.p
Header Header=X-Request-Id, \d+
Host Host=**.somehost.org
Method Method=GET
Path Path=/foo/{segment}
Query Query=baz
RemoteAddr RemoteAddr=192.168.1.1/24

 

④配置spring gateway自带过滤器——网关路由重试

spring gateway自带过滤较多——参考zuul的转发重试通过ribbon.MaxAutoRetriesNextServer等配置项完成,gataway的重试通过过滤器实现


@Configuration
@Slf4j
public class GatewayConfig {

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user", r -> r.path("/user/**")
                        .or()
                        .after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
                        .filters(gatewayFilterSpec -> gatewayFilterSpec
                                .filter((exchange, chain) -> {
                                    log.error(exchange.getRequest().getPath().toString());
                                    return chain.filter(exchange);
                                }, 1)
                                //转发重试
                               .filter(new RetryGatewayFilterFactory().apply(retryConfig -> {
                                    retryConfig
                                            .setRetries(2).setBackoff(Duration.of(10, ChronoUnit.MILLIS), Duration.of(50, ChronoUnit.MILLIS), 2, false)
                                            .setStatuses(HttpStatus.BAD_GATEWAY, HttpStatus.GATEWAY_TIMEOUT)
                                            .setMethods(HttpMethod.GET, HttpMethod.DELETE);//post/put如有幂等风险建议不重试
                                }))
                                .filter((exchange, chain) -> {
                                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                                    return chain.filter(exchange);
                                }, 2)
                                .stripPrefix(1)
                        )
                        .uri("lb://user"))

                .build();
    }

}

⑤配置spring gateway自带的全局过滤器——网关熔断,需引入spring-cloud-starter-netflix-hystrix,全局过滤器还可以通过spring.cloud.gateway.default-filters配置

网关是所有请求的入口,如果部分后端服务延时严重,则可能导致大量请求线程堆积在网关上,拖垮网关进而瘫痪整个系统(微服务内部调用不经过网关,需要通过feign配置熔断),这就需要对响应慢的服务做超时快速失败处理,即熔断


@RestController
public class FallbackController {

    @GetMapping("/fallback")
    public Object fallback() {
        Map result = new HashMap<>();
        result.put("data",null);
        result.put("message","Hystrix fallback!");
        result.put("code",500);
        return result;
    }


}
spring:
  cloud:
    gateway:
      default-filters:  #hystrix熔断转发,全局过滤器
      - name: Hystrix
        args:
          name: default
          fallback-uri: forward:/fallback
#熔断器配置
hystrix:
  command:
    default:    #default指路由的所有下级服务都使用相同默认的熔断配置,可指定具体服务具体配置如payment,defulat=>payment
      execution:
        isolation:
          strategy: SEMAPHORE  #默认THREAD,网关高扇出适合信号量
          thread:
            timeoutInMilliseconds: 1 #实现HystrixComand即的run(..业务代码..)方法的超时时间,设置1ms方便测试
          semaphore:
            maxConcurrentRequests: 100   #允许信号量1000并发-压测
      circuitBreaker:
        errorThresholdPercentage: 50 #错误或超时50%时开启熔断器
        sleepWindowInMilliseconds: 5000 #//熔断器中断请求5秒后会进入半打开状态,放部分流量去正常请求,成功则关闭熔断器
        requestVolumeThreshold: 2 #至少有2个请求并且达到错误阈值才会判断是否打开熔断器

⑥集成redis限流,Gateway通过内置的RequestRateLimiter过滤器实现限流,使用令牌桶算法。如果请求太大默认会返回HTTP 429-Too Many Requests(自定义注解也可简单实现)


   org.springframework.boot
   spring-boot-starter-data-redis-reactive
@Configuration
public class RateLimiterConfiguration {
    /**
     * 用户可通过自定义KeyResolver设置限流维度,例如:
     *
     * 对请求的目标URL进行限流
     * 对来源IP进行限流
     * 通过jwt token等请求头对用户进行限流
     * @return
     */
    @Bean(value = "ipKeyResolver")
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true 
      routes:
      - id: user
        uri: lb://user
        predicates:
        - Path=/user/**
        filters:
        - StripPrefix=1
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 1
            key-resolver: '#{@ipKeyResolver}'

⑦跨域请求配置文件或配置类(这个配置类极似zuul的跨域配置类,但使用的类包路径不一样——reactive)

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            maxAge: 60
            allowedHeader: "*"
            allowCredentials: true
            allowedMethods:
            - GET
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CrosConfiguration {
    @Bean
    public CorsWebFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.setAllowCredentials(true);
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }

}

⑧自定义全局过滤器——认证授权

简单鉴权参考——鉴权与日志参考

// 过滤器需要成为Spring的组件
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    // 过滤器逻辑
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("全局过滤器执行...");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token))
        {
            // 设置状态码为未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            // 设为已完成 不再继续执行
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    // 过滤器执行顺序 return的值越小越先执行
    @Override
    public int getOrder() {
        return 1;
    }
}

集成oauth2认证授权参考——跨域问题解决参考

从zuul到springcloud gateway_第4张图片

 

 

你可能感兴趣的:(springcloud)