https://www.novatec-gmbh.de/en/blog/api-gateways-an-evaluation-of-zuul-2/

API Gateways – also known as Edge Service – are a fundamental part of a cloud-native microservice architecture. They represent the central access point for all requests for the backend services. In this blog post we evaluate Zuul 2, the open source Gateway solution of Netflix. Using specific code examples, we show how to create your own Zuul 2-based Edge Service.

Netflix Zuul 1 and Zuul 2

Founded in the late 90s Netflix moved from being an online DVD-by-mail service to offer
a streaming application service starting by 2007 [NeStat]. By now almost everyone owns or at least
shares a Netflix account. The chart below shows the number of households subscribing to Netflix.

Figure 1 Number of households subscribing to Netflix in millions [NeStat]

To offer such a reliable experience Netflix maintains and operates a complex
intertwined system of software components. The Netflix API is the front door to that system,
supporting over 1,000 different device types and handling over 50,000 requests per second
during peak hours [NetZu]. In order to handle the complexity of this system Netflix developed
their own solution for particular problems. One of these problems is how to handle HTTP requests
from different devices. How do different clients communicate with the back-end services without
exploiting the entire back-end infrastructure? Thus, Netflix developed Zuul as a solution for these problems.

In 2013 Netflix announced Zuul 1 as their implementation of an API Gateway.
Within a microservice architecture, the API Gateway pattern suggests a wrapper around
your microservice landscape in order to expose a single API to the client thus providing
a single-entry point to your back-end infrastructure. The Gateway may  also expose
different kinds of APIs respective to different clients connecting to your application.
Additionally, an API Gateway may implement cross-cutting concerns such as authorization,
analytics and so on. By now API Gateways are a common architectural pattern
to proxy requests to their origins and secure microservices within a cloud environment.
Zuul 1’s popularity increased after Spring integrated it into their Cloud Framework and
made it therefore easily accessible to the broad community.

In order to get a better understanding about Zuul 1, Figure 2 gives a brief overview of its
application within the Netflix Cloud.

Figure 2 Zuul 1 within the Netflix Cloud [NCldZ]

Zuul serves as an Edge Service executing different tasks such as request routing, analytics,
decline bad requests, authorization, and stress testing. Filters provide all these functionalities.

To get a better understanding of Filters the following Figure shows the request life cycle of HTTP requests.

Figure 3 Request Life cycle of incoming HTTP Requests handled by Zuul’s filters

Figure 3 shows different Filter types. Pre-Filters are executed before the HTTP request is routed to
the origin (Netflix’s synonym of back-end services). Routing-Filters are responsible for
routing HTTP requests to their origin and also handle outgoing responses from the origin.
Post-Filters perform tasks such as analytics or customization of HTTP headers.

Why did Netflix build Zuul 2?

Motivation

The main disadvantage of Zuul 1 is that calls to the API Gateway are all blocking.
If there is an HTTP request to the API Gateway, the calling thread will be blocked until
the client receives a response from the server. To take care of this Netflix needed to adjust
Zuul 1’s architecture in order to support non-blocking asynchronous request/response lifecycles.
Additionally, Netflix intended that Zuul 2 should support HTTP 2 as well as
WebSockets which Zuul 1 doesn’t. As a drawback there is no backwards compatibility
between Zuul 1 and Zuul 2 therefore you will have to rewrite your application
if you have integrated Zuul 1 and want to transition to Zuul 2.

In order to adjust Zuul 1 for their needs Netflix has made major architectural changes to
their Cloud Gateway which resulted in the newer Zuul 2. It still does the same as
his predecessor Zuul 1 such as request routing, authorization and analytics but with minor changes.

One of the primary advantages of Zuul 2 is that it now provides the capability
for devices and browsers to have a persistent connection to Netflix’s cloud services.
This emerged due to running Zuul 2 in production for several months. Additionally,
the execution of synchronous and asynchronous Filters is a huge benefit.
Understandably, Netflix’s intention was to improve performance of major tasks
such as connection scaling. More information may be found in this article.

