简体   繁体   English

为什么下面的Java NIO API比netty慢

[英]Why is the following Java NIO API so slow when compared to netty

I have the following JAVA implementation to create a simple web server using the NIO API.我有以下 JAVA 实现来使用 NIO API 创建一个简单的 web 服务器。

package zion

import java.net._
import java.nio.ByteBuffer
import java.nio.channels._

object NHello {

  import java.nio.CharBuffer
  import java.nio.charset.Charset

  def helloWorldBytes: ByteBuffer = Charset
    .forName("ISO-8859-1")
    .newEncoder
    .encode(CharBuffer.wrap(httpResponse("NHello World\n")))

  def httpResponse(content: String): String = {
    val rn = "\r\n"
    List(
      "HTTP/1.1 200 OK",
      "Content-Type: text/html",
      "Connection: Keep-Alive",
      s"Content-Length: ${content.length()}",
      rn + content
    ).mkString(rn)
  }

  def main(args: Array[String]): Unit = {
    val port    = 8080
    val address = new InetSocketAddress(port)

    // Server Socket Channel
    val serverSocketChannel = ServerSocketChannel.open()
    serverSocketChannel.bind(address)
    serverSocketChannel.configureBlocking(false)

    // Selector
    val selector = Selector.open()
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT)

    while (true) {
      selector.select()
      val iterator = selector.selectedKeys().iterator()
      while (iterator.hasNext) {
        val key = iterator.next()
        if (key.isAcceptable) {
          val channel = serverSocketChannel.accept()
          channel.write(helloWorldBytes)
          channel.close()
        }

      }
      iterator.remove()
    }

    sys.addShutdownHook({
      println("Shutting down...")
      serverSocketChannel.close()
    })

    println("Exiting...")
  }
}

Using wrk I get around a few thousand requests per second.使用wrk我每秒收到大约几千个请求。

wrk -t12 -c100 -d10s http://127.0.0.1:8080

This seems like a bit too slow when compared to Netty.与 Netty 相比,这似乎有点太慢了。 With Netty I am able to get at least 10 ~ 15 times better throughput.使用 Netty,我可以获得至少 10 ~ 15 倍的吞吐量。 Considering Netty is also built on top of NIO what am I doing wrong?考虑到 Netty 也是建立在 NIO 之上的,我做错了什么?

Are there some obvious performance optimizations that I am missing?我是否缺少一些明显的性能优化?

After doing some further searching and analysis I have finally figured out all the problems in this above code.经过进一步的搜索和分析,我终于弄清楚了上述代码中的所有问题。

def main(args: Array[String]): Unit = {
    val port    = 8080
    val address = new InetSocketAddress(port)

    val serverSocketChannel = ServerSocketChannel.open()
    serverSocketChannel.bind(address)
    serverSocketChannel.configureBlocking(false)

    val selector = Selector.open()
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT)

    while (true) {
      selector.select()
      val iterator = selector.selectedKeys().iterator()
      while (iterator.hasNext) {
        val key = iterator.next()
        if (key.isAcceptable) {
          val channel = serverSocketChannel.accept()
          // 1. Blocking Write
          channel.write(helloWorldBytes)
          // 2. Blocking Close
          channel.close()
        }

      }
      iterator.remove()
    }

    sys.addShutdownHook({
      println("Shutting down...")
      serverSocketChannel.close()
    })

    println("Exiting...")
  }
}

The main problems were that主要问题是

1. Blocking Write Because of the blocking write call, unless the bytes are written into the stream I was not able to accept more connections. 1.阻塞写由于阻塞写调用,除非字节被写入 stream 我无法接受更多的连接。 So those connections are just lying idle, thus affecting the performance of the webserver所以那些连接只是闲置,从而影响网络服务器的性能

2. Blocking Close The close call is also blocking and takes time to complete. 2.阻塞关闭close调用也是阻塞的,需要时间才能完成。 Again unless the connection is closed, no new requests are accepted and no accepted connections are responded.同样,除非连接关闭,否则不会接受新请求,也不会响应已接受的连接。

