• SpringCloud学习之四:服务网关之Zuul


    服务网关之Zuul

    Spring Cloud版本:Hoxton.SR5

    1. 简介

    之前的文章已经搭建服务注册中心、服务提供者、服务消费者以及Turbine聚合监控。随着微服务越来越大,服务调用也将越来越麻烦。因为一个正常的业务处理可能涉及到多个服务调用,那么客户端就需要多次调用不同的微服务。这样就存在如下问题:

    • 客户端需要多次调用http请求。多次创建/销毁http连接,效率低
    • 每个微服务都需要实现一套认证功能。若认证方式不同,客户端的逻辑就更加复杂
    • 跨域问题。每个微服务都需要进行跨域处理
    • 客户端可能需要维护多个ip、端口。若服务进行迁移,客户端改动大

    所以可以通过增加一个服务网关解决这些问题。所有的外部请求都先通过服务网关,在网关中进行统一的认证,认证通过后转发到相应的微服务中。这样客户端就仅与服务网关进行交互,隐藏了各个微服务。

    graph LR A[客户端] --统一认证--> B[服务网关] B -.抓取注册信息.-> C[服务注册中心] D[微服务A] -.注册/续约.-> C E[微服务...] -.注册/续约.-> C F[微服务N] -.注册/续约.-> C B --转发--> D B --转发--> E B --转发--> F

    2. Zuul网关

    2.1 实现

    • 创建一个Spring Boot项目,引入如下依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>com.fasterxml.jackson.dataformat</groupId>
                  <artifactId>jackson-dataformat-xml</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      </dependency>
      
    • 在启动类上添加@EnableZuulProxy注解,声明一个Zuul代理。

      @EnableZuulProxy
      @SpringBootApplication
      public class SclZuulApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(SclZuulApplication.class, args);
          }
      
      }
      
    • 编写配置文件application.yml

      server:
        port: 8500
      spring:
        application:
          name: scl-zuul # 服务名称
      eureka:
        client:
          service-url:
            defaultZone: http://root:123456@test1:8100/eureka-server1/eureka,http://root:123456@test2:8200/eureka-server2/eureka,http://root:123456@test3:8300/eureka-server3/eureka # 服务注册中心地址
      management:
        endpoint:
          health:
            show-details: always # 展示详细的健康检查信息
        endpoints:
          web:
            exposure:
              include: info,health,routes,hystrix.stream # 暴露actuator端点
      
    • 启动项目,请求url http://localhost:8500/微服务名称/url,zuul会自动转发到对应的微服务上。
      在这里插入图片描述

    2.2 路由配置详解

    • 自定义指定微服务的访问路径

      zuul.routes.微服务名称 = 指定路径

      zuul:
        routes:
          scl-eureka-client-consumer: /secc/**
      

      此时访问 http://localhost:8500/scl-eureka-client-consumer/consumer/infohttp://localhost:8500/secc/consumer/info 的结果一致。都会转发到scl-eureka-client-consumer对应的微服务上。

    • 忽略指定微服务

      zuul.ignored-services.需要忽略的微服务名称

      zuul:
        ignored-services: scl-eureka-client-provider
      

      此时访问 http://localhost:8500/scl-eureka-client-provider/consumer/info将无法进行转发,返回404

    • 忽略所有微服务,只路由指定微服务

      zuul:
        ignored-services: '*' # 使用'*'可忽略所有微服务
        routes:
          scl-eureka-client-consumer: /secc/**
      
    • 同时指定path和URL

      zuul:
        routes:
          # user-route为路由名称,可以任意起名
          user-route:
            url: http://localhost:8090 # 指定的url
            path: /user/**             # url对应的路径
      

      此时访问 http://localhost:8500/user/** 将会转发到 http://localhost:8090/**

      这种方式配置的路由不会作为HystrixCommand执行,也无法使用Ribbon进行负载均衡多个URL。

    • 同时指定path和URL,并且不破坏Zuul的Hystrix和Ribbon特性

      zuul:
        routes:
          # user-route为路由名称,可以任意起名
          user-route:
            path: /user/**              # url对应的路径
            service-id: 'a'        
      ribbon:
        eureka:
          enabled: false                # 为Ribbon禁用Eureka
      a:
        ribbon:
          listOfServers: localhost:8080,localhost:8090
      
      • 若指定的service-id已存在服务注册中心里,则后续的不需要配置,结果与示例1相同
      • 若指定的service-id不存在服务注册中心里,则后续的必须配置

    2.3 Zuul过滤器

    Zuul中定义了4中标准过滤器

    • PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证等
    • ROUTING:这种过滤器建将请求路由到微服务。这种过滤器用于构建发送给微服务的请求
    • POST:这种过滤器在路由到微服务之后执行。可为响应添加Header、收集统计信息和指标等
    • ERROR:在其他阶段发生错误时执行该过滤器

    本文可实现一个简单的Zuul过滤器

    • 实现一个自定义的Zuul过滤器

      public class PreRequestFilter extends ZuulFilter {
      
          private static final Logger log = LoggerFactory.getLogger(PreRequestFilter.class);
      
          @Override
          public String filterType() {
              // 过滤器类型,可返回pre、routing、post、error
              return "pre";
          }
      
          @Override
          public int filterOrder() {
              // 过滤器的执行顺序
              return 1;
          }
      
          @Override
          public boolean shouldFilter() {
              // 可通过一些业务逻辑判断是否要执行该过滤器。true执行,false不执行
              log.info("该请求将经过过滤器...");
              return true;
          }
      
          @Override
          public Object run() throws ZuulException {
              // 过滤器的具体逻辑
              log.info("进行身份认证等处理...");
              RequestContext ctx = RequestContext.getCurrentContext();
              HttpServletRequest request = ctx.getRequest();
              log.info("该请求信息为: " + request.getMethod() + " " + request.getRequestURL());
              return null;
          }
      }
      
    • 在启动类中添加如下内容

      @Bean
      public PreRequestFilter preRequestFilter() {
          return new PreRequestFilter();
      }
      
    • 启动项目,访问http://localhost:8500/微服务名称/url,会在控制台看见如下输出内容。表明自定义的Zuul过滤器被执行了。
      在这里插入图片描述

    2.4 为Zuul添加回退

    • 实现一个Zuul的回退类

      @Component
      public class ProviderFallback implements FallbackProvider {
          @Override
          public String getRoute() {
              // 指定为哪个微服务提供回退
              return "scl-eureka-client-provider";
          }
      
          @Override
          public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
              return new ClientHttpResponse() {
                  @Override
                  public HttpStatus getStatusCode() throws IOException {
                      // 回退的状态码
                      return HttpStatus.OK;
                  }
      
                  @Override
                  public int getRawStatusCode() throws IOException {
                      // 数字类型的状态码
                      return this.getStatusCode().value();
                  }
      
                  @Override
                  public String getStatusText() throws IOException {
                      // 状态文本
                      return this.getStatusCode().getReasonPhrase();
                  }
      
                  @Override
                  public void close() {
      
                  }
      
                  @Override
                  public InputStream getBody() throws IOException {
                      // 响应体
                      return new ByteArrayInputStream("用户微服务不可用,请稍后再试。".getBytes());
                  }
      
                  @Override
                  public HttpHeaders getHeaders() {
                      // 响应头
                      HttpHeaders headers = new HttpHeaders();
                      MediaType mt = new MediaType("application", "json", StandardCharsets.UTF_8);
                      headers.setContentType(mt);
                      return headers;
                  }
              };
          }
      }
      
    • 启动项目,并停止scl-eureka-client-provider对应的服务。然后访问http://localhost:8500/scl-eureka-client-consumer/consumer/info
      在这里插入图片描述

    2.5 使用Zuul聚合微服务

    客户端的一个操作可能需要请求多次微服务,如果只是通过Zuul进行转发,那么网络开销、耗费时长依然会很长。所以我们可以使用Zuul聚合微服务请求。做到客户端只发送一个请求给Zuul,由Zuul请求其他的微服务,将数据组织好后返回。

    本文利用RxJava结合Zuul实现微服务的聚合请求。

    • 需要利用OpenFeign请求其他的微服务,因此需要引入依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
    • 创建CustomerFeign接口,访问服务消费者接口

      @FeignClient(name = "scl-eureka-client-consumer", fallbackFactory = ConsumerFeignFallback.class)
      public interface ConsumerFeign {
      
          @GetMapping("/consumer/info")
          Map<String, String> get();
      
      }
      
      @Component
      class ConsumerFeignFallback implements FallbackFactory<ConsumerFeign> {
      
          @Override
          public ConsumerFeign create(Throwable throwable) {
              return () -> {
                  Map<String, String> map = new HashMap<>();
                  map.put("errcode", "500");
                  map.put("errmsg", "Consumer异常,请稍后重试");
                  return map;
              };
          }
      }
      
    • 创建ProviderFeign接口,访问服务提供者接口

      @FeignClient(name = "scl-eureka-client-provider", fallbackFactory = ProviderFeignFallback.class)
      public interface ProviderFeign {
      
          @GetMapping("/provider/info")
          Map<String, String> get();
      
      }
      
      @Component
      class ProviderFeignFallback implements FallbackFactory<ProviderFeign> {
      
          @Override
          public ProviderFeign create(Throwable throwable) {
              return () -> {
                  Map<String, String> map = new HashMap<>();
                  map.put("errcode", "500");
                  map.put("errmsg", "Provider异常,请稍后重试");
                  return map;
              };
          }
      }
      
    • 在启动类中添加@EnableHystrix@EnableFeignClients注解,支持Feign请求调用及回退

      @EnableHystrix
      @EnableZuulProxy
      @EnableFeignClients
      @SpringBootApplication
      public class SclZuulApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(SclZuulApplication.class, args);
          }
      
          @Bean
          public PreRequestFilter preRequestFilter() {
              return new PreRequestFilter();
          }
      
      }
      
    • 修改配置文件application.yml,添加如下配置,使Feign支持Hystrix进行熔断回退

      feign:
        hystrix:
          enabled: true
      
    • 实现一个聚合服务类

      @Service
      public class AggregationService {
      
          private final ConsumerFeign consumerFeign;
          private final ProviderFeign providerFeign;
      
          @Autowired
          public AggregationService(ConsumerFeign consumerFeign, ProviderFeign providerFeign) {
              this.consumerFeign = consumerFeign;
              this.providerFeign = providerFeign;
          }
      
          public Observable<Map> getByConsumer() {
              return Observable.create(observer -> {
                  Map<String, String> map = this.consumerFeign.get();
                  observer.onNext(map);
                  observer.onCompleted();
              });
          }
      
          public Observable<Map> getByProvider() {
              return Observable.create(observer -> {
                  Map<String, String> map = this.providerFeign.get();
                  observer.onNext(map);
                  observer.onCompleted();
              });
          }
      }
      
    • 创建一个Controller,聚合多个请求。

      @RestController
      @RequestMapping("/aggregate")
      public class AggregationController {
      
          private static final Logger log = LoggerFactory.getLogger(AggregationController.class);
      
          private final AggregationService aggregationService;
      
          @Autowired
          public AggregationController(AggregationService aggregationService) {
              this.aggregationService = aggregationService;
          }
      
          @GetMapping
          public DeferredResult<Map> aggregate() {
              Observable<Map> result = this.aggregateObservable();
              return this.toDeferredResult(result);
          }
      
          private DeferredResult<Map> toDeferredResult(Observable<Map> result) {
              DeferredResult<Map> deferredResult = new DeferredResult<>();
              result.subscribe(new Observer<Map>() {
                  @Override
                  public void onCompleted() {
                      log.info("执行完成...");
                  }
      
                  @Override
                  public void onError(Throwable e) {
                      log.info("执行失败:" + e.toString());
                  }
      
                  @Override
                  public void onNext(Map map) {
                      log.info("onNext...");
                      deferredResult.setResult(map);
                  }
              });
              return deferredResult;
          }
      
          private Observable<Map> aggregateObservable() {
              return Observable.zip(
                      this.aggregationService.getByConsumer(),
                      this.aggregationService.getByProvider(),
                      (consumer, provider) -> {
                          Map<String, Map<String, String>> map = new HashMap<>();
                          map.put("consumer", consumer);
                          map.put("provider", provider);
                          return map;
                      }
              );
          }
      }
      
    • 启动项目,访问http://localhost:8500/aggregate
      在这里插入图片描述

    • 停止服务提供者,再次访问http://localhost:8500/aggregate

      因为服务提供者停止,所以provider对应的数据在Zuul中进行熔断,返回Zuul中回退数据;服务消费者正常请求,在服务消费者请求服务提供者时熔断,返回服务消费者中回退数据。
      在这里插入图片描述

    • 由上述可知,服务聚合与Hystrix容错都正常

    2.6 Turbine监控

    之前已经利用Turbine+RabbitMQ+dashboard实现了聚合监控,因为也可将Zuul纳入监控中。只需将Zuul将Hystrix上报到RabbitMQ中接口。

    • 添加spring-cloud-netflix-hystrix-streamspring-cloud-starter-stream-rabbit依赖。完整依赖如下

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>com.fasterxml.jackson.dataformat</groupId>
                  <artifactId>jackson-dataformat-xml</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
      </dependency>
      
      
    • 在配置文件中添加RabbitMQ的连接信息。完整配置如下

      server:
        port: 8500
      spring:
        application:
          name: scl-zuul # 服务名称
        rabbitmq:
          host: 127.0.0.1
          port: 5672
          username: guest
          password: guest
      eureka:
        client:
          service-url:
            defaultZone: http://root:123456@test1:8100/eureka-server1/eureka,http://root:123456@test2:8200/eureka-server2/eureka,http://root:123456@test3:8300/eureka-server3/eureka # 服务注册中心地址
      management:
        endpoint:
          health:
            show-details: always
        endpoints:
          web:
            exposure:
              include: info,health,routes,hystrix.stream
      zuul:
        routes:
          scl-eureka-client-consumer: /secc/**
      feign:
        hystrix:
          enabled: true
      
    • 重启项目,访问http://localhost:8401/hystrix,在输入框输入Turbine的监控端口地址http://localhost:8401,然后点击Monitor Stream按钮,即可看到监控数据
      在这里插入图片描述

  • 相关阅读:
    Fortran学习记录1(Fortran数据类型)
    ABAQUS学习记录1——用户子程序综述
    Abaqus用户子程序umat的学习
    信号基础知识---线阵
    信号基础知识--FFT DFT
    信号基础知识
    服务器文件打压缩包下载(java)
    网页鼠标特效-点击漂浮文字
    jQuery父子页面之间元素、方法获取、调用
    常用数字与字母的正则表达式
  • 原文地址:https://www.cnblogs.com/jinjiyese153/p/13219622.html
Copyright © 2020-2023  润新知