What has changed

Unlike his predecessor, the Spring Cloud Framework does not integrate Zuul 2.
The implementation of Filters have slightly changed due to the transition from a synchronous blocking
system to an asynchronous non-blocking framework. In the following sections
we discuss the changes and how Zuul 1 has changed.

Netty

The Netty framework provides easy development of network applications such as
protocol servers and clients. It greatly simplifies and streamlines network programming
such as TCP and UDP socket server development. It is well documented and supported by a great community. Additionally, Netty enables bi-directional connections thus client and server can communicate without any interruptions. Otherwise the client (usually) needs to initiate a Two way-Handshake in order communicate with the server.

A Two way-Handshake protocol refers to a procedure where the client makes a request
to the server for enabling a connection. The server then responds with an acknowledgment
which the client accepts or declines. To take advantage of the previously mentioned benefits,
Netflix integrated Netty into Zuul 1’s architecture.

In Figure 4 you see a high-level architecture diagram of Netflix Zuul 2 with Netty.

Figure 4 High level architecture of Netflix Zuul 2 with Netty

The Netty handlers on the front and back of the Filters are responsible for handling the network protocol, web server, connection management and proxy work. With those inner workings abstracted away, the Filters do all of the heavy lifting [NzZuul].

Filters

Zuul 2 supports the following filter types:

  • Inbound Filters are the same as Pre-Filters and implement features such as authorization or manipulating the HTTP request headers.
  • Endpoint Filters either return a static response or proxy a request to the appropriate origin. Endpoint Filters are executed after Inbound Filters.
  • Outbound Filters implement the same logic as Post Filters and are executed after Endpoint Filters. Outbound Filters can be used to perform metrics or customizing the response.

Due to the changes Netflix made to their API Gateway each type of Filter may be implemented as a synchronous Filter or asynchronous Filter.

Zuul 1 had a major disadvantage respective to Zuul 2 being a blocking synchronous Gateway. Thus, being a choke point between a client and your back-end services. Netflix’s Zuul 2 is therefore more powerful respective to the possibility that you can decide if you want to implement asynchronous Filters or not.

Code Example

In the following chapter we will show an example how Zuul 2 is implemented and used as a reverse-proxy Gateway. All mentioned code examples can be found in GitHub.

Figure 5 shows a brief description of the following scenario. The Gateway (Zuul 2) will receive a HTTP request from a Client and copy the value of a Cookie to the Authorization header. If the HTTP request does not provide a Cookie, we route the request to a special Endpoint Filter which will return a Bad Request response. The Endpoint Filter is part of the API Gateway. Otherwise the Gateway proxies the request to one of the back-end services userscomments or images.

Figure 5 Scenario of given example

Dependencies

To use Zuul 2 you have to get the correct dependency which is available in the Netflix Zuul wiki. For Maven you may use the following dependency inside your pom.xml [DNet]

1
2
3
4
5

<dependency>
    <groupId>com.netflix.zuul</groupId>
    <artifactId>zuul-core</artifactId>
    <version>2.1.2</version>
</dependency>

and for Gradle [DNet]

1
compile "com.netflix.zuul:zuul-core:2.1.2"

Setup Netty

Since Netflix uses the Netty framework to provide an asynchronous style of implementation you need to setup a Netty server on your own. Although Netflix provided a sample setup within their Git repository. You may decide to come up with your own implementation of a Netty server.

As it’s still a complex and extensive framework we will not cover to explain the setup of a Netty server instance in this post. You are free to visit the Netty page and work out your own solution, use Netflix’s example or have a look at our example solution which is accessible by a Git repository.

Authorization Filter

In Zuul 2 Filters are implemented in Groovy but Zuul does support any other JVM-based language.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

