网关挖掘机(一)Zuul网关1.x

参考github wiki:https://github.com/Netflix/zuul/wiki

一、什么是Zuul

Zuul是从设备和网站到Netflix流应用程序后端的所有请求的前门。作为边缘服务应用程序,Zuul旨在实现动态路由,监控,弹性和安全性。

特点

  • 身份验证和安全性
  • 观察与监控
  • 动态路由
  • 压力测试
  • 负载
  • 静态响应处理
  • 多区域弹性

二、架构原理

  • 工作原理

    Zuul的中心是一系列的过滤器,能够在请求和响应的路由过程中执行一系列操作。

    Zuul提供了一个支持动态读取、编译和运行过滤器的框架。且过滤器之间不互相通信,而是通过RequestContext共享状态,且RequestContext 对于每个请求都是唯一的(ThreadLocal)。

  • 核心流程

网关挖掘机(一)Zuul网关1.x_第1张图片
all.jpg
  • 核心功能

    • 服务发现

      • 支持与 Eureka 无缝集成
      • 支持静态服务类别以发现服务
    • 负载均衡

    • 连接池

    • 状态类别

    • 重试

    • 申请护照

    • 申请重试

    • 原始并发保护

    • 相互TLS

    • 代理协议

    • GZip压缩

  • 内置Filter说明

    Zuul内置了四种不同生命周期的过滤器类型,且过滤器之间不“直接”互相通信,而是通过RequestContext共享状态。开发人员可以通过使用zuul来创建各种校验规则的过滤器。

    Zuul RequestContext

    ​ 为了在过滤器之间传递信息,Zuul使用了RequestContext。其数据保存在ThreadLocal每个请求的特定数据中

    过滤器类型如下

    • pre-filter(s):在请求被路由之前调用
    • route-filter(s):在路由请求时调用
    • error-filter(s):在处理请求发生错误时调用
    • post-filter(s):在route和error过滤器被调用之后调用

    Zuul请求的一个生命周期如下图

网关挖掘机(一)Zuul网关1.x_第2张图片
filters.jpg

二、Zuul-Instance-Demo说明

1. 简介

Demo只有服务注册中心、网关服务、服务提供方

网关挖掘机(一)Zuul网关1.x_第3张图片
code_list.jpg

网关的配置

spring:
  application:
    name: zuul-api-gateway
  redis:
    cluster:
      nodes: 192.168.0.201:7000
      max-redirects: 3
    password: 123456
    jedis:
      pool:
        max-idle: 10
        max-active: 500
        max-wait: 1000


server:
  port: 1001

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1001/eureka/

