spring cloud学习笔记(5)--统一网关zuul

微服务网关背景及简介

不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才能完
成一个业务需求。比如一个电影购票的收集APP,可能回调用电影分类微服务,用户微服
务,支付微服务等。如果客户端直接和微服务进行通信,会存在一下问题:

  1. 客户端会多次请求不同微服务,增加客户端的复杂性
  2. 存在跨域请求,在一定场景下处理相对复杂
  3. 认证复杂,每一个服务都需要独立认证
  4. 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通信,那么重构会难以实施
  5. 某些微服务可能使用了其他协议,直接访问有一定困难

上述问题,都可以借助微服务网管解决。微服务网管是介于客户端和服务器端之间的中间
层,所有的外部请求都会先经过微服务网关,架构演变成:

这样客户端只需要和网关交互,而无需直接调用特定微服务的接口,而且方便监控,易于认
证,减少客户端和各个微服务之间的交互次数

zuul微服务网关

Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul
组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求

Spring Cloud对Zuul进行了整合和增强。目前,Zuul使用的默认是Apache的HTTPClient,也可以使用Rest Client,可以设置ribbon.restclient.enabled=true.

编写一个简单微服务网关

添加依赖并在启动类上增加注解@EnableZuulProxy,声明一个zuul代理。该代理使用Ribbon来定位注册在 Eureka server中的微服务;同时,该代理还整合了Hystrix,从而实现了容错,所有经过 zuul的请求都会在 Hystrix命令中执行。

在启动类上增加注解@EnableZuulProxy,声明一个zuul代理

 配置文件application.yml如下:

扫描二维码关注公众号,回复: 4646886 查看本文章

启动项目(启动Eureka服务,两个用户微服务,一个订单微服务,和一个网关zull的微服务)

通过网关来调用用户的微服务

发现zuul会代理所有注册到eureka server的微服务,并使用ribbon做负载均衡,zuul的路由规则如下,可以访问地址:

http://localhost:8040/routes


http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**会被转发到serviceId对应的微服务如:

http://192.168.56.1:8040/microservice-provider-user/person/getIpAndPort 就会自动转发到用户微服务的getIpAndPort方法上

zuul还自动整合了hystrix,访问地址:http://localhost:8040/hystrix.stream

zuul聚合微服务

许多场景下,外部请求需要查询 zuul后端的多个微服务。举个例子,一个电影售票手机APP,在购票订单页上,既需要查询“电影微服务"获得电影相关信息,又需要查询“用户微服务"获得当前用户的信息。如果让手机端直接请求各个微服务(即使使用 zuul进行转发),那么网络开销、流量耗费、耗费时长可能都无法令我们满意。那么对于这种场景,可使用 zuul聚合微服务请求一一一手机 APP只需发送一个请求给 zuul,由 zuul请求用户微服务以及电影微服务,并组织好数据给手机 APP,使用这种方式,手机端只须发送一次请求即可,简化了客户端侧的开发;不仅如此,由于 zuul、用户微服务、电影微服务一般都在同一个局域网中,因此速度会非常快,效率会非常高。

看实例,其实就是在网关层加一个逻辑,同时请求多个微服务

package com.cym.springcloud.controller;

import com.cym.springcloud.model.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
public class AggregationController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/getPersonById/{id}")
    public Map<String, Person> getPersonById(@PathVariable Long id) throws Exception {
        Map<String, Person> dataMap = new HashMap<>();
        Person person = restTemplate.getForObject("http://microservice-provider-user/person/getPersonById/" + id, Person.class);
        Person orderPerson = restTemplate.getForObject("http://microservice-order/order/getPersonById/" + id, Person.class);
        dataMap.put("person", person);
        dataMap.put("orderPerson", orderPerson);
        return dataMap;
    }
}

然后在启动类中配置一个(RestTemplate的通信)

测试:

zuul的路由配置

前文已经编写了一个简单的 zuul网关,并让该网关代理了所有注册到 Eureka server的微服务。但在现实中可能只想让 zuul代理部分微服务,又或者需要对 URL进行更加精确的控制。

看这样子就实现指令url

  • 配置忽略指定微服务,只需在application.yml里加上如下配置(如果多个用逗号隔开)

禁用之后zuul并不会代理用户微服务了

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

  • 同时指定微服务的serviceId和对应路径

  • 同时指定path和url

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

  • 为Zuul添加映射前缀1

  • 为Zuul添加映射前缀2

  • 忽略某些路径

  • 本地转发

zuul过滤器使用及详解

过滤器(filter)是zuul的核心组件zuul大部分功能都是通过过滤器来实现的。 zuul中定义了4种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

  1. PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  2.  ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpCIient或 Netfilx Ribbon请求微服务
  3. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  4. ERROR:在其他阶段发生错误时执行该过滤器。

zuul请求的生命周期如下图

编写一个过滤器

增加过滤器类<PreRequestLogFilter>,需继承抽象类<ZuulFilter>,然后实现几个抽象方法 