package info.novatec.zuul2.filters.inbound
import com.netflix.zuul.filters.http.HttpInboundSyncFilter
import com.netflix.zuul.message.http.HttpRequestMessage
import info.novatec.zuul2.filters.endpoint.BadRequestEndpoint
import io.netty.handler.codec.http.cookie.Cookie
import org.apache.http.HttpHeaders
/**
* HttpInboundSyncFilter that extracts the 'customer-Id' cookie and adds its value to the
* request's Authorization header.
*/
class AuthorizationFilter extends HttpInboundSyncFilter {
    @Override
    HttpRequestMessage apply(HttpRequestMessage request) {
        Cookie cookie = request.parseCookies().getFirst("customer-Id")
        if (!cookie?.value()?.trim()) {
            request.getContext().setEndpoint(BadRequestEndpoint.class.getCanonicalName())
        } else {
            request.getHeaders().add(HttpHeaders.AUTHORIZATION, cookie.value())
            return request
        }
    }
    @Override
    int filterOrder() {
        return 1
    }
    @Override
    boolean shouldFilter(HttpRequestMessage request) {
        return true
    }
}

As mentioned in section Netflix Zuul 1 and Zuul 2, Inbound-Filter are executed first within the Request Lifecycle. In case you have more than one Inbound-Filter that needs to be executed you can also define an execution order inside the filterOrder() method. The custom AuthorizationFilter extends the Zuul class HttpInboundSyncFilter. By doing this you have to overwrite three methods apply()shouldFilter()and filterOrder().

filterOrder() returns an Integer value which defines the index of execution for this particular Filter.
There may be requests that do not need the application of specific Filters. Therefore, shouldFilter()implements predicates in order to verify the application of the Filter. If the method body is empty for every Filter then all Filters apply consecutively on every incoming requests. apply() implements the main functionality of a Filter.

In this example we apply a Filter on an HTTP request to copy the value of a Cookie and pass it into an Authorization Header. In line 17 we extract the Cookie. If there is no Cookie in the request we set our Endpoint to be BadRequestEndpointFilter in order to return a Bad Request response with status code 400.

Finally, in line 21 we add the retrieved customer-Id to the Authorization Header of the request.

The return type of the apply() method depends on the type of Filter you would like to implement. In case of an Outbound Filter which modifies responses from an origin you will need to change the return type to HttpResponseMessage and consecutively receive an HttpResponse object as a parameter instead of an HttpRequest object.

You may have noticed that we used the HttpInboundSyncFilter class to extend our AuthorizationFilterclass therefore implementing a synchronous Filter. Again, we emphazise that due to the new architecture of Zuul 2 you also have the possibility to extend from HttpInboundFilter class and therefore implement an asynchronous approach.

Bad Request Endpoint Filter

In this section we provide an Endpoint-Filter which returns a Bad Request as a response if no Cookie exists in the HTTP request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package info.novatec.zuul2.filters.endpoint
import com.netflix.zuul.filters.http.HttpSyncEndpoint
import com.netflix.zuul.message.http.HttpRequestMessage
import com.netflix.zuul.message.http.HttpResponseMessage
import com.netflix.zuul.message.http.HttpResponseMessageImpl
import org.apache.http.HttpStatus
class BadRequestEndpoint extends HttpSyncEndpoint {
    @Override
    HttpResponseMessage apply(HttpRequestMessage request) {
        HttpResponseMessage response = new HttpResponseMessageImpl(request.getContext(), request, HttpStatus.SC_BAD_REQUEST)
        response.finishBufferedBodyIfIncomplete()
        return response
    }
}

Endpoint Filters are executed after Inbound Filters and are responsible for routing requests to their origin. In this example we have used an Endpoint Filter to return a Bad Request in case that no Cookie is present or empty. In order to implement our Filter as an Endpoint Filter we extend the class HttpSyncEndpoint.

The apply() method will receive an HttpRequestMessage object as a parameter and return a response message. Then we create a new HTTP response message and set the appropriate status code.

Routes Filter