# 单实例配置:zuul.routes..path与zuul.routes..serviceId 参数对的方式配置
zuul:
#  # 过滤客户端附带的headers
#  sensitive-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
#  # 过滤网关内服务之间通信所附带的headers
#  ignored-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
  # 限流設置
  ratelimit:
    enabled: true
    # 配置60秒内请求超3次,网关则抛异常,且60s后可恢复正常请求
    default-policy-list:
      - limit: 3
        quota: 2
        refresh-interval: 60
    repository: Redis
  # 路由设置
  routes:
    # 将对符合/api/** 规则的请求路径转发到服务名为eureka-provider的服务实例上
    service:
      path: /api/**
      serviceId: eureka-provider

    # 当访问格式如:http://localhost:port/服务名/请求路径 时,若遇到服务名太长,可如下做修改
    eureka-provider: /p/**

  # 前缀,所有服务调用需在方法路径前加/api
#  prefix:/api

  # 排除服务
  ignored-services: eureka-provider1

# 设置超时时间,ribbon和hystrix能够同时生效,且取两者的最小值
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000



ribbon:
  # 该参数用来设置路由转发请求的超时时间
  ReadTimeout: 4000
  # 该参数用来设置路由转发请求的时候,创建请求连接的超时时间
  ConnectTimeout: 4000
  # 最大自动重试次数
  MaxAutoRetries: 1
  # 最大自动重试下一个服务的次数
  MaxAutoRetriesNextServer: 1
  eureka:
    enabled: true

2. 准备

  • 服务端口及参数定义

    启动服务 端口 运行参数
    eureka-server 1000
    zuul-api-gateway 1001
    eureka-provider 1101 server.port=1101
    thread.sleep-ms=500
    eureka-provider 1102 server.port=1102
    thread.sleep-ms=1500
    eureka-provider 1103 server.port=1103
    thread.sleep-ms=10000
    eureka.instance.metadata-map.publish=gray

    注意:1103的实例配置的休眠时间是10秒,网关配置的超时时间是4秒必定会超时

  • 配置服务提供方

    以IDEA为例,打开Maven面板,依次打开eureka-provider——Plugins——spring-boot,右击选择spring-boot:run

    网关挖掘机(一)Zuul网关1.x_第4张图片
    maven_config.jpg

依次添加三个provider实例,按以上表格的运行参数分别配置

网关挖掘机(一)Zuul网关1.x_第5张图片
provider_config.jpg

3. 运行

  • 启动注册中心
  • 启动网关
  • 分别运行第二步配置的三个provider实例
  • 运行网关下的测试类TestZuul.java

三、使用开发

1. 引入依赖


    org.springframework.boot
    spring-boot-starter-parent
    2.1.5.RELEASE



    1.0.0
    2.1.2.RELEASE
    2.1.5.RELEASE



    
        org.springframework.cloud
        spring-cloud-context
        ${cloud.version}
    
    
        org.springframework.cloud
        spring-cloud-starter-netflix-zuul
        ${cloud.version}
    
    
        org.springframework.cloud
        spring-cloud-starter-netflix-eureka-server
        ${cloud.version}
    
    
        org.springframework.cloud
        spring-cloud-starter-netflix-eureka-client
        ${cloud.version}
    




    
        
            org.springframework.cloud
            spring-cloud-dependencies
            Greenwich.RELEASE
            pom
            import
        
    



    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
        
            org.apache.maven.plugins
            maven-compiler-plugin
            
                UTF-8
                1.8
                1.8
            
        
        
            org.apache.maven.plugins
            maven-surefire-plugin
            
                true
            
        
    

2. zuul配置

  • 步驟一:创建微服务网关:zuul-api-gateway

    这里默认已有eureka注册中心eureka-server和服务提供方eureka-provider

    服务提供方的某个controller

    @RestController
    @RequestMapping("/test/")
    public class TestController {
    
        @GetMapping("hello/{name}")
        public String hello(@PathVariable String name) throws InterruptedException {
            Thread.sleep(5* 1000);
            return "hello " + name;
        }
    }
    
  • 步骤二:配置文件

    spring:
      application:
        name: zuul-api-gateway
    
    server:
      port: 1000
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:1001/eureka/
    
    # 单实例配置:zuul.routes..path与zuul.routes..serviceId 参数对的方式配置
    zuul:
      routes:
      # 将对符合/api/** 规则的请求路径转发到服务名为eureka-provider的服务实例上
        service:
          path: /api/**
          serviceId: eureka-provider
        # 当访问格式如:http://localhost:port/服务名/请求路径 时,若遇到服务名太长,可如下做修改
        eureka-provider: /p/**
    
      # 前缀,所有服务调用需在方法路径前加/api
    # prefix:/api
    
      # 排除服务
      ignored-services: eureka-provider1
    
  • 步骤三:添加启动类

    @SpringCloudApplication
    @EnableZuulProxy
    public class ZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }
    

    这里解释以下@EnableZuulServer@EnableZuulProxy的区别

    • @EnableZuulServer

      普通版Zuul Server, 支持基本的router和filter功能

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Import({ZuulServerMarkerConfiguration.class})
      public @interface EnableZuulServer {
      }
      
    • @EnableZuulProxy

      增强版Zuul Server,在普通版的基础上,结合eureka+ribbon+增加服务发现与熔断等功能

      @EnableCircuitBreaker
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Import({ZuulProxyMarkerConfiguration.class})
      public @interface EnableZuulProxy {
      }
      

      详细可查看ZuulProxyConfiguration

  • 步骤四:启动

    访问eureka注册中心后台:http://localhost:1001/,可以看到eureka上面注册了服务提供方和zuul网关

    网关挖掘机(一)Zuul网关1.x_第6张图片
    eureka.jpg

访问方式:

  • http://localhost:1000/eureka-provider/test/hello/jerry
  • http://localhost:1000/api/test/hello/jerry

正常即可转发到服务提供方eureka-provider

3. 设置超时时间

Zuul 内部使用了 Ribbon 做负载均衡,它的默认超时时间是1s,当执行一些比较长的请求,会被当做超时处理,返回504

网关挖掘机(一)Zuul网关1.x_第7张图片
timeout.jpg

在配置文件内添加如下配置

# 设置超时时间,ribbon和hystrix能够同时生效,且取两者的最小值
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 30000
ribbon:
  ReadTimeout: 30000
  ConnectTimeout: 30000

4. 自定义熔断降级策略

当Zuul中给定的路径发生错误时,可以通过创建自定义FallbackProvider来提供熔断响应,而Zuul默认的响应不太友好(比如上个point的超时错误返回504)。

  • 自定义熔断处理类

    @Component
    public class TestFallbackProvider implements FallbackProvider {
    
        /**
         * 指定为哪个微服务提供回退功能,*表示所有微服务
         * @return
         */
        @Override
        public String getRoute() {
            return "eureka-provider";
        }
    
        /**
         * 返回体
         * @param route
         * @param cause
         * @return
         */
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            if (cause instanceof HystrixTimeoutException) {
                return response(HttpStatus.GATEWAY_TIMEOUT);
            } else {
                return response(HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    
        private ClientHttpResponse response(final HttpStatus status) {
            return new ClientHttpResponse() {
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return status;
                }
    
                @Override
                public int getRawStatusCode() throws IOException {
                    return status.value();
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return status.getReasonPhrase();
                }
    
                @Override
                public void close() {
                }
    
                @Override
                public InputStream getBody() throws IOException {
                    return new ByteArrayInputStream("服务暂时不可用,要不等一会再试试?".getBytes());
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);
                    return headers;
                }
            };
        }
    }
    
    

