Java精选面试题 (微信小程序): 5000+ 道面试题和选择题, 真实面经 , 简历模版 ,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计、大厂真题等,在线随时刷题!

打开网易新闻 查看精彩图片

Cloudflare公司之前宣布弃用Nginx,转用自研的新一代反向代理服务Pingora,并号称比Nginx更快、更高效、更安全。

Cloudflare公司今年年初正式对外宣布开源Pingora框架,并在GitHub上获得了17.5K的Star数。

‌下面通过Cloudfare官方网站的一篇文章来了解下Pingora比Nginx强在哪里。

简介

今天,我们很高兴有机会在此介绍Pingora,这是我们使用 Rust 在内部构建的新HTTP代理,它每天处理超过1万亿个请求,提高了我们的性能,并为 Cloudflare客户带来了许多新功能,同时只需要我们以前代理基础架构的三分之一的CPU和内存资源。

随着Cloudflare规模的扩大,我们已经超越了Nginx的处理能力。多年来它一直运作良好,但随着时间的推移,它在我们规模上的局限性意味着我们有必要构建一些新的东西。我们无法再获得我们所需要的性能,Nginx也没有我们在非常复杂的环境中所需要的功能。

许多Cloudflare客户和用户使用Cloudflare全球网络作为HTTP客户端(例如 Web浏览器、应用程序、物联网设备等)和服务器之间的代理。过去,对于浏览器和其他用户代理如何连接到我们的网络,我们已进行过许多讨论,我们开发了很多技术并实施了新协议(参见QUIC和http2优化)来使这段连接更高效。

今天,我们将关注这个等式的另一部分:代理我们的网络和互联网上服务器之间的流量的服务。这个代理服务为我们的CDN、Workers fetch、Tunnel、Stream、R2以及许多其他功能和产品提供了动力。

让我们研究为什么我们选择取代我们的旧版服务以及Pingora的开发过程,这是我们专门为Cloudflare的客户用例和规模而设计的新系统。

为什么要再建一个代理

作为世界上最大的免费CDN服务商,Cloudflare的代理端层运行这世界上最大Web请求,每天的客户端请求超万亿。之前Cloudflare的代理端层架构一度使用的基于内部定制化的Nginx服务器,但是无论在性能上,规模上以及功能层面都面临日益困窘的局面。

架构的限制损害了性能

Nginx worker(进程)架构对于我们的用例而言存在操作缺陷,这会损害我们的性能和效率。

首先,在Nginx中,每个请求只能由单个worker处理。这会导致所有CPU内核之间的负载不平衡,从而导致速度变慢。

由于这种请求进程锁定效应,执行CPU繁重或阻止IO任务的请求可能会减慢其他请求的速度。正如这些博客文章所表明的那样,我们已经花了很多时间来解决这些问题。

对于我们的用例来说,最关键的问题是糟糕的连接重用。我们的机器与原始服务器建立TCP连接,以代理HTTP请求。连接重用通过重用之前从连接池建立的连接,跳过新连接所需的TCP和TLS握手,来加快请求的TTFB(首字节时间)。

推荐划水摸鱼地址: https://www.yoodb.com/slack-off/home.html

但是,Nginx连接池与单个worker相对应。当请求到达某个worker时,它只能重用该worker内的连接。当我们添加更多Nginx worker以进行扩展时,我们的连接重用率会变得更差,因为连接分散在所有进程的更多孤立的池中。这导致更慢的TTFB以及需要维护更多连接,进而消耗我们和客户的资源(和金钱)。

打开网易新闻 查看精彩图片

正如在过去的博客文章中所提到的,我们为其中一些问题提供了解决方法。但如果我们能够解决根本问题:worker/进程模型,我们将自然而然地解决所有这些问题。

有些类型的功能难以添加

Nginx是一个非常好的Web服务器、负载均衡器或简单的网关。但Cloudflare 的作用远不止于此。我们过去常常围绕Nginx构建我们需要的所有功能,但要尽量避免与Nginx上游代码库有太多分歧,这并不容易。