The Routes-Filter is responsible for routing of incoming HTTP requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package info.novatec.zuul2.filters.inbound
import com.netflix.zuul.context.SessionContext
import com.netflix.zuul.filters.http.HttpInboundSyncFilter
import com.netflix.zuul.message.http.HttpRequestMessage
import com.netflix.zuul.netty.filter.ZuulEndPointRunner
import info.novatec.zuul2.filters.endpoint.NotFoundEndpoint
/**
* Routing filter on base of HttpInboundSyncFilter.<br/>
* URIs of the proxied back-end services are defined in {@code application.properties}
*/
class Routes extends HttpInboundSyncFilter {
    @Override
    HttpRequestMessage apply(HttpRequestMessage request) {
        SessionContext context = request.getContext()
        switch (request.getPath()) {
            case "/images":
                context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME)
                context.setRouteVIP("images")
                break
            case "/comments":
                context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME)
                context.setRouteVIP("comments")
                break
            case "/users":
                context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME)
                context.setRouteVIP("users")
                break
            default:
                context.setEndpoint(NotFoundEndpoint.class.getCanonicalName())
        }
        return request
    }
    @Override
    int filterOrder() {
        return 0
    }
    @Override
    boolean shouldFilter(HttpRequestMessage request) {
        return true
    }
}

The Routes-Filter looks at the request path of an incoming HTTP request in order to decide which origin will be invoked. Thus, we use the built-in ProxyEndpoint Filter which is responsible for routing the request to the appropriate origin. If the requested path does not exist, we will route the request to the Endpoint Filter in order to return a Not Found status message.

Below is the implementation of the NotFoundEndpoint Filter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package info.novatec.zuul2.filters.endpoint
import com.netflix.zuul.filters.http.HttpSyncEndpoint
import com.netflix.zuul.message.http.HttpRequestMessage
import com.netflix.zuul.message.http.HttpResponseMessage
import com.netflix.zuul.message.http.HttpResponseMessageImpl
import org.apache.http.HttpStatus
class NotFoundEndpoint extends HttpSyncEndpoint {
    @Override
    HttpResponseMessage apply(HttpRequestMessage request) {
        HttpResponseMessage response = new HttpResponseMessageImpl(request.getContext(), request, HttpStatus.SC_NOT_FOUND)
        response.finishBufferedBodyIfIncomplete()
        return response
    }
}

There already have been issues reported to Netflix’s Github respective to a filed-based routing possibility such as in Zuul 1. According to Netflix routing may also be done by a routing configuration file in the future but it’s not implemented yet. Thus, the Routes Inbound Filter may not be necessary in the future.

Application Properties

In the application.properties we declare specific properties in order to configure our API Gateway.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

zuul.server.port.main=8887
# Configure filters
zuul.filters.root=src/main/groovy/info/novatec/zuul2/filters
zuul.filters.locations=${zuul.filters.root}/inbound,${zuul.filters.root}/outbound,${zuul.filters.root}/endpoint
zuul.filters.packages=com.netflix.zuul.filters.common
# Routing to proxied back-end services
users.ribbon.listOfServers=localhost:8081
users.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
comments.ribbon.listOfServers=localhost:8082
comments.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
images.ribbon.listOfServers=localhost:8083
images.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
# Deactivate Eureka
eureka.shouldFetchRegistry=false
eureka.validateInstanceId=false
@next=application-${@environment}.properties

In order to execute Filters, Zuuls’ FilterLoader has to know where to look for Filters. Therefore, the path to Filters must be declared. Thus, you need to set the root folder and accordingly the packages where the different Filters are implemented.

In Line 1 we state the port where our Gateway will be running, in this case the Gateway will be listening on port 8887. Line 9 to 14 define the routes where your back-end services are listening. Ribbon performs the back-end routing. To set a route to a back-end service you need to declare the following:

1
2

<name_of_service>.ribbon.listOfServers= <port>
<name_of_service>.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerLis

If you would like to disable Eureka as a Service Discovery you can set both eureka.shouldFetchRegistryand eureka.validateInstanceId to false.