5. 自定义Filter

通过继承ZuulFilter,即可实现过滤器机制。

以下自定义过滤器用于校验接口是否传递了token

@Slf4j
@Component
public class AccessFilter extends ZuulFilter {

    /**
     * 四种不同生命周期的过滤器类型
     * 1. pre:在请求被路由之前调用
     * 2. route:在路由请求时被调用
     * 3. post:在route和error过滤器之后被调用
     * 4. error:处理请求时发生错误时被调用·
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤的优先级,数字越大,优先级越低。
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * @return 该过滤器是否需要被执行
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("token");

        if(Objects.isNull(token)) {
            log.warn("token is empty");
            // 让zuul过滤该请求,不对其进行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        log.info("token is ok");
        return null;
    }
}

访问方式:

  • http://localhost:1000/eureka-provider/test/hello/jerry?token=111
  • http://localhost:1000/api/test/hello/jerry?token=111

正常即可转发到服务提供方eureka-provider

6. 限流

  • 引入依赖包spring-cloud-zuul-ratelimit,支持与zuul整合提供分布式限流策略、

    github地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit

    
        com.marcosbarbero.cloud
        spring-cloud-zuul-ratelimit
        2.2.4.RELEASE
    
    
    
    • 限流粒度:

      粗粒度:

      • 网关限流
      • 单个服务限流

      细粒度:

      • url:对请求的目标url进行限流
      • origin:对请求来源ip进行限流
      • user:对特定用户(比如系统的非vip用户)进行限流
      • serviceId:对特定服务id进行限流
    • 限流统计数据存储

      ConsulRateLimiter:Consul

      RedisRateLimiter:Redis

      
          org.springframework.boot
          spring-boot-starter-data-redis
      
      
      

      SpringDataRateLimiter:Spring Data

      Bucket4jJCacheRateLimiter:Bucket4j

      Bucket4jHazelcastRateLimiter:Bucket4j

      Bucket4jIgniteRateLimiter: Bucket4j

      Bucket4jInfinispanRateLimiter:Bucket4j

    • 限流配置模板

      zuul:
        ratelimit:
          key-prefix: your-prefix
          # 开启限流
          enabled: true
          # 存储类型,用于存储统计信息(对于不同的存储类型,需要在pom添加不同的依赖)
          repository: REDIS
          behind-proxy: true
          add-response-headers: true
          
          default-policy-list: #optional  全局配置
            - limit: 10 #optional - 单位时间内窗口的请求数限制
              quota: 1000 #optional - 单位时间内窗口的请求总时间限制
              refresh-interval: 60 # 单位时间设置
              type: #optional
                - user        # 通过登录用户区分
                - origin      # 通过请求ip区分
                - url         # 通过请求路径区分
                - httpmethod  # 通过请求类型区分
          
          #################################################################
              
          policy-list: # 局部配置(对特定的服务id进行限流)
            myServiceId:
              - limit: 10 #optional - request number limit per refresh interval window
                quota: 1000 #optional - request time limit per refresh interval window (in seconds)
                refresh-interval: 60 #default value (in seconds)
                type: #optional
                  - user
                  - origin
                  - url
              - type: #optional value for each type
                  - user=anonymous
                  - origin=somemachine.com
                  - url=/api #url prefix
                  - role=user
                  - httpmethod=get #case insensitive
      
      
  • 测试

    这里选用redis作为网关的数据存储,需要引入redis的依赖包,并且配置redis和ratelimit

    spring:
      application:
        name: zuul-api-gateway
      redis:
        cluster:
          nodes: 192.168.0.201:7000
          max-redirects: 3
        password: 123456
        jedis:
          pool:
            max-idle: 10
            max-active: 500
            max-wait: 1000
            
    zuul:
      # 开启全局配置限流
      ratelimit:
        enabled: true
        # 配置60秒内请求超3次,网关则抛异常,且60s后可恢复正常请求
        default-policy-list:
          - limit: 3
            quota: 2
            refresh-interval: 60
        repository: Redis
    
    

    结果如下:

    网关挖掘机(一)Zuul网关1.x_第8张图片
    ratelimit.png

4. 负载均衡

5. 路由重试

引入依赖


    org.springframework.retry
    spring-retry


配置 zuul.retryable=true

zuul-demo-provider:   #指定服务
  ribbon:
    MaxAutoRetries: 0    #本服务重试次数
    MaxAutoRetriesNextServer: 1   #重试下一个服务个数
    ReadTimeout: 1000     
    ConnectTimeout: 3000

6. 权限集成

参考自定义Filter的实现方式。

7. 灰度发布

8. 开启跨域

注意:当网关配置了跨域处理后,内部服务则不需要配置

配置过滤请求头

zuul:
  # 过滤客户端附带的headers
  sensitive-headers: Access-Control-Allow-Origin
  # 过滤网关内服务之间通信所附带的headers
  ignored-headers: Access-Control-Allow-Origin

配置解決跨域访问问题

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);       // 允许cookies跨域
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.setMaxAge(18000L);
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

感谢阅读,Ending...

你可能感兴趣的:(网关挖掘机(一)Zuul网关1.x)