使用Zuul构建微服务网关(路由)

一、网关产生的背景

大型综合性网站往往由成百上千乃至上万个微服务组成,而每个微服务往往会依赖多个不同的微服务,不同微服务一般会有不同的网络地址。如一个放款微服务,可能需要调用多个微服务接口才能完成一次放款流程。一次放款的业务流程如下:

若让放款微服务直接与各个微服务系统通信,会有以下弊端:

  • 调用服务多次请求多个不同的微服务,增加了调用方的复杂性
  • 存在跨域请求,一定场景下处理起来相对复杂
  • 调用认证复杂,每个服务都需要独立认证
  • 难以重构,随着需求变动,项目迭代,可能需要重新划分微服务,或者拆分或者合并。若调用服务与各个微服务直接通信,调用服务也需要随之变动。
  • 每个微服务使用协议可能会不一样,直接访问困难

鉴于以上存在的问题,微服务网关便应运而生。微服务网关是介于客户端与服务端直接的中间层,所有来自客户端对不同服务端的请求都先经过网关。

微服务网关封装了各个微服务的内部调用结构,调用方只须跟网关交互,而无须直接调用特定微服务接口。这样开发就可以得到简化。不仅如此,而且还有以下优点:

  • 易于监控。微服务网关搜集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可在微服务网关进行认证,然后将请求转发到后端微服务,无须每个请求服务都认证
  • 减少了调用方与各个微服务的交互次数

二、Zuul简介

Zuul亦是Netflix开源的微服务网关,它和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一系列的过滤器,这些过滤器大致有以下功能:

  1. 身份认证与安全: 识别每个资源的验证要求,并不拒绝那些与要求不符的请求。
  2. 审查与监控: 在边缘位置追踪有意义的的数据和统计结果,从而产生精确的数据视图。
  3. 动态路由: 动态地将不同的请求路由到不同的后端集群。
  4. 压力测试: 逐渐增加指向集群的流量,以了解性能。
  5. 负载分配: 为每一种负载策略分配对应的容量,并启用超出限定值的请求。
  6. 静态响应处理: 在边缘位置直接建立部分响应,从而避免其转发到内部集群;
  7. 多区域弹性: 跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统边缘更贴近系统的使用者。

SpringCloud对Zuul进行了整合与增强。目前,Zuul使用的默认的HTTP客户端是Apache HTTPClient,也可以使用RedisClient或okhttp3.OkHttpClient。若想要使用它们,设置ribbon.restclient.enabled=true 或者 ribbon.okhttp.enabled=true即可

三、Zuul实现API网关

1、引入maven pom.xml依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

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

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

3、编写配置application.yml

server:
  port: 8088
spring:
  application:
    name: uws-data-zuul
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

四、 路由配置详解

默认情况下,Zuul网关代理会代理所有注册到Eureka Server的微服务。有时候我们需要对代理部分微服务,或者对url较为精确的控制,Zuul也提供了这种能力。

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