For sake of simplicity we have used localhost to run the example on our local machines. In a real cloud-based environment you would usually integrated a service-discovery such as Eureka to fetch the URL of your back-end services instead of static URLs.

Outlook

The new version of Zuul has now been available for a couple of months. Implementing Zuul 2 as an API Gateway turned out to be a time-consuming task unlike Zuul 1 or Spring Cloud Gateway. Complexity increases by implementing your own Netty server. Additionally, this also adds more complexity to your application at the latest if you need to adjust your Netty server for additional needs.

Since the Spring Cloud Team integrated Zuul 1 into the Spring Cloud Framework one might assume that the Spring Cloud team considers to include Zuul 2 as well. According to a talk given by Spencer Gibbs on SpringOne in December 2017, the Spring Cloud team will not integrate Zuul 2 into the Spring ecosystem. However, the Spring Cloud Framework still supports Zuul 1 and thus, Zuul 1 remains as a viable solution. Particularly if you do not need your API Gateway to be asynchronous and non-blocking.

However, if you want your API Gateway to be asynchronous and non-blocking then you need to consider to either implement Zuul 2 or evaluate another solution. Though you want to try out Zuul 2 there a few aspects to consider.

There have been questions about missing, or precisely, desired features which are currently not available. One being the possibility to configure routes inside a configuration file. But this way of configuration only refers to Zuul 1. However, Zuul 2 is configured by a combination of an Inbound-Filter as well as a corresponding Endpoint-Filter as shown in the Routes-Filter example.

We already mentioned that Netflix intended to add file-based routing to Zuul 2 as a possibility to manage API routes, but it is yet unclear when they intended to add this feature. Further we explained that Zuul 2 comes with Netty as a network application framework. Thus, it is your responsibility as a developer to setup a Netty server in order to start your API Gateway. Despite the fact that you have to deal with Netty as an additional framework, this will certainly add more complexity to your application and implies more effort.

Lastly, as previously mentioned there is no backwards compatibility between both versions of Zuul. Thus, if you would like to use Zuul 2 in your application and already integrated Zuul 1 you have to consider rewriting your entire application.

Moreover, if you are already operating within a Spring Cloud environment but need an asynchronous and non-blocking Gateway there might be another possibility to consider: Spring Cloud Gateway is easy to integrate and to maintain.

Besides, Filters in Spring Cloud Gateway are route specific. As a consequence Filters apply merely on requests that match a defined route on the Filter. In Zuul 1 as well as in Zuul 2, Filters are global and thus executed consecutively to every request.

As a conclusion Zuul 2 remains a solid and viable solution as an API Gateway. If you would like to get more information about Zuul 1 and/or Zuul 2 you can follow this link.

Thanks for reading! If you have further questions feel free to leave a comment!

List of References

[NeStat]           https://www.statista.com/statistics/273885/quarterly-subscriber-numbers-of-netflix/

[NCldZ]            https://github.com/Netflix/zuul/wiki/How-We-Use-Zuul-At-Netflix

[NZarch]          https://github.com/Netflix/zuul/wiki/How-It-Works-2.0

[DNet]              https://github.com/Netflix/zuul/wiki/Getting-Started-2.0

[NetZu]            https://medium.com/netflix-techblog/announcing-zuul-edge-service-in-the-cloud-ab3af5be08ee

[NzZuul]           https://medium.com/netflix-techblog/open-sourcing-zuul-2-82ea476cb2b3

All links have been visited lastly on Thursday, 8th November 2018.

转载于:https://www.cnblogs.com/davidwang456/articles/10402471.html