例如,当重试请求/请求失败时,有时我们希望将请求发送到具有不同请求标头集的不同源服务器。但Nginx并不允许执行此操作。在这种情况下,我们需要花费时间和精力来解决Nginx的限制。

同时,我们被迫使用的编程语言并没有帮助缓解这些困难。Nginx纯粹是用C 语言编写的,这在设计上不是内存安全的。使用这样的第3方代码库非常容易出错。即使对于经验丰富的工程师来说,也很容易陷入内存安全问题,我们希望尽可能避免这些问题。

我们用来补充C语言的另一种语言是Lua。它的风险较小,但性能也较差。此外,在处理复杂的Lua代码和业务逻辑时,我们经常发现自己缺少静态类型。

而且Nginx社区也不是很活跃,开发往往是“闭门造车”。

选择建立我们自己的

在过去的几年里,随着我们的客户群和功能集的持续增长,我们持续评估了三种选择:

继续投资Nginx,向其付款进行定制,使其100%满足我们的需求。我们拥有所需的专业知识,但鉴于上述架构限制,需要付出大量努力才能以完全支持我们需求的方式重建它。

迁移到另一个第三方代理代码库。肯定有好的项目,比如envoy和其他一些。但这条道路意味着在几年内可能会重复同样的循环。

从头开始建立一个内部平台和框架。这一选择需要在工程方面进行最大的前期投资。

在过去的几年中,我们每个季度都会对这些选项进行评估。没有明显的公式来判断哪种选择是最好的。在几年的时间里,我们继续走阻力最小的道路,继续增强Nginx。然而,在某些情况下,建立自有代理的投资回报率似乎更值得。我们呼吁从头开始建立一个代理,并开始设计我们梦想中的代理应用程序

Pingora 项目

设计决定

为了打造一个每秒提供数百万次请求且快速、高效和安全的代理,我们必须首先做出一些重要的设计决定。

我们选择Rust作为项目的语言,因为它可以在不影响性能的情况下以内存安全的方式完成C语言可以做的事情。

尽管有一些很棒的现成第3方HTTP库,例如hyper,我们选择构建自己的库是因为我们希望最大限度地提高处理HTTP流量的灵活性,并确保我们可以按照自己的节奏进行创新。

在Cloudflare,我们处理整个互联网的流量。我们必须支持许多奇怪且不符合 RFC的HTTP流量案例。这是HTTP社区和Web中的一个常见困境,在严格遵循 HTTP规范,和适应潜在遗留客户端或服务器的广泛生态系统的细微差别之间存在矛盾和冲突,需要在其中作出艰难抉择。

HTTP状态码在RFC 9110中定义为一个三位整数,通常预期在100到599的范围内。Hyper就是这样一种实现。但是,许多服务器支持使用599到999之间的状态代码。我们为此功能创建了一个问题,探讨了争论的各个方面。虽然hyper 团队最终确实接受了这一更改,但他们有充分的理由拒绝这样的要求,而这只是我们需要支持的众多不合规行为案例之一。

为了满足Cloudflare在HTTP生态系统中的地位要求,我们需要一个稳健、宽容、可定制的HTTP库,该库可以在互联网的各种风险环境中生存,并支持各种不合规的用例。保证这一点的最佳方法就是实施我们自己的架构。

下一个设计决策关于我们的工作负载调度系统。我们选择多线程而不是多处理,以便轻松共享资源,尤其是连接池。我们认为还需要实施工作窃取来避免上面提到的某些类别的性能问题。Tokio异步运行时结果非常适合我们的需求。

最后,我们希望我们的项目直观且对开发人员友好。我们构建的不是最终产品,而是应该可以作为一个平台进行扩展,因为在它之上构建了更多的功能。

我们决定实施一个类似于Nginx/OpenResty的基于“请求生命周期”事件的可编程接口。例如,“请求过滤器”阶段允许开发人员在收到请求标头时运行代码来修改或拒绝请求。通过这种设计,我们可以清晰地分离我们的业务逻辑和通用代理逻辑。之前从事Nginx工作的开发人员可以轻松切换到Pingora并迅速提高工作效率。

Pingora在生产中更快

