当前,部署在云中的分布式Java应用程序组件之间的通信逻辑是通过TCP / IP套接字编程技术实现的。 随着云数据中心采用更高速度的网络(例如10/40 / 100Gbps以太网),现在可以使用更快的网络通信技术,例如远程直接内存访问(RDMA)。 RDMA程序通常使用低级API(例如OpenFabrics Alliance(OFA)动词)或高性能计算工具(例如消息传递接口(MPI))以C / C ++编写。 通过Java本机接口(JNI)访问基于Java的应用程序中的此类低级API,会增加编程的复杂性和性能开销。 套接字直接协议(Sockets Direct Protocol,SDP)是Java 7中可用的一种类似方法,并未显示出许多工作负载的性能优势。 RDMA套接字(R-Sockets)是另一种可比较的方法,仅适用于C / C ++程序。
在本文中,我们介绍了一个新的特定于Linux的兼容Linux套接字的RDMA通信库,称为RDMA上的Java套接字(JSOR)—是Linux / AMD64和Linux / Intel平台上的IBM Java SDK 7SR6的一部分。 我们通过使用Java套接字接口编写的简单Java客户端-服务器程序(请参见下载 )演示JSOR的用法和好处,该程序无需在支持RDMA的云基础架构上进行任何代码更改即可执行。
背景
在部署了云环境的典型Java客户端-服务器方案中,服务请求的响应时间通常受请求者与提供服务的主机之间的网络连接的响应时间限制。 启用远程端点之间的交互的网络通信逻辑通常使用Java套接字接口进行连接建立和数据传输。 默认情况下,Java套接字接口是基于POSIX套接字API实现的。 每个网络操作必须先通过基础操作系统,然后才能到达网络接口。 这一要求导致了昂贵的OS上下文切换以及软件层之间的多个缓冲区副本。
专用网络接口卡(NIC)的一部分专用TCP / IP协议卸载引擎可用于减少这些网络处理开销。 但是,这种卸载技术仍然需要一些缓冲区复制步骤。 随着云技术的普及,许多企业数据中心开始在其网络链路中从10Gbps以太网迁移到40Gbps以太网,以解决云计算不断增长的带宽需求。
RDMA是一种基于硬件的协议卸载技术,最初是为诸如InfiniBand和高速以太网之类的高性能网络结构所提出的,它可以在两个远程应用程序存储器之间直接传输数据,而无需任何主机处理器的参与。 RDMA可以消除昂贵的OS上下文切换,从而节省大量CPU周期。 由于此基于消息的协议是为高性能网络专门定义的,因此应用程序可以利用提高的网络速度来实现低于10微秒的延迟。
随着融合以太网RDMA标准(RoCE)的出现,RDMA协议现在可以直接在现有的高速10 / 40Gbps以太网基础架构之上使用。 因此,通过从传统的TCP / IP堆栈转移到基于RDMA的网络处理,一些基于云的应用程序可以看到延迟和吞吐量的好处,同时使用更少的CPU资源。
SDP是为支持RDMA的网络结构(例如InfiniBand和RoCE)定义的基于有线的标准协议,可以透明地加速基于流套接字的应用程序。 从Java 7开始,JDK附带了对Linux和Solaris平台上的SDP的支持。 但是,由于缓冲区复制和上下文切换开销,SDP是基于内核的实现,会对性能产生负面影响。
在以下各节中,我们介绍并描述JSOR,这是一个完全用户空间的解决方案,可以绕过内核以达到与基于本地RDMA的同类解决方案相当的性能。
关于JSOR
JSOR是一种云网络加速功能,当基础基础结构支持RDMA时,它透明地启用Java流套接字的RDMA通信。 JSOR在标准Java套接字库中集成了高性能RDMA有线协议。 当前,提供了对java.net.Socket
和java.net.ServerSocket
API的支持以及相关的输入和输出流-因此,大多数现有的Java客户端-服务器应用程序都可以从改进的性能中受益。 (请参阅本文后面的选择JSOR 。)
JSOR设计简介
在传统的云联网场景中,访问和服务节点之间的任何交互最终都会变成数据包通过一个或多个以太网交换机在线路上流动的情况,如图1所示。
图1.传统的云网络