API Gateways – An Evaluation of Zuul 2相关推荐

  1. Spring Cloud Zuul API 网关服务

    API 网关是一个更为智能的应用服务器,它的定义类似于面向对象设计模式中的 Facade 模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实 ...

  2. 微服务架构开发实战:如何集成Zuul和实现API网关?

    如何集成 Zuul 本节将基于Zuul来实现API网关.作为Spring Cloud 的一部分,集成Zuul会变得非常简单. Zuul简介 路由是微服务架构中必需的一部分,如""可 ...

  3. API网关是否真的起到了它该有的作用?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 以下内容来源 https://www.jianshu.co ...

  4. 你正在用的API网关,真的起到了它该有的作用?

    来源 | https://urlify.cn/ZfyyIv 最近看到一篇翻译一篇API网关的文章,介绍了其三种角色:API管理.集群入口控制.API网关模式,最后还讲了与服务网格的关系,通过此文可以更 ...

  5. 一文搞懂 Service Mesh 和 API Gateway 关系和区别

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 关于Service Mesh和API Gateway之间的关系,这个问题过去两年间经常被问起,社区也有不少文章和资 ...

  6. 国产最强开源 API 网关,没有之一,不接受任何反驳!

    以下文章来源方志朋的博客,回复"666"获面试宝典 " 这篇文章由刚哥授权分享,刚哥是 Splunk Information Technology 的架构师,Linked ...

  7. 谈谈微服务设计中的API网关模式

    来源:架构头条(ID: ArchFront) 原文:http://dwz.date/crrw 根据 Gartner 对微服务的定义:"微服务是范围狭窄.封装紧密.松散耦合.可独立部署且可独立 ...

  8. properties 配置回车_非常全面的讲解SpringCloud中Zuul网关原理及其配置,看它就够了!...

    本文同步Java知音社区,专注于Java 作者:kosamino http://www.cnblogs.com/jing99/p/11696192.html Zuul是spring cloud中的微服 ...

  9. Netflix Play API:我们为什么构建了一个演进式架构?

    在QCon SF大会上,Suudhan Rangarajan做了题为"Netflix Play API:我们为什么构建了一个演进式架构"的演讲.他演讲的要点包括:具有单一标识/职责 ...

最新文章

  1. mysql数据库计划是什么_计划备份mysql数据库
  2. 2017报计算机热不热,2017年五月份热吗?2017年五月天气热不热?
  3. WINDOWS 需要您的当前凭证
  4. java安全 ——JAAS(Java 认证和授权服务)开发指南
  5. C#调用WebKit内核
  6. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(1)
  7. 信息学奥赛一本通 1034:计算三角形面积 | OpenJudge NOI 1.3 17
  8. NOTEBOOK随笔
  9. 用配置还用Attribute来实现IoC?
  10. selenium java1.7_selenium-java(第一篇)
  11. C++的STL标准库学习(vector)
  12. LINQ 花3个礼拜的时间来弄清楚
  13. 【Tools】TCP/IP 测试工具——SocketTools
  14. 动态域名解析NAT版结合绿盾加密解密软件使用方法
  15. 毕业设计任务书参考地址
  16. Vendor使用:golang的vendor是个啥?
  17. 00003 不思议迷宫.0001:解密Lua脚本
  18. 【vivado IP核学习】DDS complier v6.0使用“SIN/COS LUT only”
  19. Robot Toolbox (一):Puma机器人仿真
  20. Halcon做二维码识别

热门文章

  1. echarts搭配MySQL_Echarts连接Mysql使用心得
  2. 《剑指offer》c++版本 8.二叉树的下一个结点
  3. 安卓实训项目:基于储存卡音乐播放器实训报告5.0
  4. c++ 冒泡排序_学习笔记-详解冒泡排序
  5. ajax被token拦截,vue中封装ajax请求,并且拦截请求在请求头中添加token
  6. spark 统计汉字字数_版面字数和实际字数一样吗
  7. 长春8中2021年高考 成绩查询,长春八中2018高考喜报成绩、本科重本上线人数情况...
  8. 三星手机Android9和10的区别,三星开始在Galaxy Note 9上测试Android 10
  9. ubuntu16.04 搭建Jenkins自动编译环境问题汇总
  10. opencv 标记有数字的区域