在我们花费过去一年时间准备 Hibernate Reactive的第一个稳定版本 的过程中,我们试图回答一些我们遇到的问题。
查看性能和可扩展性
我们很好奇,是否可以在使用关系型数据库时展示何时切换到Hibernate Reactive可能是有益的:这既是为了我们自己的理解,也是为了帮助大家做出合理的决策。
我们发现要回答这样的问题并不简单,因为数据库是为经典的交互设计的;这在不同层面上都有影响 - 从低级别的协议设计到高级结构,例如高度期望的将工作单元封装在ACID事务中,这是通过定义执行“工作”的显式边界来建模的:事务从时间点A开始,在时间点B结束,在这个标记内同一连接上的所有操作都被视为在这个范围内的一部分。
这意味着例如,这样一个“时间段”的开头经常占用了物理连接,使其无法与其他响应式流(如响应式技术所能做到的)进行复用。
另一个影响是,这样的“时间标记”使得交互不适合与连续流一起工作:是的,你可以使用流和游标,但你仍然需要标记工作范围的开头和结束。
我在这里简化了这一点,以提供困难的一幅画面,因为协议和数据库功能的细节在不同的实现中差异很大(而且我还在学习这些细节) - 但这也是我们问题的一部分:我们现在在这个较低级别工作,在这个级别上这些细节很重要,而在使用标准JDBC和既定的交互模式工作时,这些细节并不重要。
因此,尽管Hibernate Reactive从Vertx SQL客户端库的完全响应式设计中受益,但一些交互模式仍然受到底层协议要求的限制;这意味着并非所有用例的实际性能都优于——甚至不同于使用基于Hibernate ORM、JDBC驱动程序和JDBC连接池的传统堆栈,因为存在可以以相同方式影响命令式和响应式堆栈的资源限制。
那么,什么时候使用Hibernate Reactive是有价值的呢?
我们在想,我们在这个领域的努力是否真的能展示出与使用经典Hibernate ORM相比的任何显著优势。
但在讨论性能考虑因素之前,有一个简单直接的答案:如果您的应用程序已经具有核心的响应性,那么与Hibernate Reactive这样的响应式框架集成无疑比尝试与传统的阻塞式Hibernate ORM集成要容易。
因此,如果您正在创建Vert.x应用程序或使用Quarkus及其提供的其他响应式组件(如RESTEasy Reactive),那么这个问题的答案很简单:使用Hibernate Reactive肯定更可取。
但现在让我们讨论性能指标,因为我们还想知道使用这种范式是否可以更高效。
Techempower测试
在这个来自流行的Techempower套件的基准测试中,一个Web端点在高负载下被反复击中,并测量吞吐量。
由于我们需要一个Web服务器,我们需要打包一个完整的应用程序;我们选择了Quarkus来运行它,因为我们非常熟悉它,并且因为它提供了Hibernate ORM "经典"和Hibernate Reactive的即插即用集成扩展,使我们能够比较在这个环境中对我们最重要的变量。
Quarkus还可以在"纯反应式"模式下运行——至少当与Hibernate Reactive结合时——这样应该可以让响应式组件利用没有执行器池,并避免线程调度的开销。
注意:虽然Techempower套件专注于吞吐量——我们更感兴趣的是实现合理的响应延迟,并探讨随着负载的增加,延迟如何变差。
在比较其他结果时,请记住这种测量方法的不同:包括Techempower网站上的数据:他们用分数(吞吐量)总结结果,这对于比较不同的框架是有用的(这是他们的目标),但通常我们更感兴趣的是技术能否在现实世界中部署,在那里最重要的是估计为了达到一定的SLA(服务水平协议)需要支付多少,以可接受的响应时间来衡量,在系统预期承受的负载下。
Techempower测试:多个查询
在Techempower的"查询"基准测试场景中,在每个Web请求中我们需要加载20个实体;不允许使用单个SELECT语句加载这20个对象:我们需要执行20个单独的、单独的加载操作。
当脱离上下文考虑时,此规则存在疑问,因为实际应用通过单次往返加载20个元素将表现得更好;然而,对这种场景进行基准测试仍很有趣,因为这种方法可以用来模拟非平凡的数据访问模式,同时编写简单的基准测试代码(这本身也存在疑问,但我们还是务实一些):在你的实际应用中,后续的加载操作可能是一些依赖于先前加载的商务逻辑的结果。例如,可能不知道需要加载哪个实体,直到处理完第一个实体,依此类推。这导致了一系列与数据库的IO交互,需要解决以满足单个Web请求;因此,尽管这个场景是人为的,但它仍然能够提供与其他框架和方法的有趣见解和比较点。
在x轴上,我们有每秒请求数量表示的负载;我们观察到随着负载的增加,两个堆栈的响应时间逐渐变慢。
因此,假设您有一个要求在10毫秒内提供响应的SLA;我们可以得出结论,Hibernate ORM“经典版”只要单个机器上的负载不超过20,000次/秒的限制,就能满足SLA要求——这相当不错,但超出这个范围后,响应的延迟开始迅速下降。
另一方面,使用Hibernate Reactive,即使将负载推高到约35,000次/秒,似乎也能在相同的10毫秒SLA内提供响应。这显然更好——并且差距明显。
这个特定结果是在我们的一台台式机上实现的;硬件的具体细节无关紧要,绝对数值也无关紧要:让我们专注于两个在相同硬件上运行、在相同条件下运行且使用相同PostgreSQL数据库的堆栈之间的比较,我们在Red Hat性能实验室也得到了类似的结果:当然,不同的硬件上的绝对数值会有所不同,但图形的形状是可比的,这表明这种方法在这个特定场景中是有益的。
为了运行这些测试,我们使用了Quarkus的最新版本:v. 2.4.0.CR1
。
Techempower测试:单个数据库查询
在这个Techempower的另一个场景中,我们需要从数据库中加载一个实体,然后进行响应编码。
如您所见,使用Quarkus的响应式堆栈编写此应用程序的好处并不明显。
因此,我们可以得出结论,尽管响应式编程有一些优点,但它并不能神奇地让每个应用程序都表现得更好。
尽管如此,响应式编程的好处不仅仅在于性能;例如,配置系统更容易,因为不需要确定如何配置执行器池,并且系统可以更好地对负载过大的情况做出反应:在传统的阻塞模式下,配置错误或突然意外的负载峰值可能会导致系统无响应。
此外,即使现在的差异很小,它似乎也表现略好——我们对这个年轻项目的进一步改进抱有乐观态度,相信在未来这些改进将变得更加明显,因为其他指标看起来很有希望。
一年来的改进
最后,让我们来看看 Hibernate Reactive 自从大约一年前首次被纳入 Quarkus 和 Techempower 以来是如何改进的。
在我撰写本文时,Techempower 仓库仍然依赖于这个相对较旧版本;我们很快需要更新它,以便他们的报告可以展示 Hibernate Reactive 1.0.0.Final 的实际数字。
注意:我们无法独享性能改进的功劳:虽然在此特定基准测试中,数据库相关操作是主要关注点,但改进的数字也是 Quarkus v2 相比于 Quarkus v1 的改进,以及使用 Vert.x v4 而不是 Vert.x v3 的证明。这两个框架也经历了显著的发展,改进的数字是三个堆栈的改进及其集成的结果。
未来改进
我们还没有完成:Hibernate ORM 的表现非常出色,作为一个成熟的技术,很容易找到多个集成选项进行实验,并且有大量的性能诊断工具可以与之集成;我们还积累了多年的经验,可以将它调整到最佳状态,并且能够快速地对其进行优化。
我们对 Hibernate Reactive 以及通用反应式编程的经验相对有限,因为它是一个相对较新的项目;我们从 Vert.x 团队得到了一些极好的帮助和指导,但仍有许多测试可以进行,每个新的基准测试都有可能使我们进一步改进年轻的 Hibernate Reactive 项目。
但我们现在满意的是,至少在某些场景下,Hibernate Reactive 确实是一个更好的选择,而且差异似乎足够大,足以让您学习这个新堆栈,并让我们继续前进,进一步改进它。
有一点是肯定的:随着技术的成熟,它们的性能和效率往往会有所提高;所以在这个阶段,我们有如此有希望的结果,我们当然很期待在不久的将来会做得更好——希望得到您的帮助和宝贵的反馈。
Quarkus Insights 采访
几天前,我和 Gavin King 在 Quarkus Insights 上接受了采访,更深入地讨论了这些主题。
现在可以在youtube上观看录制视频。
获取 Hibernate Reactive 1.0.0.Final
所有详细信息和文档都可以在hibernate.org 上的专用页面上找到,并且是最新的。
反馈、问题、想法?
要取得联系,请使用以下渠道
-
用户论坛或hibernate-user 流在 Zulip(使用问题、一般反馈)
-
问题跟踪器(错误报告、功能请求)
-
邮件列表或hibernate-reactive-dev 流在 Zulip(与开发相关的讨论)