Java不仅是血腥的企业,而且还是对延迟敏感的快速应用程序

我在Raiffeisenbank做算法交易。 这是银行业的一个相当具体的领域。 我们建立了一个交易平台,可以低延迟地预测。 应用程序的成功尤其取决于应用程序的速度,因此我们必须处理交易涉及的整个堆栈:专用网络通道,特殊硬件,操作系统设置和特殊JVM,当然还有应用程序本身。 我们不能停止专门优化应用程序本身-操作系统或网络设置同样重要。 这需要技术专长和知识才能理解数据如何流经整个堆栈,以及在何处可能存在延迟。


并非每个组织/银行都能负担得起此类软件的开发费用。 但是我很幸运,这样的项目是在Raiffeisenbank的围墙内启动的,而且我有一个合适的专业领域-我在Intel Moscow Compiler Laboratory从事代码性能方面的研究。 我们为C,C ++和Fortran制作了编译器。 在Raiffeisenbank,我改用Java。 如果早些时候我做了很多人使用的工具,那么现在我已经搬到了路障的另一端,不仅对代码性能,而且对整个应用程序堆栈的性能都进行了应用分析。 通常,研究性能问题的方法远远超出了代码范围,例如,在内核或网络设置中。

Java不适合高负载吗?


人们普遍认为Java不是非常适合开发高负载的系统。
这只能部分达成共识。 Java在许多方面都很漂亮。 如果将其与C ++之类的语言进行比较,则其潜在开销可能会更高,但有时C ++中功能相似的解决方案的运行速度可能会更慢。 有一些优化可以在Java中自动运行,但不能在C ++中运行,反之亦然。 考虑到Java JIT编译器之后的代码质量,我想相信性能将不如我在高峰期所能达到的好,最多不超过几倍。 但是与此同时,我得到了非常快的开发,出色的工具和各种各样的现成组件。

让我们面对现实:在C ++世界中,开发环境(IDE)远远落后于IntelliJ和Eclipse。 如果开发人员使用这些环境中的任何一种,则调试速度,发现错误和编写复杂逻辑的速度都将提高一个数量级。 结果,事实证明,将Java存放在正确的位置要容易得多,因此与在C ++中长时间进行所有操作相比,它足够快地工作。 最有趣的是,在编写竞争性代码时,Java和C ++中的同步方法非常相似:它们是OS级原语(例如, synced / std :: Mutex )或铁原语( Atomic * / std :: atomic <*> ) 。 看到这种相似性非常重要。

一般而言,我们正在开发非高负载应用程序))

高负载和低延迟应用程序之间有什么区别?


高负载一词并不能完全反映我们工作的细节-我们从事的是等待时间敏感的系统 。 有什么区别? 对于高负载的系统,重要的是要相当快地平均工作,充分利用硬件资源。 实际上,这意味着对系统的每百万分之一/千分之一../百万分之一的请求都可能非常缓慢地工作,因为我们专注于平均值,而并不总是考虑到用户会遭受严重的刹车。

我们从事的系统的延迟水平至关重要。 我们的任务是确保系统始终具有可预测的响应。 如果事件很少发生,则可能对延迟敏感的系统可能不会负担很重,但是需要保证响应时间。 但这并不能使他们的开发更加容易。 恰恰相反! 危险无处不在。 现代硬件和软件的绝大多数组件都以“平均”的良好工作为导向,即 用于吞吐量

至少采用数据结构。 我们使用哈希表,并且如果整个数据结构的重新哈希发生在关键路径上,这可能导致特定用户在单个请求上出现明显的刹车。 或JIT编译器-优化执行最频繁的代码模式,悲观执行很少的代码模式。 但是这种特殊情况的速度对于我们而言可能非常重要!

也许这段代码处理罕见的命令? 还是需要快速响应的一些异常市场情况? 我们试图确保我们的系统对这些潜在的罕见事件的反应花费一些可预测的时间,最好是非常短的时间。

如何实现可预测的反应时间?


