Kotlin和Rust微服务基准测试


Kotlin和Rust微服务基准测试

微服务是现在业界常用的平台优化和业务解耦的方法,Java作为一门企业级语言在企业业务架构中了统治地位,Kotlin作为一个Java的优化语言今年内也广为采纳。Rust作为一门内存安全,高效的系统级语言是目前最流行的语言之一,那么在微服务开发中他们的表现如何呢?本文我们学习GoOut进行的一项基准测试,来验证Kotlin的,Ktor和Rust Actix的的微服务接口的压力测试。

概述

在2020年春季的GoOut 就打算用更轻量级的框架替换Spring-Tomcat体系架构,以支持未来的Kotlin微服务。为此他们决定使用最可行的框架实施概念验证微服务,并在整个过程中以基准测试为重点。虽然Kotlin是主要语言,但是也并发测试了Rust,Rust作为一个安全高性能的语言作为一个基准参考也有很大的意义。

想要衡量一种通常会编写的代码样式,一种是努力做到简单/惯用/可读,但要迅速完成,而不能进行广泛的概述。另一方面,希望包括生产级API错误处理(列出了各个参数问题的正确HTTP代码和JSON错误消息等)。除了高要求的性能和相关的延迟外,还希望在微服务实例的整个生命周期中捕获更多的操作特征,例如内存使用率,CPU使用率和效率。

在生产中,在相当高密度的Kubernetes集群中运行微服务,其中每个实例都必须受到CPU和内存的限制。为了模拟这种环境,并检查是否可以将趋势化的"无服务器"产品用于新服务。

本实践中对最简单的/city/v1/get端点进行了基准测试,该端点的工作是city在Elasticsearch中通过id查找region文档,查找关联的文档,并呈现响应JSON。它为区域(仅数十个)使用内部内存缓存,但没有为更多城市使用缓存。

为了减少从Elasticsearch提取文档时的带宽,所有实现都指示它(至少)剥离最大属性geometry。此属性仅在Elasticsearch端过滤中使用,微服务不使用。

基准测试

基准

基准由多个相同的运行者组成,用bench-3-impls.py为每个运行者每个派生实施Docker容器化。运行以实现及其顺序号命名,比如rs-actix-1表示Rust(Actix)实现的第一次运行,kt-ktor-3 为Kotlin(Ktor)的第三次运行。每次运行都使用test-image.py模拟微服务实例的前几分钟。运行的步骤:

Docker容器已启动,它测量了服务开始响应之前的时间,进行了一系列HTTP检查,以确保正确的响应和错误处理行为,HTTP负载是使用wrk通过一系列10秒间隔生成,并发让连接数逐步增加;wrk在每个步骤之后,都会从Docker API中收集指标:

前5个步骤仅使用一个并发连接进行预热,每个步骤都加倍并发连接计数,即并发数为2,4,8,...,1024,2048等。

最后,对所有的运行者使用Pygal库的render-tests.py绘制图形。单个实现的不同运行使用相同的颜色,从而可以对比检查其差异。

运行环境

使用Docker限制微服务容器的资源,这是典型的Kubernetes集群中的常见做法。使用--cpus=1.5,选项的等效CPU限制为1.5个CPU单元,即使实例可以访问计算机的所有4个CPU内核,在每个10秒的步骤中,它也只能使用15 CPU秒(理论上为40 CPU秒)。选择1.5的值足够小以确保测试的微服务成为基准测试期间的实际瓶颈,并且选择足够大的值以确定框架可以正确使用多个CPU内核。

内存限制为512 MiB。两种JVM托管的Kotlin实现均在OpenJDK 11中运行,配置 -XX:MaxRAMPercentage=75是唯一的选择。它应该允许JVM最多将75%的可用内存512 MiB分配给Java堆栈。

硬件

基准测试在同一区域中的2台Google Cloud Platform(GCP)Compute Engine中的虚拟机上运行。

第一个VPS,n2d-highcpu-4机器类型(4 AMD Epyc Rome vCPUs, 4 GB memory):运行在一个docker容器中的基准微服务和wrk负载生成器。