There is another problem in closing the connection: Creating a new connection is expensive and tools like wrk etc. don't kill the connection automatically after making one request.关闭连接还有另一个问题:创建新连接的成本很高,并且wrk等工具在发出一个请求后不会自动终止连接。 Closing it on the server is after each request also becomes a performance killer and thus affects your benchmarks.在每个请求也成为性能杀手并因此影响您的基准测试之后,在服务器上关闭它。

Here is an alternative "Highly performant" implementation这是另一种“高性能”实现

package zion

import java.io.IOException
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.{
  AsynchronousChannelGroup,
  AsynchronousServerSocketChannel,
  AsynchronousSocketChannel,
  CompletionHandler
}
import java.util.concurrent.{Executors, TimeUnit}

/**
  * This is potentially as fast as it can get using NIO APIs.
  */
object HelloAsyncNIO {
  // Create a thread pool for the socket channel
  // It would be better to have probably only one thread for events.
  // That pool could be shared betwee the SocketServer and in future SocketClients.
  private val group =
    AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(24))

  // Socket to accept connections
  private val serverSocketChannel = AsynchronousServerSocketChannel.open(group)

  // Port to be used to connect
  private val PORT = 8081

  // Flag to handle logging
  private val ENABLE_LOGGING = false

  /**
    * Contains utilities to manage read/write on the socket channels
    */
  object NIOBuffer {
    def helloWorldBytes: ByteBuffer = Charset
      .forName("ISO-8859-1")
      .newEncoder
      .encode(CharBuffer.wrap(httpResponse("NHello World\n")))

    def httpResponse(content: String): String = {
      val rn = "\r\n"
      List(
        "HTTP/1.1 200 OK",
        "Content-Type: text/html",
        "Connection: Keep-Alive",
        s"Content-Length: ${content.length()}",
        rn + content
      ).mkString(rn)
    }
    private val writeByteBuffer = ByteBuffer.wrap(helloWorldBytes)
    private val readByteBuffer  = ByteBuffer.allocateDirect(1024 * 2) // 2kb
    def read(
        socket: AsynchronousSocketChannel
    )(h: CompletionHandler[Integer, AsynchronousSocketChannel]): Unit =
      socket.read(readByteBuffer.duplicate(), socket, h)
    def write(
        socket: AsynchronousSocketChannel
    )(h: CompletionHandler[Integer, AsynchronousSocketChannel]): Unit =
      socket.write(writeByteBuffer.duplicate(), socket, h)
  }

  // Generic async completion handler
  case class Handle[V, A](cb: (V, A) => Unit) extends CompletionHandler[V, A] {
    override def completed(result: V, attachment: A): Unit =
      cb(result, attachment)
    override def failed(cause: Throwable, attachment: A): Unit = {
      cause match {
        case e: IOException => log(e.getMessage)
        case _              => cause.printStackTrace()
      }
    }
  }

  // Logging utility
  def log(input: Any*): Unit = {
    if (ENABLE_LOGGING) println(input.map(_.toString).mkString(", "))
  }

  private val onAccept
      : Handle[AsynchronousSocketChannel, AsynchronousServerSocketChannel] =
    Handle[AsynchronousSocketChannel, AsynchronousServerSocketChannel](
      (socket, server) => {
        log("\nACCEPT")

        // Accept new connections immediately
        server.accept(serverSocketChannel, onAccept)

        // Read from the current socket
        NIOBuffer.read(socket)(onRead)
      }
    )

  private val onRead: Handle[Integer, AsynchronousSocketChannel] =
    Handle[Integer, AsynchronousSocketChannel]((bytes, socket) => {
      log("READ", bytes)

      // EOF, meaning connection can be closed
      if (bytes == -1) socket.close()

      // Some data was read and now we can respond back
      else if (bytes > 0) NIOBuffer.write(socket)(onWrite)

    })

  private val onWrite: Handle[Integer, AsynchronousSocketChannel] =
    Handle[Integer, AsynchronousSocketChannel]((bytes, socket) => {
      log("WRITE", bytes)

      // Read from the socket
      NIOBuffer.read(socket)(onRead)
    })

  def main(args: Array[String]): Unit = {

    // Setup socket channel
    serverSocketChannel.bind(new InetSocketAddress(PORT))
    serverSocketChannel.accept(serverSocketChannel, onAccept)

    // Making the main thread wait
    group.awaitTermination(Long.MaxValue, TimeUnit.SECONDS)
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM