高频交易
高频交易是指从那些人们无法利用的极为短暂的市场变化中寻求获利的计算机化交易,比如,某种证券买入价和卖出价差价的微小变化,或者某只股票在不同交易所之间的微小价差。在高频交易中,自动化应用程序每天处理几亿个市场信号,在全球各地的交易所发送上千万个订单。为了保持业务竞争力,响应时间必须始终保持在微秒级,尤其是在黑天鹅异常事件等高峰期。
高频交易系统的典型系统结构一般是这样:金融交易信号将转换成内部市场数据格式(交易使用TCP、UDP等各种协议)和多种格式(如二进制、SBE、JSON、FIX等)。然后,这些标准化的消息被发送到算法服务器、统计引擎、UI、Log Server和各种数据库(缓存、文件或分布式数据库)。任何延迟都会带来都会带来高成本的结果。例如,根据根据旧的价格进行决策或下单太迟。为了获得微秒级的优势,大部分交易参与者都会投入高价硬件:一个超频液冷CPU的服务器池(2020年可以买56核、5.6GHz、1TB内存的服务器),组装在主交换数据中心、高端纳秒级网络交换机、专用跨洋线,甚至是微波网络。
常见的高频交易系统使用高度定制的Linux内核,并且带有操作系统旁路,这样数据就可以直接从网卡 "跳转" 到应用程序、基于IPC 进程间通信,甚至使用FPGA(可编程单用途芯片)。至于编程语言,一般首先想到的就是C++,事实上也确实是这个领域的天然选择。C++的最大优势就是运行速度快,最接近机器代码,而且是直接根据目标平台进行编译,具有高效稳定的特点。
使用Java代替C++
我们做了一个不同的选择。在过去14年里,我们在外汇算法交易领域用Java进行开发,并使用廉价的硬件代替昂贵的高端设备。
在一个团队小,资源有限以及熟练开发人员欠缺的工作环境,Java意味着我们可以快速进行软件迭代,因为Java生态系统比C系列具有更快的开发效率。可以在早上讨论改进措施,并在下午在生产中实施、测试和发布。
与需要几周甚至几个月软件更新时间的大型公司相比,这是一个关键优势。在这个领域,一个错误可以在几秒钟内抹去一整年的利润,因此不能在质量上妥协。我们使用了许多开源库和项目,实现了严格的敏捷开发环境,包括使用Jenkins、Maven、单元测试、夜间构建和Jira。通过Java,开发人员可以专注于业务逻辑,而不是像C++那样调试内存Coredump或跟指针打交道。而且,由于Java强大的内存管理,初级程序员也可以立即参与开发代码,并且风险可控。
只要有良好的设计模式和干净的编码习惯,就可以用Java达到C++的延迟。我们都知道,使Java成为软件开发强大和方便语言的原因,同时也是它的缺点的最主要的原因,那就是Java虚拟机(JVM)。
Java即时编译代码(Just in Time 编译器),意味着第一次遇到一些代码时,也可能产生编译延迟。Java管理内存的方式是通过在堆空间中分配内存块。每隔一段时间,它就会清理这个空间,删除旧的对象,为新的对象腾出空间。主要问题是,为了进行准确的统计,应用程序线程需要被瞬间 "冻结"。这个过程被称为垃圾收集(GC)。GC是低延迟应用程序开发人员放弃 Java 的主要原因。
市场上Java 虚拟机最常见和标准的是 Oracle Hotspot JVM,它在 Java 社区中被广泛使用,主要是出于历史原因。对于要求非常高的应用程序,Azul Systems 提供了一个很棒的替代方案,称为 Zing。Zing是Oracle Hotspot JVM一个强大的替代品。Zing解决了GC暂停和JIT编译问题。
让我们来研究使用Java的固有问题和可能的解决方案。
理解Java即时编译器
像C++这样的语言被称为编译语言,因为交付的代码完全是二进制的,可以直接在CPU上执行。PHP或Perl 被称为解释语言,因为解释器(安装在目标机器上)会边运行边编译每一行代码。
Java介于两者之间;它将代码编译成所谓的 Java 字节码,而字节码又可以在它认为合适的时候被编译成二进制。Java之所以不在启动时编译代码,与长期的性能优化有关。通过观察应用程序的运行情况,分析实时的方法调用和类的初始化,Java 会编译经常调用的部分代码。它甚至可能会根据经验做出一些假设(这部分代码永远不会被调用,或者这个对象永远是一个 String)。
因此,实际编译后的代码速度非常快,但依然有3个缺点。
1、一个方法需要被调用一定的次数来达到编译阈值,然后才能被优化和编译(这个限制是可以配置,但通常是10000 次左右的调用)。在此之前,未经优化的代码并没有以 "全速" 运行。Java在更快的编译和高质量的编译之间做了一个取舍(如果假设不对,会有重新编译的代价)。
2、当Java应用程序重启时,又回到了原点,必须等待再次达到这个阈值。
3、有些应用程序(比如我们的场景)有一些不频繁但很关键的方法,这些方法只会被调用少数几次,但当它们被调用时,需要极快的速度(想想看,一个风险或止损函数只有在紧急情况下才会被调用)。
Azul Zing通过让其JVM将编译后的方法和类的状态 "保存" 在它所谓的配置文件中来解决这些问题。这种名为 ReadyNow!® 的独特功能,意味着Java应用程序始终以最佳速度运行,即使在重新启动后也是如此。当使用现有的配置文件重新启动应用程序时,Azul JVM会立即调用其先前的结果并直接编译标注的的方法,从而解决了 Java 预热问题。
此外,可以在开发环境中建立一个配置文件,以模拟生产行为。然后,优化后的配置文件可以部署在生产环境中,因为所有的关键路径都被编译和优化了。Zing的延迟随着时间的推移保持相当稳定。百分位数分布表明,1%的时间里,Hotspot JVM产生的延迟是 Zing JVM的16倍。
解决垃圾收集(GC)暂停的问题
在垃圾收集过程中,整个应用程序可能会冻结几毫秒到几秒不等(延迟随着代码复杂度和堆大小而增加),更糟糕的是,你无法控制这种情况何时发生。虽然暂停一个应用程序几毫秒甚至几秒钟对于许多Java应用程序来说可能是可以接受的,但对于低延迟应用程序来说却是一场灾难,无论是汽车、航空航天、医疗还是金融领域。
GC的影响在Java开发者中是一个很大的话题;一个完整的垃圾收集通常被称为 "stop-the-world",因为它会冻结整个应用程序。
多年来,许多GC算法都试图在吞吐量(多少CPU用于实际的应用逻辑而不是垃圾收集)与 GC暂停之间做一个取舍。
自Java 9以来,G1 收集器一直是默认 GC,其主要思想是根据用户提供的时间目标来划分GC暂停时间。它通常提供较短的暂停时间,但代价是较低的吞吐量。此外,暂停时间会随着堆的大小而增加。Java提供了大量的设置来调整其垃圾收集(以及 JVM),从堆大小到收集算法,以及分配给GC的线程数。所以,看到Java应用程序配置了大量的自定义选项是很常见的。
很多开发者已经转向各种技术来完全避免GC。主要思路是,如果创建的对象少了,需要清除的对象就会变少。一个古老的技术是使用可重用对象的对象池。例如,一个数据库连接池将持有10个已打开的连接的引用,准备在需要时使用。
多线程通常需要锁,这会导致同步延迟和暂停(特别是当它们共享资源时)。一个流行的设计是一个环形缓冲队列系统,在一个无锁的设置中,有许多线程写和读。一些专家甚至选择完全自己实现 Java 内存管理,自己管理内存分配,虽然解决了一个问题,但却带来了更多的复杂性和风险。在这种情况下,显然应该考虑其他 JVM,于是我们决定尝试 Azul Zing JVM。很快,我们就实现了非常高的吞吐量,停顿可以忽略不计。
这是因为Zing使用了一个独特的收集器,叫做C4(Continuurrentously Concurrent Compacting Collector),它允许无暂停地收集垃圾,而不关心Java堆的大小(最高可达8TB)。这是通过在应用程序仍在运行时,并发映射和压缩内存来实现。此外,它不需要修改任何代码,延迟和速度的提升都是开箱即见,无需冗长的配置。在这种情况下,Java程序员可以享受到两全其美的好处,既可以享受到 Java 的简单性(无需偏执于创建新对象),又可以享受到Zing的底层性能,使整个系统的延迟高度可预测。
多亏了GC easy,一个通用的GC日志分析器,我们可以在真实的自动交易应用中(在模拟环境中)快速比较两种JVM。在高频交易的应用中,使用Zing的GC比使用标准的 Oracle Hotspot JVM 小 180 倍左右。更令人印象深刻的是,GC暂停通常与实际应用暂停时间相对应,而Zing智能GC通常是在最小或没有实际暂停的情况下平行发生的。
总结
Java在享受简单性和面向业务的特性同时,仍然可以实现高性能和低延迟。虽然C++ 仍然可用于特定的底层组件,如驱动程序、数据库、编译器和操作系统,但大多数现实中都可以用Java来开发,包括象高频交易这样要求苛刻的应用。
java jdk 下载 来自 嗖嗖下载