zuul:
  ignored-services: '*'
  routes:
    users: /myusers/**

2、忽略指定微服务,多个用逗号隔开

zuul:
  ignored-services: users,consumers

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

 zuul:
  routes:
    users-service: /myusers/**

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

 zuul:
  routes:
    users:
      path: /myusers/**
      serviceId: users_service

5、同时指定url和path

 zuul:
  routes:
    users:
      path: /myusers/**
      url: http://example.com/users_service

需要说明的是此种方式的配置,路由不会作为HystrixCommand执行,也不会使用Ribbon负载多个url,即Hystrix容错和Ribbon的负载均衡在这种配置下无效。下边5的配置可解决此问题

6、同时指定path和url,并且不破坏Zuul的Hystrix和Ribbon特性

zuul:
  routes:
    echo:
      path: /myusers/**
      service-Id: myusers-service
      strip-prefix: true

hystrix:
  command:
    myusers-service:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: ...

myusers-service:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    listOfServers: http://example1.com,http://example2.com
    ConnectTimeout: 1000
    ReadTimeout: 3000
    MaxTotalHttpConnections: 500
    MaxConnectionsPerHost: 100

另一种方法是指定服务路由并为serviceId配置Ribbon客户端,但是需要禁用Ribbon支持Eureka

zuul:
  routes:
    users:
      path: /myusers/**
      service-Id: users

ribbon:
  eureka:
    enabled: false

users:
  ribbon:
    listOfServers: example.com,google.com

7、使用正则表达式指定Zuul的匹配规则
借助PatternServiceRouteMapper实现微服务到映射路由的正则匹配

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
//PatternServiceRouteMapper(String servicePattern,String routePattern)
//servicePattern 指定微服务的正则
//routePattern 指定路由正则
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

可以把myusers-v1这个服务映射到路由到/v1/myusers/**这个路径

8、路由前缀
默认情况下前缀在Zuul转发前将去除,将zuul.stripPrefix=fasle,可以关闭此行为。

(1)访问Zuul的/api/myusers/101路径被转发到users微服务的/api/myusers/101路径

 zuul:
 	prefix: /api
  	stripPrefix: false
  routes:
    users: /myusers/**

(2)访问Zuul的/myusers/101路径被转发到users微服务的/myusers/101路径

 zuul:
  routes:
    users:
      path: /myusers/**
      stripPrefix: false

9、忽略某些路径
有时候,我们想让Zuul代理某个微服务,但又想保护某些敏感路径不被代理,可以使用ignoredPatterns,指定忽略的正则

 zuul:
  ignoredPatterns: /**/admin/**
  routes:
    users: /myusers/**

将users微服务映射到 /myusers/**路径,并且忽略包含/admin/的路径。

10、若你想要保持路由顺序,需要使用yml配置,使用property文件顺序会丢失

 zuul:
  routes:
    users:
      path: /myusers/**
    legacy:
      path: /**

五、Zuul的容错与回退

SpringCloud中,Zuul默认整合了Hystrix。当断路器打开时,通过实现FallbackProvider来响应回退,指定路由ID,提供ClientHttpResponse作为回退响应。
(1)application.yml配置回退服务

zuul:
  routes:
    customers: /customers/**

(2)回退类如下:

class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {//指定回退服务
        return "customers";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, final 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("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {//headers设定
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

若服务无法正常响应,就会返回响应体中的内容“fallback”

(3)如果要为所有路由提供默认回退,可以创建类型为FallbackProvider的bean,并让getRoute方法返回*或空,如下例所示:

class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

六、Zuul的安全与Header

一般来说,同一个系统中的服务之间可以共享Header。不过应该尽量防止让一些敏感的Header外泄。因此在很多场景下,需要通过路由指定一些敏感头列表。

1、敏感头设置

(1)为每个服务单独指定敏感头。
如下,为users微服务单独指定敏感Header。

zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders: Cookie,Set-Cookie,Authorization
      url: https://downstream

Cookie,Set-Cookie,Authorization这也是sensitiveHeaders的默认值。若没有需要,无须再去设置它

(2)发送全部Header
sensitiveHeaders好比是一个黑名单,默认值不为空。要使zuul发送所有头(忽略的头除外),必须将其显式设置为空列表。如果要将cookie或authorization headers传递到后端,则必须这样做。如下

 zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders:
      url: https://downstream

(3)设置全局头
通过设置zuul.sensitiveHeaders来设置敏感头,会覆盖全部敏感头的设置

 zuul:
  sensitiveHeaders:Cookie,Set-Cookie,Authorization 

2、忽略Header

可使用zuul.ignoredHeaders,丢弃一些头,不被传播到其它微服务中

 zuul:
  ignoredHeaders: Header1,Header2

默认情况下,zuul.ignoredHeaders的值时空的。但是如果Spring Security在项目的classpath中,那么其默认是就是。所以当Spring Security在项目的classpath中时候,同时又要使用下游微服务的Spring Security的Header时,可以将zuul.ignoreSecurityHeaders设置为false。

七、Zuul的过滤器

1、@EnableZuulProxy vs. @EnableZuulServer

过滤器是Zuul的核心组件,Zuul的大部分功能都是通过过滤器实现的。Spring Cloud Netflix包含了许多过滤器,这取决于使用哪个过滤器启用Zuul。@EnableZuulProxy 是 @EnableZuulServer的超集,换句话说,@EnableZuulProxy包含了@EnableZuulServer安装的所有的过滤器。“代理”中的附加过滤器设计目的是路由功能,若你想要空白的Zuul,你该使用@EnableZuulServer注解。

2、@EnableZuulServer包含的过滤器

@EnablezuulServer创建一个SimpleRouteLocator,它从SpringBoot配置文件加载路由定义。
启用该注解包含了以下4中过滤器类型

  • Pre filters:这种过滤器在请求被路由之前调用。
    • ServletDetectionFilter:通过设置布尔型的过滤器常量IS_DISPATCHER_SERVLET_REQUEST_KEY来检测请求是否通过Spring调度器。
    • FormBodyWrapperFilter:解析表单数据并为下游请求重新编码
    • DebugFilter:如果设置了debug请求参数,则将RequestContext.setDebugRouting() 和RequestContext.setDebugRequest()设置为true
  • Route filters:这种过滤将请求路由到微服务。
    • SendForwardFilter:使用servlet requestDispatcher转发请求。转发位置存储在RequestContext属性FilterConstants.FORWARD_TO_KEY键中。这对于转发到当前应用程序中的endpoints 很有用
  • Post filters:在路由到微服务以后执行。
    • SendResponseFilter:将代理请求的响应写入当前响应
  • Error filters:在发生错误时执行该过滤器
    • SendErrorFilter:如果requestContext.getthrowable()不为空,则转发到/error(默认情况下)。通过设置error.path属性,可以更改默认转发路径(/error)

3、@EnableZuulProxy包含的过滤器

启用 @EnableZuulProxy注解时,会创建一个 DiscoveryClientRouteLocator ,加载DiscoveryClient(如Eureka)路由定义和属性,从DiscoveryClient为每个serviceId一个路由,添加新服务时,路由也会被刷新。
除了前面2中的过滤器外,还安装了以下过滤器

  • Pre filters:
    • PreDecorationFilter:根据RouteLocator确定路由的位置和方式,它还为下游请求设置各种与代理相关的头
  • Route filters:
    • RibbonRoutingFilter:使用Ribbon、Hystrix和可插拔的HTTP Client客户端发送请求,在RequestContext 属性FilterConstants.SERVICE_ID_KEY中可以得到serviceId。这过滤器包含了不同的HTTP客户端:
      • Apache HttpClient:也是默认的HTTP客户端
      • Squareup OkHttpClient v3: 通过在类路径上设置com.squareup.okhttp3:okhttp库并设置Ribbonokhttp.enabled=true来启用。
      • Netflix Ribbon HTTP client: 通过设置ribbon.restclient.enabled=true启用。但此客户端有一些限制,包括它不支持“PATCH ”方法,但是它也有内置的重试。
    • SimpleHostRoutingFilter:通过Apache HttpClient向预定的URL发送请求,RequestContext.getRouteHost()包含发送的URLS

八、Zuul的高可用

Zuul做为微服务的网关,把外部请求路由到各个服务的,扮演着至关重要的作用。故在生产环境中高可用部署是必不可少的。

1、Zuul注册到了Eureka Server

Zuul注册到Eureka Server时,高可用就很简单了。只须将多个Zuul节点注册到Eureka Server上,就可以实习Zuul的高可用了。这种情况Zuul的高可用和一般的微服务高可用没有什么区别。

2、Zuul未注册Eureka Server

此种场景也更为常见,不可能让所有的客户端都注册到Eureka Server上。这时候,需要借助负载均衡器转发到每个Zuul节点来实现高可用,例如nginx、haproxy、F5等

参考:《Spring Cloud与Docker微服务架构实战》周立
参考: https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html#_router_and_filter_zuul

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值