让我们快进到现在。Pingora处理几乎所有需要与源服务器交互的HTTP请求(例如缓存未命中),我们在此过程中收集了很多性能数据。

首先,让我们看看 Pingora 如何加快我们客户的流量。Pingora上的总体流量显示,TTFB中位数减少了5毫秒,第95个百分位数减少了80毫秒。这不是因为我们运行代码更快。甚至我们的旧服务也可以处理亚毫秒范围内的请求。

时间节省来自我们的新架构,它可以跨所有线程共享连接。这意味着更好的连接重用率,在TCP和TLS握手上花费的时间更少。

打开网易新闻 查看精彩图片

在所有客户中,与旧服务相比,Pingora每秒的新连接数只有三分之一。对于一个主要客户,它将连接重用率从87.1% 提高到99.92%,这将新连接减少了 160倍。更直观地说,通过切换到Pingora,我们每天为客户和用户节省了434年的握手时间。

更多功能 拥有工程师熟悉的开发人员友好界面,同时消除以前的限制,让我们能够更快地开发更多功能。像新协议这样的核心功能充当我们为客户提供更多产品的基石。

例如,我们能够在没有重大障碍的情况下向Pingora 添加HTTP/2上游支持。这使我们能够在不久之后向我们的客户提供gRPC。将相同的功能添加到Nginx将需要更多的工程工作,并且可能无法实现。

最近,我们宣布推出了Cache Reserve,其中Pingora使用R2存储作为缓存层。随着我们向Pingora添加更多功能,我们能够提供以前不可行的新产品。

更高效

在生产环境中,与我们的旧服务相比,Pingora在相同流量负载的情况下,消耗的CPU和内存减少了约70%和67%。节省来自几个因素。

与旧的Lua代码相比,我们的Rust代码运行效率更高。最重要的是,它们的架构也存在效率差异。例如,在Nginx/OpenResty中,当Lua代码想要访问 HTTP头时,它必须从Nginx C结构中读取它,分配一个Lua字符串,然后将其复制到Lua字符串中。之后,Lua还对其新字符串进行垃圾回收。在Pingora中,它只是一个直接的字符串访问。

多线程模型还使得跨请求共享数据更加高效。Nginx也有共享内存,但由于实施限制,每次共享内存访问都必须使用互斥锁,并且只能将字符串和数字放入共享内存。在Pingora中,大多数共享项目可以通过原子引用计数器后面的共享引用直接访问。

如上所述,CPU节省的另一个重要部分是减少了新的连接。与仅通过已建立的连接发送和接收数据相比,TLS握手成本显然更为高昂。

更安全

在我们这样的规模下,快速安全地发布功能十分困难。很难预测在每秒处理数百万个请求的分布式环境中可能发生的每个边缘情况。模糊测试和静态分析只能缓解这么多。Rust的内存安全语义保护我们免受未定义行为的影响,并让我们相信我们的服务将正确运行。

有了这些保证,我们可以更多地关注我们的服务更改将如何与其他服务或客户来源进行交互。我们能够以更高的节奏开发功能,而不用背负内存安全和难以诊断崩溃的问题。

当崩溃确实发生时,工程师需要花时间来诊断它是如何发生的以及是什么原因造成的。自Pingora创立以来,我们已经处理了数百万亿个请求,至今尚未因为我们的服务代码而崩溃。

事实上,Pingora崩溃是如此罕见,当我们遇到一个问题时,我们通常会发现不相关的问题。最近,我们的服务开始崩溃后不久,我们发现了一个内核错误。我们还在一些机器上发现了硬件问题,过去排除了由我们的软件引起的罕见内存错误,即使在几乎不可能进行重大调试之后也是如此。

总结

总而言之,我们已经建立了一个更快、更高效、更通用的内部代理,作为我们当前和未来产品的平台。

我们之后将介绍有关我们面临的问题和应用优化的更多技术细节,以及我们从构建Pingora并将其推出以支持互联网的重要部分的经验教训。同时还将介绍我们的开源计划。

来源:Nginx(ID:Nginx-study)

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有技术或摸鱼交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!在线摸鱼:https://www.yoodb.com/

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!

特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注

文章有帮助的话,点在看,转发吧!