每个网络操作(无论是与连接还是数据传输有关的)都会导致一个或多个Java套接字调用的调用。 在Java级别执行的任何套接字操作都会通过JNI层调用相应的本机(C或C ++)库操作。 在JNI层执行调用之前和之后,在Java级别上会进行一定数量的预处理。 因为TCP / IP协议是由OS内核堆栈处理的,所以最终所有特定于JNI套接字的方法都会导致上下文切换。 传输或接收还需要Java,OS和NIC级别的多个缓冲区副本。 诸如多个缓冲区副本和CPU上下文切换之类的网络处理开销导致较高的网络延迟和较差的吞吐量。
JSOR库与R-Sockets协议兼容,该协议由Open Fabric Enterprise Distribution(OFED)中包含的R-Sockets库提供。 JSOR库包含一些修改,使其适合于常见的Java应用程序需求。 它提供了显着的可伸缩性,可靠性和可维护性改进。
与基于InfiniBand的SDP和TCP / IP(IPoIB)相比,JSOR通常可产生更高的性能。 在我们的微基准测试中,JSOR可以提供比SDP高出50%的吞吐量,并且可以提供比IPoIB高出100%的吞吐量。 更好的性能主要归因于JSOR作为标准Java类库的一部分可以优化Java套接字实现的事实。 例如,JSOR避免跨JNI边界进行数据复制,更好地支持各种套接字语义,并根据套接字使用模式自动调整RDMA参数。 而且,尽管IPoIB和SDP是基于内核的传输解决方案,但是JSOR完全在用户空间中,因此它可以缩短数据路径并减少内核中的开销。
如图2所示,JSOR在Java级别拦截Java套接字调用,并将其路由到基础RDMA基础结构。 必须在Java执行期间指定JSOR启用属性,该属性指向适当的配置文件。 当发生从TCP / IP到RDMA的切换时,应用程序与其远程对等方的所有交互都通过基础的RDMA硬件进行。
图2.加速的云网络
使用JSOR
在使用JSOR之前,必须满足云执行环境中的一些先决条件:
- 基础主机应具有适当的主机通道适配器(HCA)或RDMA NIC,并通过高性能InfiniBand或以太网交换结构与远程主机互连。
- 每个参与的主机都应安装OFED 1.5.1或更高版本的基本运行时库。 具体来说,JSOR在执行时会查找libibverbs.so和librdmacm.so库,以动态加载函数指针。
- 您的用户帐户应有权根据您的应用程序需求获得足够的(最好是无限的)可锁定内存。 JSOR套接字缓冲区默认情况下是由内存固定的,因此,操作系统无法在数据传输的关键阶段将它们换出。 在Linux上,使用
ulimit -l
shell命令显示最大锁定内存设置。
当满足这些基本要求时,客户端和服务器端点都需要纯文本格式的配置文件。 配置文件中的每条记录或每一行都指定一个accept , bind或connect规则,并且至少应包含四个由空格分隔的字段:
- 第一个字段指示网络提供商的类型。 当前,只有
rdma
提供程序可用。 - 第二个字段根据您指定的规则指定一个
accept
,bind
或connect
关键字。 - 如果指定的规则是接受或绑定 ,则第三个字段指定本地IP地址;如果指定的规则是connect,则指定远程IP地址。
- 第四个字段指定允许RDMA通信的端口或端口集。 基本上,第三和第四字段共同定义了一组套接字端点,用于特定于RDMA的连接建立和数据传输。
- 第五和后续字段仅适用于接受规则,该规则指定在接受传入的RDMA连接请求时客户端IP地址的列表。
服务(被动)端的配置应具有接受或绑定条目,而客户端(主动)端的配置应具有连接或绑定条目。
例如,要通过端口65444从服务主机192.168.1.1上的客户端192.168.1.3和192.168.1.4接受RDMA连接,Java应用程序服务器的配置文件(我们将其称为rdma_server.conf)中需要以下规则:
rdma accept 192.168.1.1 65444 192.168.1.3 192.168.1.4
同样,要请求从任一客户端到侦听端口65444的服务主机192.168.1.1的RDMA连接,Java客户端应用程序的配置文件(我们将其称为rdma_client.conf)中需要以下规则:
rdma connect 192.168.1.1 65444
除非您明确绑定到特定的本地地址,否则客户端将使用临时端口与服务端建立连接。 在以下示例中,将绑定规则添加到rdma_client.conf文件,以在端口65333上建立其RDMA连接的末端:
rdma connect 192.168.1.1 65444
rdma bind 0.0.0.0 65333
绑定规则中的第三个字段( 0.0.0.0
)指向空地址,并且默认为本地主机上的第一个可用InfiniBand地址。
准备好配置文件后,请在执行Java命令期间将其指定为com.ibm.net.rdma.conf
属性的值。 例如,在被动(服务)端:
java -Dcom.ibm.net.rdma.conf=rdma_server.conf SampleServer args
在主动(客户端)端:
java -Dcom.ibm.net.rdma.conf=rdma_client.conf SampleClient args
清单1显示了SampleServer
类的一部分,该类创建服务器套接字并等待来自远端的连接。 建立连接后,服务器会从客户端接收指定数量的字节,并在一次迭代中将相同数量的字节发送回客户端。 此接收/发送步骤重复指定的次数。
清单1. SampleServer.java
// Create server socket to listen on x.x.x.x address and x port
ServerSocket server = new ServerSocket(Integer.parseInt(args[1]), 0, InetAddress.getByName(args[0]));
...
Socket client = server.accept();
...
// Receive and send message specified number of times
for (int i = 0; i < xferCount; i++) {
in.read(msgBuf, 0, msgSize);
out.write(msgBuf, 0, msgSize);
}
清单2显示了SampleClient
类的一部分,该部分请求与远程服务主机的连接。 建立连接后,客户端将指定数量的字节发送到服务器,并在一次迭代中从服务器接收相同数量的字节。 此发送/接收步骤重复指定的次数。
清单2. SampleClient.java
// Create client socket to connect x.x.x.x address and x port
Socket client = new Socket(InetAddress.getByName(args[0]), Integer.parseInt(args[1]));
...
long startTime = System.nanoTime();
for (int i = 0; i < xferCount; i++) {
out.write(msgBuf, 0, msgSize);
in.read(msgBuf, 0, msgSize);
}
long endTime = System.nanoTime();
在SampleClient.java中,整个发送/接收序列都是定时的,因此我们可以计算总字节数的往返时间(RTT)。
样品运行
我们执行了以下示例运行,以比较消息大小为4KB和重复计数为1,000的各种协议的RTT。 这些示例运行是在由两个由Voltaire 40Gbps InfiniBand交换机互连的IBM HS22刀片服务器组成的测试床上进行的。 每台服务器都运行Red Hat Enterprise Linux(RHEL)v61,并由具有148GB内存的8核Intel Xeon CPU L5609 @ 1.87GHz和Mellanox MT26428 ConnectX VPI PCIe卡供电。
JSOR — SampleClient日志
$ cat rdma_client.conf
rdma connect 7.7.12.10 65444
$ java - Dcom.ibm.net.rdma.conf=rdma_client.conf SampleClient 7.7.12.10 65444 1000 4096
Client Ready>
Local: /7.7.12.9:40563 Remote: /7.7.12.10:65444
SBuf: 32768 bytes RBuf: 45056 bytes
Round trip time of 4096000 bytes: 27313 usec
JSOR — SampleServer日志
$ cat rdma_server.conf
rdma accept 7.7.12.10 65444 7.7.12.9
$ java -Dcom.ibm.net.rdma.conf=rdma_server.conf SampleServer 7.7.12.10 65444 1000 4096
Server Ready>
Local: /7.7.12.10:65444 Remote: /7.7.12.9:40563
SBuf: 32768 bytes RBuf: 45056 bytes
Received/Sent 4096000 bytes
SDP — SampleClient日志
$ cat sdp_client.conf
bind * *
connect 7.7.12.10 65444
$ java -Dcom.sun.sdp.conf=sdp_client.conf
-Djava.net.preferIPv4Stack=true SampleClient 7.7.12.10 65444 1000 4096
Client Ready>
Local: /7.7.12.9:39156 Remote: /7.7.12.10:65444
SBuf: 8388608 bytes RBuf: 8388608 bytes
Round trip time of 4096000 bytes: 33836 usec
SDP — SampleServer日志
$ cat sdp_server.conf
bind * *
connect 7.7.12.10 65444
$ java -Dcom.sun.sdp.conf=sdp_server.conf
-Djava.net.preferIPv4Stack=true SampleServer 7.7.12.10 65444 1000 4096
Server Ready>
Local: /7.7.12.10:65444 Remote: /7.7.12.9:39156
SBuf: 8388608 bytes RBuf: 8388608 bytes
Received/Sent 4096000 bytes
IPoIB-SampleClient日志
$ java SampleClient 7.7.12.10 65444 1000 4096
Client Ready>
Local: /7.7.12.9:40666 Remote: /7.7.12.10:65444
SBuf: 99000 bytes RBuf: 174752 bytes
Round trip time of 4096000 bytes: 98848 usec
IPoIB — SampleServer日志
$ java SampleServer 7.7.12.10 65444 1000 4096
Server Ready>
Local: /7.7.12.10:65444 Remote: /7.7.12.9:40666
SBuf: 99000 bytes RBuf: 174752 bytes
Received/Sent 4096000 bytes
以太网上的TCP / IP — SampleClient日志
$ java SampleClient 9.42.84.20 65444 1000 4096
Client Ready>
Local: /9.42.84.26:48729 Remote: /9.42.84.20:65444
SBuf: 32768 bytes RBuf: 43690 bytes
Round trip time of 4096000 bytes: 194224 usec
以太网上的TCP / IP — SampleServer日志
$ java SampleServer 9.42.84.20 65444 1000 4096
Server Ready>
Local: /9.42.84.20:65444 Remote: /9.42.84.26:48729
SBuf: 32768 bytes RBuf: 43690 bytes
Received/Sent 4096000 bytes
表1显示了我们测试的每种协议的RTT。
表1.样品运行的往返时间
协议 | 发送/接收的总字节数 | RTT(usec) |
---|---|---|
JSOR | 4,096,000 | 27,313 |
SDP | 4,096,000 | 33,836 |
知识产权局 | 4,096,000 | 98,848 |
TCP / IP | 4,096,000 | 194,224 |
如表1所示,JSOR的性能优于其他协议。
跟踪JSOR
当您以JSOR模式运行基于云的Java应用程序时,验证您的应用程序已选择RDMA路径来建立连接和进行数据传输始终非常重要。 因为JSOR启用旨在使应用程序透明,所以没有简单的方法可以在正常模式下进行检查。 但是,您可以通过打开IBM JDK的跟踪选项来启用服务级别视图。 最好同时打开Java方法跟踪和JSOR / NET本机跟踪,以获取完整图片。 在启用了JSOR的应用程序上调用trace选项的典型方法是使用以下调用:
java -Dcom.ibm.net.rdma.conf=config_file
-Xtrace:methods={java/net/RDMA*.*},iprint=mt,iprint=NET,iprint=JSOR main_class args
例如,我们可以在启用了跟踪的情况下以JSOR模式重新运行SampleClient和SampleServer应用程序。
SampleClient跟踪调用为:
java -Dcom.ibm.net.rdma.conf=rdma_client.conf
Xtrace:methods={java/net/RDMA*.*},iprint=mt,iprint=NET,iprint=JSOR
SampleClient 7.7.12.10 65444 1000 4096
SampleServer跟踪调用为:
java -Dcom.ibm.net.rdma.conf=rdma_server.conf
-Xtrace:methods={java/net/RDMA*.*},iprint=mt,iprint=NET,iprint=JSOR
SampleServer 7.7.12.10 65444 1000 4096
清单3显示了两次调用生成的跟踪日志的一部分。
清单3. JSOR示例跟踪日志
04:26:27.500 0x21e3e100 mt.0 >java/net/RDMANetworkProvider.initialize()V Bytecode method, This=21e02468
04:26:27.500 0x21e3e100 mt.2 >java/net/RDMANetworkProvider.initialize0()I Native method, This=21e02468
04:26:27.501 0x21e3e100 NET.440 >initialize0(env=0000000021E3E100, obj=0000000021E73B40)
04:26:27.502 0x21e3e100 JSOR.0 >RDMA_Init()
04:26:27.502 0x21e3e100 JSOR.39 >initverbs()
04:26:27.502 0x21e3e100 JSOR.43 <initverbs(rc=0)
04:26:27.502 0x21e3e100 JSOR.46 >initjsor()
04:26:27.502 0x21e3e100 JSOR.47 <initjsor(rc=0)
04:26:27.502 0x21e3e100 JSOR.3 <RDMA_Init(rc=0)
04:26:27.502 0x21e3e100 NET.441 <initialize0(rc=0)
04:26:27.502 0x21e3e100 mt.8 <java/net/RDMANetworkProvider.initialize0()I Native method
04:26:27.502 0x21e3e100 mt.6 <java/net/RDMANetworkProvider.initialize()V Bytecode
method
JSOR在本机级别具有200多个跟踪挂钩,因此即使对于小型应用程序,您也可以轻松获得大型跟踪文件。 例如,当在跟踪模式下运行SampleServer和SampleClient时,跟踪日志大约为7.5MB。
选择JSOR
需要注意的是,只有网络I / O密集型和延迟关键型工作负载才能从RDMA中受益。 我们建议您在决定使用JSOR之前估算工作负载的端到端延迟。 两种类型的应用程序可能会看到更多好处:
- 通过分布式组件之间长期运行的连接传输大量数据的应用程序。 与传统的TCP / IP套接字相比,建立连接所花费的时间会更长一些,并且在JSOR中所需的堆外可锁定内存量明显更大。
- 不为每个网络通信动态分配数据缓冲区的应用程序。 JSOR需要显式的缓冲区管理-与TCP / IP不同,后者可以根据需要动态分配缓冲区。 如果应用程序消息大小的变化很小并且消息的最大大小是事先已知的,则JSOR可以静态分配缓冲区。
结论
本文介绍了一种称为RDMA的Java套接字(JSOR)的IBM JDK功能,该功能可在IBM Java 7SR6中用于Linux / AMD64和Linux / Intel平台。 我们讨论了JSOR背后的技术,以及如何将其与基于TCP / IP和SDP协议的现有解决方案进行比较。 我们概述了在基于云的环境中使用JSOR的过程,其中包含示例客户端服务器程序和关联的配置文件。 通过在本地测试平台上运行示例程序,我们证明了与SDP,IPoIB和TCP / IP协议相比,JSOR可以提供更好的往返时间。 我们还描述了服务级别跟踪选项,这些选项可用于验证应用程序端点是否使用RDMA路径进行通信。 最后,我们讨论了选择可以从JSOR中受益的应用程序的准则。
翻译自: https://www.ibm.com/developerworks/java/library/j-transparentaccel/index.html