美文网首页API网关
网关挖掘机(一)Zuul网关1.x

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

作者: 进击的阿黑 | 来源:发表于2019-11-14 10:46 被阅读0次

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

    一、什么是Zuul

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

    特点

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

    二、架构原理

    • 工作原理

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

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

    • 核心流程

    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请求的一个生命周期如下图

    filters.jpg

    二、Zuul-Instance-Demo说明

    1. 简介

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

    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.<route>.path与zuul.routes.<route>.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<br />thread.sleep-ms=500
      eureka-provider 1102 server.port=1102<br />thread.sleep-ms=1500
      eureka-provider 1103 server.port=1103<br />thread.sleep-ms=10000<br />eureka.instance.metadata-map.publish=gray

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

    • 配置服务提供方

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

      maven_config.jpg

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

    provider_config.jpg

    3. 运行

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

    三、使用开发

    1. 引入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
    </parent>
    
    <properties>
        <project.version>1.0.0</project.version>
        <cloud.version>2.1.2.RELEASE</cloud.version>
        <springboot.version>2.1.5.RELEASE</springboot.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-context</artifactId>
            <version>${cloud.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>${cloud.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <version>${cloud.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>${cloud.version}</version>
        </dependency>
    
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    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.<route>.path与zuul.routes.<route>.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网关

      eureka.jpg

    访问方式:

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

    3. 设置超时时间

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

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

    访问方式:

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

    6. 限流

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

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

      <dependency>
          <groupId>com.marcosbarbero.cloud</groupId>
          <artifactId>spring-cloud-zuul-ratelimit</artifactId>
          <version>2.2.4.RELEASE</version>
      </dependency>
      
      
      • 限流粒度:

        粗粒度:

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

        细粒度:

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

        ConsulRateLimiter:Consul

        RedisRateLimiter:Redis

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        

        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
      
      

      结果如下:

      ratelimit.png

    4. 负载均衡

    5. 路由重试

    引入依赖

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>
    
    

    配置 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

        本文链接:https://www.haomeiwen.com/subject/oeglictx.html