由于容器限制为1.5 CPU,并且wrk占用的内存少于剩余的2.5 CPU,因此它们不应相互影响。

确实,短高峰期间的最大CPU利用率从未超过70%。N2D机器类型在GCP甚至在整个云提供商上都具有最佳的性能/成本比。

第二台VPS,e2-custom机器类型(12个高密度vCPU,6 GB内存):

运行一个Dockerized Elasticsearch 7.8.0实例(微服务的存储后端)。短高峰期间的最大CPU利用率从未超过80%,测试扩大了Elasticsearch CPU内核的数量,直到不再成为瓶颈为止,最终达到12。

运行负荷

actix-rt

最初在Vert.x中实现了Kotlin概念验证,但事实证明,没有一个团队成员热衷于该框架建议的编程范例(这可能是一个主观的问题),因此没有进一步进行。

locations-kt-http4k

语言:Kotlin v1.3.72

框架:Http4k v3.163.0和Undertow 服务器引擎v2.0.22

编程模型:经典同步(线程和阻塞I O调用)

Http4k位置服务为GoOut的生产实现,因此与诸如Swagger服务之类的其他功能一起最为完善。

Http4k 允许从多个服务器后端中进行选择,因此首先需要进行资格测试并选择性能最佳的引擎。

基准测试的一个很好的副作用是,发现了一个事业的的Netty的后端性能问题。该netty-updfix-*图显示了一些不错的延迟特性,已经包含此修复程序。它后来发布在http4k v3.259.0中(如果使用此后端,请确保进行升级)。

Apache HttpCore 4.4版(标记为apache4-*)是CPU效率最高的后端,并在峰值req/s性能上共享第一名。但是,随着并发连接数量的增加,它往往会占用过多的内存,并且一旦它们的数量达到(极端)1024,它就会被内存耗尽。Apache HttpCore 5.0版(apache-*)和一个具有较低套接字积压的变体(apache-q100-*)。也有同样的问题。同时,对于不被负载均衡器屏蔽的服务,建议使用不同的http4k后端。

本轮资格赛的获胜者是Undertow后端(undertow-*v3.163.0和undertow-upd-*v3.258.0),在高水位性能方面排名第一,但在其余指标中也带来了良好的综合结果,尤其是负载下的内存消耗。

在大多数指标中,Jetty后端(jetty-*)略微但始终落后于其他生产引擎。为了完整起见,还包括了仅供开发使用的SunHttp后端,基准测试证实了这一点。

locations-kt-ktor

语言:Kotlin v1.3.70

框架: v3.163.0和Netty服务器引擎v4.1.44

编程模型:异步(Kotlin协程)

Ktor也在GoOut使用,调整了/city/v1/get端点与其他功能的实现评价。仅使用此单个接口,它仍然是最不完整的实现。

它使用es-kotlin-wrapper-client将Elasticsearch客户端的内部异步API包装到Kotlin协程中。

Ktor还允许使用测试过的可插拔HTTP服务器引擎。这里的模式是相似的,Netty比Jetty更加高效和高效,因此有资格成为Ktor主基准测试的首选引擎。

locations-rs

语言:Rust v1.45.2

框架:Actix Web v2.0(后来发现v3.0的性能相同)

编程模型:异步(使用Tokio运行时v0.2.22,运行Rust异步/等待)

这是一个简单的locations-kt-http4kRust克隆紧随着的发展。

由于Actix Web 3.0 刚刚发 ,希望将其与v2.0性能进行比较。一对一的基准测试表明v3.0至少与v2.0相当。考虑到v3.0在安全方面已经取得了进步,这是个好消息。请注意,测试中必须删除Swagger支持,因为paperclip还没有移植到Actix 3.0,但这应该不会对运行时产生影响。本文使用原始的actix-web 2.0版本,但是结论同样适用于最新的v3.0。

基准测试版本使用便宜的性能技巧,显示:

与普通发布版本(标记为)相比lto = "fat",将codegen-units = 1放入Cargo.toml 的[profile.release]一节可将性能提高约17%,将效率提高约19%rs-actix-base-*。

