multithreading jlabel - Hilo de Java por modelo de conexión vs NIO




ejemplo html (6)

¿El Java NIO no bloqueante es aún más lento que el hilo estándar por socket asíncrono de conexión?

Además, si usara hilos por conexión, ¿crearía hilos nuevos o utilizaría un grupo de hilos muy grande?

Estoy escribiendo un servidor MMORPG en Java que debería poder escalar 10000 clientes fácilmente con hardware suficientemente potente, aunque la cantidad máxima de clientes es 24000 (que creo que es imposible de alcanzar para el hilo por modelo de conexión debido a un hilo de 15000 límite en Java). De un artículo de hace tres años, he escuchado que bloquear IO con un hilo por modelo de conexión era todavía un 25% más rápido que NIO (es decir, este documento http://www.mailinator.com/tymaPaulMultithreaded.pdf ), pero puede lo mismo se puede lograr en este día? Java ha cambiado mucho desde entonces, y he escuchado que los resultados eran cuestionables cuando se comparan escenarios de la vida real porque la VM utilizada no era Sun Java. Además, dado que es un servidor MMORPG con muchos usuarios simultáneos que interactúan entre sí, ¿el uso de las prácticas de sincronización y seguridad de subprocesos disminuirá el rendimiento hasta el punto de que un selector de NIO con un único subproceso que atienda 10000 clientes sea más rápido? (No es necesario procesar todo el trabajo en el hilo con el selector, se puede procesar en hilos de trabajo como funciona MINA / Netty).

¡Gracias!


Answers

En realidad hay 3 soluciones:

  1. Múltiples hilos
  2. Un hilo y NIO
  3. Ambas soluciones 1 y 2 al mismo tiempo

Lo mejor que se puede hacer por el rendimiento es tener un número pequeño y limitado de subprocesos y eventos de red múltiplex en estos subprocesos con NIO a medida que ingresan nuevos mensajes a través de la red.

Usar NIO con un hilo es una mala idea por algunas razones:

  • Si tiene varias CPU o núcleos, estará inactivo de recursos ya que solo puede usar un núcleo a la vez si solo tiene un hilo.
  • Si tiene que bloquear por alguna razón (tal vez para hacer un acceso al disco), su CPU está inactiva cuando podría manejar otra conexión mientras espera el disco.

Un hilo por conexión es una mala idea porque no escala. Digamos que tiene:

  • 10 000 conexiones
  • 2 CPU con 2 núcleos cada uno
  • solo 100 hilos serán bloqueados en un momento dado

Entonces puedes descubrir que solo necesitas 104 hilos. Más y estás desperdiciando recursos administrando hilos adicionales que no necesitas. Hay una gran cantidad de contabilidad bajo el capó necesaria para gestionar 10 000 hilos. Esto lo retrasará.

Es por eso que combina las dos soluciones. Además, asegúrese de que su VM esté usando las llamadas al sistema más rápidas. Cada sistema operativo tiene sus propias llamadas al sistema únicas para IO de red de alto rendimiento. Asegúrese de que su máquina virtual esté usando lo último y lo mejor. Creo que esto es epoll () en Linux.

Además, si usara hilos por conexión, ¿crearía hilos nuevos o utilizaría un grupo de hilos muy grande?

Depende de cuánto tiempo desee optimizar. La solución más rápida es crear recursos como hilos y cadenas cuando sea necesario. Luego deje que la recolección de basura los reclame cuando haya terminado con ellos. Puede obtener un aumento de rendimiento al tener un grupo de recursos. En lugar de crear un objeto nuevo, le pide al grupo uno y lo devuelve al grupo cuando haya terminado. Esto agrega la complejidad del control de concurrencia. Esto se puede optimizar aún más con algoritmos de concurrencia avanzada como algoritmos sin bloqueo . Las nuevas versiones de la API de Java tienen algunos de estos para usted. Puede pasar el resto de su vida haciendo estas optimizaciones en un solo programa. Cuál es la mejor solución para su aplicación específica es probablemente una pregunta que merece su propia publicación.


Si está dispuesto a gastar cualquier cantidad de dinero en hardware lo suficientemente potente, ¿por qué limitarse a un servidor? google no usa un servidor, ni siquiera usan un centro de datos de servidores.

Una idea errónea común es que NIO permite IO no bloqueante, por lo que es el único modelo que vale la evaluación comparativa. Si compara el bloqueo de NIO, puede obtenerlo un 30% más rápido que el IO antiguo. es decir, si usa el mismo modelo de subprocesamiento y compara solo los modelos de E / S.

Para un juego sofisticado, es mucho más probable que se quede sin CPU antes de llegar a las conexiones de 10K. De nuevo, es más simple tener una solución que se escale horizontalmente. Entonces no necesita preocuparse por la cantidad de conexiones que puede obtener.

¿Cuántos usuarios pueden interactuar razonablemente? 24? en cuyo caso tiene 1000 grupos independientes interactuando. No tendrás tantos núcleos en un solo servidor.

¿Cuánto dinero por usuario piensa gastar en servidor (es)? Puede comprar un servidor de 12 núcleos con 64 GB de memoria por menos de £ 5000. Si coloca 2500 usuarios en este servidor, habrá gastado £ 2 por usuario.

EDITAR: Tengo una referencia http://vanillajava.blogspot.com/2010/07/java-nio-is-faster-than-java-io-for.html que es mía. ;) Hice una revisión por alguien que es un GURU de Java Networking y estuvo de acuerdo con lo que había encontrado.



Si tiene conexiones ocupadas, lo que significa que constantemente le envían datos y los envía de vuelta, puede usar non-Blocking IO junto con Akka .

Akka es un kit de herramientas de código abierto y tiempo de ejecución que simplifica la construcción de aplicaciones concurrentes y distribuidas en la JVM. Akka admite múltiples modelos de programación para concurrencia, pero enfatiza la concurrencia basada en actores, con inspiración extraída de Erlang. Existen enlaces de lenguaje para Java y Scala.

La lógica de Akka no bloquea, por lo que es perfecta para la programación asincrónica. Usando Akka Actors puedes eliminar el Thread overhead .

Pero si su socket se bloquea con mayor frecuencia, le sugiero que use Blocking IO junto con Quasar

Quasar es una biblioteca de código abierto para la concurrencia de JVM simple y liviana, que implementa hilos ligeros verdaderos (fibras AKA) en la JVM. Las fibras Quasar se comportan como los hilos simples de Java, excepto que prácticamente no tienen memoria y tareas de cambio de tareas, por lo que puede generar fácilmente cientos de miles de fibras, o incluso millones, en una sola JVM. Quasar también proporciona canales para comunicaciones interfibras modeladas a partir de las que ofrece el lenguaje Go, completadas con selectores de canales. También contiene una implementación completa del modelo de actor, muy similar a Erlang.

La lógica de Quasar es bloqueante, por lo que puedes generar, digamos 24000 fibras esperando conexiones diferentes. Uno de los puntos positivos sobre Quasar es que las fibras pueden interactuar fácilmente con los hilos lisos. También Quasar tiene integraciones con bibliotecas populares, como Apache HTTP client o JDBC o Jersey y así sucesivamente, por lo que puede utilizar los beneficios del uso de Fibras en muchos aspectos de su proyecto.
Puede ver una buena comparación entre estos dos marcos here .


Los beneficios de NIO deben tomarse con un grano de sal.

En un servidor HTTP, la mayoría de las conexiones son conexiones keep-alive, están inactivas la mayoría de las veces. Sería un desperdicio de recursos preasignar un hilo para cada uno.

Para el MMORPG las cosas son muy diferentes. Supongo que las conexiones están constantemente ocupadas recibiendo instrucciones de los usuarios y enviando el último estado del sistema a los usuarios. Se necesita un hilo la mayor parte del tiempo para una conexión.

Si usa NIO, tendrá que volver a asignar constantemente un hilo para una conexión. Puede ser una solución inferior a la solución simple de hilo fijo por conexión.

El tamaño predeterminado de la pila de subprocesos es bastante grande (1/4 MB). Es la principal razón por la que solo puede haber subprocesos limitados. Intenta reducirlo y ver si tu sistema puede soportar más.

Sin embargo, si tu juego está realmente "ocupado", es tu CPU la que debes preocuparte más. NIO o no, es realmente difícil manejar miles de jugadores hiper activos en una máquina.


El entrevistador probablemente estaba buscando una referencia circular como el código a continuación (que, por cierto, solo pierde memoria en JVM muy antiguas que usaban el conteo de referencias, que ya no es el caso) Pero es una pregunta bastante vaga, por lo que es una excelente oportunidad para demostrar su comprensión de la gestión de memoria JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Luego puede explicar que con el recuento de referencias, el código anterior perdería memoria. Pero la mayoría de las JVM modernas ya no usan el conteo de referencias, la mayoría usa un recolector de basura de barrido, que de hecho recopilará esta memoria.

A continuación, puede explicar cómo crear un Objeto que tenga un recurso nativo subyacente, como este:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Luego, puede explicar que esto es técnicamente una pérdida de memoria, pero en realidad la pérdida es causada por el código nativo en la JVM que asigna recursos nativos subyacentes, que no fueron liberados por su código Java.

Al final del día, con una JVM moderna, necesita escribir algún código Java que asigne un recurso nativo fuera del alcance normal de la conciencia de la JVM.





java multithreading asynchronous nio nonblocking