Spring Cloud Zuul(路由网关)

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

官方原话:

Zuul is the front door for all requests from devices and web sites to 
the backend of the Netflix streaming application. 
As an edge service application, Zuul is built to enable dynamic routing, monitoring, 
resiliency and security. It also has the ability to route requests to 
multiple Amazon Auto Scaling Groups as appropriate.

github地址:https://github.com/Netflix/zuul
官方文档:https://github.com/Netflix/zuul/wiki

不用zuul,让客户端直接与各个微服务通讯,会有以下的问题:

1.客户端会多次请求不同的微服务,增加了客户端的复杂性。
2.存在跨域请求,在一定场景下处理相对复杂。
3.认证复杂,每个服务都需要独立认证。
4.难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通讯,那么重构将会很难实施。
5.某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。

Zuul能干什么?

zuul主要实现的功能就是API Gateway(api网关)的功能

作用:

1.易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。使用网关时客户端只与网关交互,降低客户端的调用逻辑的复杂度,同时网关也可以实现认证逻辑简化内部服务的之间相互调用的复杂度。
2.对不同客户端的支持及数据的聚合,如一个网站有web端,手机端,页面所需的数据有同有异,可以将数据整合或者裁剪,减少客户端的请求次数,比如BFF架构。
3.可以更好的对项目的微服务封装,可将项目的微服务统一封装在一个内网环境中,减少了客户端与各个微服务之间的交互次数。只通过网关提供服务,同时网关也可以对安全,认证,监控,防御单独强化。
4.易于认证。可在微服务网关上进行认证。然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。

架构图

image.png

zuul是一个网关和负载均衡器,在通过ribbon或者feign实现了客户端负载均衡之后,zuul在服务端实现负载均衡。zuul支持用任何JVM语言来编写规则和过滤条件。

服务网关是微服务架构中不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。
Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

集成Spring Cloud Zuul

引入Spring Cloud Zuul依赖和相关依赖,Eureka用来注册服务。

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

在启动类加入@EnableZuulProxy 启用zuul服务,@EnableEurekaClient注册服务

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

修改yml配置文件

server:
  port: 9100
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9090/eureka
spring:
  application:
    name: springcloud-zuul
zuul:
  routes:
     springcloud-eureka-client:
         path: /eureka-client/**
         serviceId: springcloud-eureka-client
     springcloud-feign-client:
         path: /feign-client/**
         serviceId: springcloud-feign-client

这里主要说下zuul的配置

image.png

可以看出routes下都是map集合
zuul.routes是一个路径列表,包含多个路由名,可以任意定义,一般用服务名来命名,每个路由需要path(访问路径)和serviceId(服务名)。

注:以上只是zuul配置中很小的一部分,更多的配置可以参考推荐文章

整体项目结构

Eureka服务端 端口号是9090
两个Eureka客户端 端口号是8081/8082 服务名springcloud-eureka-client
feign消费者客户端 端口号是8088 服务名为springcloud-feign-client
zuul路由网关 服务名为springcloud-zuul

启动服务进行测试

先启动服务端再启动客户端,客户端启动顺序没要求
访问http://localhost:9090服务注册中心

image.png

在服务注册中心可以看到,服务提供者和消费者都注册好了

访问在zuul定义的接口/infohttp://localhost:9100/eureka-client/client

image.png

再访问一次
image.png

zuul默认和Ribbon相结合实现服务端负载均衡

下面我们来访问下springcloud-feign-client的服务,访问http://localhost:9100/feign-client/info

image.png

也是可以正常访问的,feign具有客户端负载均衡,zuul具有服务端负载均衡,再访问一次,对应的端口肯定是8082
image.png

也许在这其中会报
com.netflix.zuul.exception.ZuulException: Hystrix Readed time out
Caused by: java.lang.RuntimeException: java.net.SocketTimeoutException: Read timed out
Caused by: java.net.SocketTimeoutException: Read timed out

这些根据报错信息,应该是zuul的调用等待时间超时

解决方法

yml配置文件中加入

zuul:
  host:
      connect-timeout-millis: 10000
      socket-timeout-millis: 60000

把等待时间设置得长一点就可以了

Zuul的熔断功能

Zuul也集成了Hystrix。假如你把springcloud-feign-client服务关掉了,它原本的熔断服务是不会生效的,因为客户端不是直接访问它,所以要在zuul上实现熔断功能

网上很多博客都是说直接实现ZuulFallbackProvider接口,不过在新版本的springcloud下是会报错的,找不到这个接口,只适用于Dalston及更低版本

原因:

Edgware.RC1版本中Spring cloud zuul针对于降级进行了升级,升级的内容主要是解决上面说到的当降级出现时,怎样在降级方法中获取具体的异常信息。
增加了一个 FallbackProvider接口,这个接口替换了ZuulFallbackProvider接口

这个接口有两个方法

public interface FallbackProvider {
    String getRoute();
    ClientHttpResponse fallbackResponse(String route, Throwable cause);
}

getRoute()主要是表明是为哪个微服务提供回退,返回*表示为所有微服务提供回退
Throwable参数把异常信息也当作参数传进来了,在处理熔断时可以输出更详细的信息。
fallbackResponse()为进入熔断器功能时执行的逻辑,返回中断信息给消费者客户端

需要实现ClientHttpResponse接口中的方法
getStatusCode()返回响应的HTTP状态代码
getRawStatusCode()返回HTTP状态代码
getStatusText()返回响应的HTTP状态文本
close()关闭方法,一般不用写任何代码
getBody()响应体,也就是熔断的返回内容
getHeaders()设置返回体的头部
具体可以参考:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/client/ClientHttpResponse.html

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

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            //返回响应的HTTP状态代码
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            // 返回HTTP状态代码
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }
            // 返回响应的HTTP状态文本
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {
            }
            // 响应体
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream((route+"服务不可用!").getBytes());
            }
            // 设置header
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers=new HttpHeaders();
                MediaType mt=new MediaType("application","json", Charset.forName("UTF-8"));
                headers.setContentType(mt);
                return headers;
            }
        };

    }
}

现在把服务springcloud-feign-client停掉之后,访问
http://localhost:9100/feign-client/info

image.png

过滤器是Zuul的核心组件

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
1.PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
2.ROUTING:这种过滤器将请求路由到微服务,在路由请求时候被调用。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClientNetfilx Ribbon请求微服务。
3.POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的
HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
4.ERROR:在其他阶段发生错误时执行该过滤器。

zuul中默认实现的filter

image.png

数字越大,优先级越低
image.png

路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由route类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发。所以说,过滤器可以说是zuul实现api网关功能最核心的部件,每一个进入zuulhttp请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。

总结:

Zuul包含了对请求的路由和过滤两个功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。
对于zuul过滤器实际使用可以参考
https://www.cnblogs.com/a8457013/p/8352349.html

推荐

关于zuul的更多配置和使用的可以参考:http://huan1993.iteye.com/blog/2424676

推荐阅读更多精彩内容