在顶部添加RUSTFLAGS="-C target-cpu=skylake" 或RUSTFLAGS="-C target-cpu=znver2" 不会影响结果。可能是该工作负载无法从基本x86-64指令之外的任何SIMD指令中受益,并且/或者即使目标CPU不支持,它们也可以在运行时检测到并使用。Elfx86exts即使在基本版本中也可以检测到SS(S)E3,AVX(2)指令。

造成Rust效率另一个原因是改变了num_cpus库的位置用以检测Docker使用的软CPU限制。所有主要的异步运行时和线程池都依赖于num_cpus产生最有效数量的工作线程。在Actix中,由于每个逻辑CPU都会启动一个单独的执行程序,这一点得到了进一步的增强。给定1.5个内核的软CPU限制,此更改将runner数量从4个减少到2个。

最后一个琐事:不知道某些Rust异步运行时之间的不兼容性,所以最初使用了基于async-std的 Web框架。当使用Reqwest HTTP客户端的elasticsearch -rs时,问题就出现了,而后者又依赖于Tokio。曾经在Elasticsearch-rs 中将Reqwest粗略地移植到Surf。虽然有效,但这种做法无法持续,错过了Tide的错误处理API的一些知识。幸运的是,移植到Actix仅涉及重做上述错误处理。

结果

从Docker完成设置容器的时间到我们收到对的有效HTTP响应的时间来衡量启动时间。如果已配置的Elasticsearch服务器不可访问,则要求这些服务不启动。

Kotlin和Rust微服务基准测试

两种基于JVM的Kotlin服务都可以在3秒内正常启动,考虑到JVM必须加载自身,服务40+MiB fat JAR并执行初始化。

图中几乎看不到Actix的Rusty启动时间,因为它们只需要1-2毫秒,基本上是HTTP ping Elasticsearch所花费的时间。此类单位毫秒级的启动程序可用于在Google Cloud Run等平台上进行部署,该平台可将实例主动缩减为零,并且只有在收到请求后才能启动实例。

在Java世界中,诸如GraalVM之类的技术最终可能会克服JVM的限制。

Kotlin和Rust微服务基准测试

错误应记录在任何基准测试中,因此开始在我们的案例中,所有框架的行为都是示例性的,直到512个连接时,纯零错误,对于1024个并行连接的错误可忽略不计,对于2048个并行连接的错误率大约为3%(Kotlin)或1%(Rust)。

Kotlin和Rust微服务基准测试

随着并发连接数量的增加,每秒高水位标记成功请求是我们的主要指标之一。随着实例生存期的进行,从左到右结果:

几个最初的1连接预热步骤显示了基于JVM的Kotlin框架如何在对其进行JIT优化时逐渐获得性能(以下更多内容),最终与Rust实现达到临时的性能平衡。

最多4个连接,所有实现都受Elasticsearch实例的延迟约束,产生相似的吞吐量。

在8个连接处,Ktor和Http4k成为CPU绑定,得益于其更好的CPU效率,Ktor略高一些。Actix火箭。

首先是Http4k达到基线,保持在8到256个并发连接之间的3000-3200 req / s范围内。

Ktor在32-128个连接区域中达到~5000 req/s。

Actix的连接速度是后者的两倍以上,128和256个并发连接的速度约为11000 req/s。

达到峰值后,Actix和Ktor都变得过饱和,表现出稍微降低的性能。在Ktor案中,这种影响更为明显,并且发生得更早。

尽管存在非零错误率,但Http4k在极端连接数方面的行为却是相反的,达到其峰值成功约为3500req/s。这既源于效率的提高,又在较小程度上超过了Docker CPU的限制。

在图中共享相似形状的Actix和Ktor都基于异步执行器和低线程数,而模式稍有不同的Http4k使用更常规的高线程数和阻塞I/O。

Kotlin和Rust微服务基准测试

Kotlin和Rust微服务基准测试

Kotlin和Rust微服务基准测试