public class PreRequestLogFilter extends ZuulFilter {
  private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);

  @Override
  public String filterType() {
    return FilterConstants.PRE_TYPE;
  }

  @Override
  public int filterOrder() {
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
  }

  @Override
  public boolean shouldFilter() {
    return true;
  }

  @Override
  public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();
    LOGGER.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
    return null;
  }
}

由代码可知,自定义的 zuul Filter需实现以下几个方法。

  • filterType:返回过滤器的类型。有 pre、 route、 post、 error等几种取值,分别对应上文的几种过滤器。详细可以参考 com.netflix.zuul.ZuulFilter.filterType()中的注释。
  • filter0rder:返回一个 int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。
  • shouldFilter:返回一个 boolean值来判断该过滤器是否要执行, true表示执行, false表示不执行。
  •  run:过滤器的具体逻辑。本例中让它打印了请求的 HTTP方法以及请求的地址

运行项目访问地址:http://localhost:8040/microservice-provider-user/getIpAndPort
可以看到zuul服务的后台正常打印了run方法里的日志

禁用zuul过滤器

Spring Cloud默认为Zuul编写并启用了一些过滤器,例如DebugFilter、FormBodyWrapperFilter等,这些过滤器都存放在spring-cloud-netflix-core这个jar包里,一些场景下,想要禁用掉部分过滤器,该怎么办呢?
只需在application.yml里设置zuul.<SimpleClassName>.<filterType>.disable=true
例如,要禁用上面我们写的过滤器,这样配置就行了:
zuul.PreRequestLogFilter.pre.disable=true

zuul的容错与回退

大家可以想一下如果zuul代理的后端微服务挂了会出现什么情况?
zuul默认已经整合了hystrix,也就是zuul也是可以利用hystrix做降级容错处理的,但是zuul监控的粒度是微服务级别,而不是某个API

编写zuul的降级回退类<MyFallbackProvider>如下:

package com.cym.springcloud.fallback;

import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

@Component
public class MyFallbackProvider implements FallbackProvider {
  @Override
  public String getRoute() {
    // 表明是为哪个微服务提供回退,*表示为所有微服务提供回退
    return "*";
  }

  @Override
  public ClientHttpResponse fallbackResponse(Throwable cause) {
    if (cause instanceof HystrixTimeoutException) {
      return response(HttpStatus.GATEWAY_TIMEOUT);
    } else {
      return this.fallbackResponse();
    }
  }

  @Override
  public ClientHttpResponse fallbackResponse() {
    return this.response(HttpStatus.INTERNAL_SERVER_ERROR);
  }

  private ClientHttpResponse response(final HttpStatus status) {
    return new ClientHttpResponse() {
      @Override
      public HttpStatus getStatusCode() throws IOException {
    	//fallback时的状态码
        return status;
      }

      @Override
      public int getRawStatusCode() throws IOException {
    	//数字类型的状态码,本例返回的其实就是200,详见HttpStatus
        return status.value();
      }

      @Override
      public String getStatusText() throws IOException {
    	//状态文本,本例返回的其实就是OK,详见HttpStatus
        return status.getReasonPhrase();
      }

      @Override
      public void close() {
      }

      @Override
      public InputStream getBody() throws IOException {
    	// 响应体
        return new ByteArrayInputStream("服务不可用,请稍后再试。".getBytes());
      }

      @Override
      public HttpHeaders getHeaders() {
        // headers设定
        HttpHeaders headers = new HttpHeaders();
        MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
        headers.setContentType(mt);
        return headers;
      }
    };
  }
}

关闭zuul代理的用户微服务,再运行本项目,访问地址:http://localhost:8040/microservice-provider-user/getIpAndPort,将会返回如下内容:

zuul的高可用

分两种场景讨论Zuul的高可用
1、Zuul客户端也注册到了Eureka Server上这种情况下,Zuul的高可用非常简单,只需将多个Zuul节点注册到Eureka Server上,就可实现Zuul的高可用。此时,Zuul的高可用与其他微服务的高可用没什么区别。见下图

当Zuul客户端也注册到Eureka Server上时,只需部署多个Zuul节点即可实现其高可用。Zuul客户端会自动从Eureka Server中查询Zuul Server的列表,并使用Ribbon负载均衡地请求Zuul集群。

2、Zuul客户端未注册到Eureka Server上

现实中,这种场景往往更常见,例如,Zuul客户端是一个手机APP——我们不可能让所有的手机终端都注册到Eureka Server上。这种情况下,我们可借助一个额外的负载均衡器来实现Zuul的高可用,例如Nginx、HAProxy、F5等。

 Zuul客户端将请求发送到负载均衡器,负载均衡器将请求转发到其代理的其中一个Zuul节点。这样,就可以实现Zuul的高可用。

猜你喜欢

转载自blog.csdn.net/qq_40368860/article/details/84726469