我最近开始开发一个密集使用网络的应用程序。 第一次使用RMI尝试,由于几个原因,我们切换到纯套接字。但是,当通过网络或甚至在localhost上测试套接字时,我们降低到25个请求/秒的速率。使用RMI时,它高出两个数量级。
通过更多测试,我们获得了以下(对于localhost):
这是客户端代码:(服务器端只回复“ACK”)
public static void main(String[] args) throws IOException, ClassNotFoundException
{
Socket kkSocket = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
kkSocket = new Socket("barium", 4444);
out = new ObjectOutputStream(kkSocket.getOutputStream());
in = new ObjectInputStream(kkSocket.getInputStream());
long throughput;
long millis;
TcpRequest hello = null;
throughput = 0;
millis = System.currentTimeMillis();
while (System.currentTimeMillis() < millis + 1000)
{
hello = new TcpRequest();
hello.service = "hello";
hello.payload = Math.random();
throughput++;
}
System.out.println("-------------------------------------------------------");
System.out.println("| Objects created: " + (throughput) + " requests/sec.");
System.out.println("-------------------------------------------------------");
throughput = 0;
millis = System.currentTimeMillis();
while (System.currentTimeMillis() < millis + 1000)
{
out.writeObject(hello);
Object res = in.readObject();
throughput++;
}
System.out.println("-------------------------------------------------------");
System.out.println("| Same object throughput: " + (throughput) + " requests/sec.");
System.out.println("-------------------------------------------------------");
throughput = 0;
millis = System.currentTimeMillis();
while (System.currentTimeMillis() < millis + 1000)
{
hello = new TcpRequest();
out.writeObject(hello);
Object res = in.readObject();
throughput++;
}
System.out.println("-------------------------------------------------------");
System.out.println("| New objetcs throughput: " + (throughput) + " requests/sec.");
System.out.println("-------------------------------------------------------");
out.close();
in.close();
kkSocket.close();
}
TcpRequest类只是一个没有任何特殊内容的虚拟类。
所以,如果创建对象很快,如果通过网络发送它很快...为什么地球上通过网络发送新对象这么慢?!?!
如果你在发送之前保留一个相同的对象并修改其内容,你也会有很高的转移率...但是陷入了令人讨厌的陷阱:
使用对象序列化时 重要的是要记住这一点 ObjectOutputStream维护一个 哈希表映射所写的对象 进入流程到句柄。当一个 object被写入流中 第一次,它的内容将是 复制到流。随后 然而,写入会产生一个句柄 被写入的对象 流。
...发生在我们身上并在搞清楚之前引起了几个小时的调试。
基本上......你如何用套接字实现高吞吐量? (...我的意思是,随着RMI成为它的包装,我们已经高了两个数量级!)
解决:
替换:
out = new ObjectOutputStream(kkSocket.getOutputStream());
使用:
out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream()))
性能再次正常(与同一对象案例几乎相同的高吞吐量)
答案 0 :(得分:8)
找到它:
而不是:
out = new ObjectOutputStream(kkSocket.getOutputStream());
您应该使用:
out = new ObjectOutputStream(new BufferedOutputStream(kkSocket.getOutputStream()));
和
out.flush();
发送消息时。
......有很大的不同......虽然我不确切知道为什么。
答案 1 :(得分:1)
我不相信该基准测试的结果。它不能确保JVM预热;即,在测量执行时间之前,将类加载,初始化并编译为本机代码。通过运行基准测试,本机代码编译很可能会在某种程度上发挥作用,这会增加一个或多个循环所花费的时间。
答案 2 :(得分:0)
您遇到的问题不是套接字的吞吐量低;它是默认的Java序列化。
当你一遍又一遍地发送同一个对象时,它只被序列化一次,然后每次都会发送一个对它的引用;这就解释了为什么它如此迅速。
每次创建新对象时,必须使用相对较慢的Java序列化机制序列化新对象,这可能比您需要的更复杂。
您可以采取的措施是为您的类实现自定义序列化代码,或者创建自己的类外部对象序列化协议(并使用DataOutput而不是ObjectOutputStream)。
这篇文章有很多好的信息,即使它有点过时了:http://java.sun.com/developer/technicalArticles/Programming/serialization/ 请参阅性能注意事项的最后一部分
答案 3 :(得分:0)
在处理小数据包时,应设置TCP_NODELAY
选项。否则,它们将被延迟以尝试优化网络通信(也称为Nagle's algorithm)。
socket.setTcpNoDelay(true);
使用缓冲区有帮助的原因是,延迟仅影响 small 数据包。因此,缓冲本身并不是帮助,而是数据包的大小。