不出所料,延迟是吞吐量的倒数,百分之99位噪音最大,并且在某些Rust运行中显示出异常。最大的差异是在8-16连接范围内,其中Kotlin框架已经使可用的CPU饱和,但是Rust还有上升的空间。

图最右边的等待时间"截止"与模式的变化相对应:从饱和引起的等待时间到错误响应的出现。

Kotlin和Rust微服务基准测试

在这里,我们根据Docker的报告,测量了容器从每个基准测试步骤开始到结束的高水位内存使用情况(即不仅仅是瞬时内存使用情况)。

基于JVM的框架与Rust框架之间的比较在这里没有代表性,因为分配的Java堆除了实际用法外还取决于配置。

Kotlin和Rust微服务基准测试

与上述指标相同,但除以每秒成功请求的数量;给出每个请求的非常规单位兆秒。

通常,低连接数的值会更高,因为每个框架的基本内存占用量分布在较少的请求中。

可能会注意到Http4k和2个异步框架之间的形状略有不同:Http4k下降到〜0.05MiB⋅s并停留在那里,而Ktor尤其是Actix 随着等待时间的增加而再次上升。

Kotlin和Rust微服务基准测试

在预热步骤中,可以看到JVM致力于通过JIT优化基于Kotlin的代码库。即时优化与未优化代码的执行相结合,大约要多花费23 CPU秒,这是非常少的资源

尽管如此,在考虑高频连续部署或主动实例计数自动扩展时,基于JVM的微服务还是要记住这一点。JIT还可能与尚未优化的目标工作负载争夺资源。这种特性也应适用于其他基于JIT的引擎,例如Node.js的V8。

一旦达到2–8个连接以及更多连接,Kotlin的实现就会迅速使分配的CPU部分饱和,Rust在大约2步之后完成。

更正确的说,Http4k比Docker分配的15个CPU秒消耗了更多的CPU。这可能是导致该区域每秒请求数量增加的原因,但是仅CPU限制超过3.5%并不能完全说明性能提高了6%。

Kotlin和Rust微服务基准测试

消耗的CPU时间除以每秒成功请求数或CPU效率。让我们将图分为两半。

在从1到大约32个并发连接的上半段:

Http4k的效率Ktor由于JIT迅速提高,而且由于垃圾收集等簿记工作在更多执行的请求之间被稀释。

Http4k的效率略有提高,这在连接过量时几乎可以预期。

Ktor的行为符合预期,显示出效率降低。

Actix非常微妙的效率下降是由于它们的逆关系导致每秒约1000个请求的下降。

我最喜欢的指标在最后;成功服务的请求的运行总数(时间积分吞吐量)超过已用CPU时间的总和,或者简称为bang。上升意味着服务请求,而上升意味着消耗CPU。线斜率对应于CPU效率。

Kotlin和Rust微服务基准测试

如果采用每个实现的最远点,并将所消耗的总处理器时间除以已服务的请求总数,则得出每个请求的加权平均CPU时间,其中包括启动服务并使其加速所需的CPU时间。

如果允许自己进行公然简化,则可以将数量转换为更可理解的每十亿次请求的成本,假设示例价格为每vCPU每小时0.0275美元,并且不现实的完美资源利用。

Kotlin和Rust微服务基准测试

由于基准化实例仅运行几分钟,因此过多地考虑了启动成本。实际的微服务可能会在远低于其饱和点的负载下运行,因此需要更长的时间来服务相同数量的请求,因此只能部分补偿这种影响。

结论

在本文中,想通过扩展一组指标来超越常规基准,以显示更多经过测试的技术细微差别,并讨论其中的一些含义。希望将每个框架的最佳状态进行比较,因此我最终制定了3个资格基准,希望它们也可以自己为人们服务。所有的实现,工具和数据都是开源的,因此基准在理论上应该是任何人都可以复制的。同样,添加新的实现应该很容易。

展开阅读全文

页面更新:2024-04-24

标签:基准   测试   负载   容器   框架   实例   步骤   效率   数量   内存   指标   性能   错误   语言   时间   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top