这个问题不能用两个词来回答。 初步了解,重要的是要了解是否存在任何类型的同步- 同步,reentrantlockjava.util.concurrent中的某种东西。 通常,您必须在“忙碌的纺纱”上使用同步。 使用任何同步原语始终是一种折衷方案。 而且重要的是要了解这些同步原语如何工作以及它们所要进行的取舍。 评估特定代码段会产生多少垃圾也很重要。 对抗垃圾收集器的最佳方法是不触发它。 我们生成垃圾的次数越少,运行垃圾收集器的次数就越少,并且在没有干预的情况下系统工作的时间越长。

我们还使用各种不同的工具,使我们不仅可以分析平均指标。 我们必须非常仔细地分析系统每百分之一千分之一秒地运行的速度。 显然,这些指标将比中位数或平均值差。 但是,了解多少对我们非常重要。 像GrafanaPrometheusHDR直方图JMH之类的工具可以帮助显示这一点。

我可以删除不安全吗?


通常,您必须使用辩护者所说的无证API。 我说的是著名的不安全因素。 我相信不安全实际上已成为Java计算机公共API的一部分。 否认它是没有道理的。 不安全使用了许多我们都积极使用的项目。 如果我们拒绝了,那么这些项目将会如何? 他们要么使用旧版本的Java,要么再次不得不花费大量精力重写所有这些内容。 社区准备好了吗? 是否准备好可能会损失百分之几十的生产力? 而且最重要的是,以换取什么?

间接地,我的观点证实了从Unsafe中非常巧妙地删除了方法-在Java11中,从Unsafe中删除了最无用的方法。 我认为,直到使用Unsafe的所有项目中至少有一半移至其他项目,Unsafe才能以一种或另一种形式出现。

有一种观点:银行+ Java =血腥的骨干企业?


我们的团队没有这种恐怖。 在春季,我们大概写了十行,由我来说)))我们尽量不要使用大型技术。 我们更喜欢小巧,整洁,快速,以便我们能够实现,控制它,并在必要时对其进行修改。 后者对于系统(如我们的系统)非常重要,因为它们受制于非标准要求,这可能与框架的90%用户的要求有所不同。 在使用大型框架的情况下,我们将无法向社区传达我们的需求或独立纠正行为。

我认为,开发人员应该始终能够使用所有可用工具。 我从C ++来到Java世界,社区划分给那些开发虚拟机/编译器或虚拟机本身的运行时的人员以及应用程序开发人员,这让我感到非常震惊。 在标准JDK类代码中可以清楚地看到这一点。 JDK作者通常使用其他API。 潜在地,这意味着我们无法达到最佳性能。 总的来说,我相信使用相同的API编写标准库和应用程序代码是该平台成熟度的一个极好的指标。

还有一件事


我认为,对于所有开发人员来说,很重要的一点就是要知道,即使不是整个堆栈,它至少也要工作一半:Java代码,字节代码,虚拟机运行时内部组件和汇编程序,硬件,操作系统,网络。 这样可以更广泛地解决问题。
性能也值得一提。 重要的是不要集中在平均值上,而要始终关注中位数和较高的百分位数(最差的10/100/1000 / ...测量值)。

我将在5月30日在圣彼得堡举行的Java用户小组会议上讨论所有这一切。 与Sergei Melnikov的会面,仅此而已)))

我们要谈什么?

  1. 关于分析和使用标准的Linux和perf探查器:如何与它们一起使用,它们如何测量以及如何解释其结果。 这将是对常规配置的介绍,包括技巧,生活技巧,如何从探查器中挤出所有可能的信息,以便他们以最大的准确性和频率进行探查。
  2. 关于用于获取更详细的配置文件和查看罕见事件的配置文件的设备功能。 例如,当您的代码每100次运行慢10倍时。 没有探查器会告诉您这一点。 我们将使用标准的Linux内核机制编写小型分析器,并尝试查看一些罕见事件的分析器。

参加会议,这将是一个很棒的夜晚,将会有许多有关我们平台和我们喜欢的语言的有趣故事。

待会见;)

Source: https://habr.com/ru/post/zh-CN453688/


All Articles