Java高负载NIO TCP服务器

Java高负载NIO TCP服务器,java,tcp,nio,high-load,Java,Tcp,Nio,High Load,作为我研究的一部分,我正在用Java编写一个高负载TCP/IP echo服务器。我想为大约3-4k的客户端提供服务,并查看每秒可以挤出的最大可能消息数。消息大小非常小-最多100字节。这项工作没有任何实际目的——只是一项研究 根据我所看到的大量演示(HornetQ基准测试、LMAX干扰程序对话等),现实世界中的高负载系统往往每秒服务数百万事务(我相信干扰程序提到了大约6 mils和Hornet-8.5)。例如,声明有可能达到4000万MPS。所以我把它当作是对现代硬件能力的粗略估计 我编写了最简

作为我研究的一部分,我正在用Java编写一个高负载TCP/IP echo服务器。我想为大约3-4k的客户端提供服务,并查看每秒可以挤出的最大可能消息数。消息大小非常小-最多100字节。这项工作没有任何实际目的——只是一项研究

根据我所看到的大量演示(HornetQ基准测试、LMAX干扰程序对话等),现实世界中的高负载系统往往每秒服务数百万事务(我相信干扰程序提到了大约6 mils和Hornet-8.5)。例如,声明有可能达到4000万MPS。所以我把它当作是对现代硬件能力的粗略估计

我编写了最简单的单线程NIO服务器,并启动了负载测试。我对在本地主机上只能获得约10万MPS,而在实际网络上只能获得约25万MPS并不感到惊讶。数字看起来很小。我在Win7x64上测试,核心是i7。看看CPU负载-只有一个内核繁忙(单线程应用程序上应该是繁忙的),而其余的则处于空闲状态。但是,即使我加载所有8个内核(包括虚拟内核),我的MPS也不会超过800k,甚至不会接近4000万:)

我的问题是:向客户提供大量消息的典型模式是什么?我是否应该在单个JVM内的几个不同套接字上分配网络负载,并使用HAProxy之类的负载平衡器将负载分配到多个内核?或者我应该在NIO代码中使用多个选择器?或者甚至可以在多个JVM之间分配负载,并使用编年史在它们之间构建进程间通信?在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();
}
}
公共类EchoServer{
私有静态最终整数缓冲区大小=1024;
专用最终静态int默认_端口=9090;
//当数据可用时,我们将在其中读取数据的缓冲区
private ByteBuffer readBuffer=ByteBuffer.allocate(缓冲区大小);
私有InetAddress主机地址=null;
专用int端口;
专用选择器;
私生活时间长;
私有长消息=0;
公共EchoServer()引发IOException{
此端口(默认_端口);
}
公共EchoServer(int端口)引发IOException{
this.port=端口;
选择器=initSelector();
loop();
}
私有void循环(){
while(true){
试一试{
selector.select();
迭代器selectedKeys=selector.selectedKeys().Iterator();
while(selectedKeys.hasNext()){
SelectionKey=selectedKeys.next();
selectedKeys.remove();
如果(!key.isValid()){
继续;
}
//检查哪些活动可用并进行处理
if(key.isAcceptable()){
接受(钥匙);
}else if(key.isReadable()){
读(键);
}else if(key.isWritable()){
写(键);
}
}
}捕获(例外e){
e、 printStackTrace();
系统出口(1);
}
}
}
私有void accept(SelectionKey)引发IOException{
ServerSocketChannel ServerSocketChannel=(ServerSocketChannel)key.channel();
SocketChannel SocketChannel=serverSocketChannel.accept();
socketChannel.configureBlocking(假);
socketChannel.setOption(标准socketoptions.SO_KEEPALIVE,true);
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY,true);
socketChannel.寄存器(选择器,SelectionKey.OP_READ);
System.out.println(“客户端已连接”);
}
私有无效读取(SelectionKey)引发IOException{
SocketChannel SocketChannel=(SocketChannel)key.channel();
//清除我们的读取缓冲区,以便为新数据做好准备
readBuffer.clear();
//尝试读取该频道
国际货币联盟;
试一试{
numRead=socketChannel.read(readBuffer);
}捕获(IOE异常){
键。取消();
socketChannel.close();
System.out.println(“强制关机”);
返回;
}
如果(numRead==-1){
System.out.println(“正常关机”);
key.channel().close();
键。取消();
返回;
}
socketChannel.register(选择器,SelectionKey.OP_WRITE);
numMessages++;
如果(numMessages%100000==0){
长时间运行=System.currentTimeMillis()-循环时间;
loopTime=System.currentTimeMillis();
系统输出打印项次(已用);
}
}
私有无效写入(SelectionKey)引发IOException{
SocketChannel SocketChannel=(SocketChannel)key.channel();
ByteBuffer dummyResponse=ByteBuffer.wrap(“ok”.getBytes(“UTF-8”);
socketChannel.write(dummyResponse);
if(dummyResponse.remaining()>0){
系统错误打印(“填写”);
}
key.interesttops(选择key.OP_READ);
}
私有选择器initSelector()引发IOException{
选择器socketSelector=SelectorProvider.provider().openSelector();
ServerSocketChannel serverChannel=ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress isa=新的InetSocketAddress(主机地址、端口);
serverChannel.socket().bind(isa);
serverChannel.register(socketSelector,SelectionKey.OP_ACCEPT);
返回插座选择器;
}
公共静态void main(字符串[]args)引发IOException{
System.out.println(“启动echo服务器”);
新的EchoServer();
}
}

你的写作逻辑是错误的。您应该在有数据要写入时立即尝试写入。如果
write()
返回零,则为时间
what is a typical pattern for serving massive amounts of messages to clients?
Or I should look towards using multiple Selectors in my NIO code?
Will testing on a proper serverside OS like CentOS make a big difference (maybe it is Windows that slows things down)?