作为我研究的一部分,我正在用Java编写一个高负载的TCP/IP回应服务器。我希望为大约3-4k的客户端提供服务,并查看我可以从中挤出的每秒最大可能的消息数。消息大小非常小-最多100个字节。这项工作没有任何实际目的--只是一项研究。
根据我看到的许多演示(HornetQ基准、LMAX Disruptor talks等),现实世界中的高负载系统往往每秒服务数百万事务(我相信Disruptor提到了大约6Mil和Hornet - 8.5)。例如,this post指出有可能实现高达4000万MPS。因此,我认为这是现代硬件应该具备的能力的粗略估计。
我编写了最简单的单线程NIO服务器并启动了负载测试。令我有点惊讶的是,我在本地主机上只能获得大约100k的MPS,而在实际联网的情况下只能获得25k的MPS。数字看起来很小。我是在Win7 x64核心i7上测试的。查看CPU负载--只有一个核心处于繁忙状态(单线程应用程序应该如此),而其他核心处于闲置状态。然而,即使我加载所有8个内核(包括虚拟),我也不会有超过800k的处理器-甚至不会接近4000万:)
我的问题是:向客户提供大量消息的典型模式是什么?我是否应该将网络负载分布在单个JVM中的几个不同套接字上,并使用某种负载均衡器(如HAProxy )将负载分布到多个内核?或者我应该考虑在NIO代码中使用多个选择器?或者甚至可以在多个JVM之间分配负载,并使用Chronicle在它们之间建立进程间通信?在像CentOS这样的合适的服务器端操作系统上进行测试会有很大的不同吗(也许是Windows放慢了速度)?
下面是我的服务器的示例代码。对于任何传入的数据,它总是以"ok“回答。我知道在现实世界中,我需要跟踪消息的大小,并准备好一条消息可能会被分成多个读取,但我现在想让事情变得超级简单。
public class EchoServer {
private static final int BUFFER_SIZE = 1024;
private final static int DEFAULT_PORT = 9090;
// The buffer into which we'll read data when it's available
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
private InetAddress hostAddress = null;
private int port;
private Selector selector;
private long loopTime;
private long numMessages = 0;
public EchoServer() throws IOException {
this(DEFAULT_PORT);
}
public EchoServer(int port) throws IOException {
this.port = port;
selector = initSelector();
loop();
}
private void loop() {
while (true) {
try{
selector.select();
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client is connected");
}
private void read(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
// Clear out our read buffer so it's ready for new data
readBuffer.clear();
// Attempt to read off the channel
int numRead;
try {
numRead = socketChannel.read(readBuffer);
} catch (IOException e) {
key.cancel();
socketChannel.close();
System.out.println("Forceful shutdown");
return;
}
if (numRead == -1) {
System.out.println("Graceful shutdown");
key.channel().close();
key.cancel();
return;
}
socketChannel.register(selector, SelectionKey.OP_WRITE);
numMessages++;
if (numMessages%100000 == 0) {
long elapsed = System.currentTimeMillis() - loopTime;
loopTime = System.currentTimeMillis();
System.out.println(elapsed);
}
}
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer dummyResponse = ByteBuffer.wrap("ok".getBytes("UTF-8"));
socketChannel.write(dummyResponse);
if (dummyResponse.remaining() > 0) {
System.err.print("Filled UP");
}
key.interestOps(SelectionKey.OP_READ);
}
private Selector initSelector() throws IOException {
Selector socketSelector = SelectorProvider.provider().openSelector();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(hostAddress, port);
serverChannel.socket().bind(isa);
serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
public static void main(String[] args) throws IOException {
System.out.println("Starting echo server");
new EchoServer();
}
}
发布于 2014-01-08 14:32:47
使用常规硬件,您将达到每秒最多几十万个请求。至少这是我尝试构建类似解决方案的经验,the Tech Empower Web Frameworks Benchmark似乎也同意这一点。
通常,最好的方法取决于您的负载是受io限制的还是受cpu限制的。
对于io绑定的负载(高延迟),您需要使用多个线程进行异步io。为了获得最佳性能,您应该尽可能地避免线程之间的切换。因此,拥有一个专用的选择器线程和另一个用于处理的线程池比拥有一个线程池(每个线程都进行选择或处理)要慢,因此在最好的情况下(如果io立即可用),请求由单个线程处理。这种类型的设置代码比较复杂,但速度很快,我不相信任何异步web框架都能充分利用这一点。
对于cpu负载,每个请求一个线程通常是最快的,因为您避免了上下文切换。
https://stackoverflow.com/questions/17556901
复制相似问题