Cómo configurar tareas de procesamiento multiproceso en disruptor
En un juego online de 2k, la concurrencia cada segundo es aterradora. La biblioteca tradicional de complementos directos de hibernación básicamente no es factible. Deduciré paso a paso una operación de base de datos sin bloqueos.
1. Cómo estar libre de bloqueos en concurrencia.
Una idea muy simple, convertir la concurrencia en un solo hilo. El Disruptor de Java es un buen ejemplo. Si usa la clase concurrentCollection de Java para hacerlo, el principio es iniciar un subproceso y ejecutar una cola. Durante la concurrencia, las tareas se insertan en la cola y los subprocesos se entrenan para leer la cola y luego ejecutarlos uno por uno.
Bajo este patrón de diseño, cualquier concurrencia se convertirá en una operación de un solo subproceso y será muy rápida. Los node.js actuales, o los servidores ARPG más comunes, tienen este diseño, una arquitectura de "gran bucle".
De esta manera, nuestro sistema original tiene dos entornos: el entorno concurrente y el entorno de "bucle grande".
El entorno concurrente es nuestro entorno tradicional bloqueado con bajo rendimiento.
El entorno "Big Loop" es un entorno sin bloqueo de un solo subproceso desarrollado por nosotros utilizando Disruptor, con un rendimiento potente.
2. Cómo mejorar el rendimiento del procesamiento en un entorno de "bucle grande".
Una vez que la concurrencia se convierte en un solo subproceso, una vez que uno de los subprocesos tiene un problema de rendimiento, todo el procesamiento inevitablemente se ralentizará. Por lo tanto, cualquier operación en un solo subproceso no debe implicar procesamiento de IO. ¿Qué pasa con las operaciones de bases de datos?
Aumentar el almacenamiento en caché. La idea es muy simple. Leer directamente desde la memoria definitivamente será más rápido. En cuanto a las operaciones de escritura y actualización, utilice una idea similar: envíe la operación a una cola y luego ejecute un subproceso separado para obtener las bibliotecas de inserción una por una. Esto garantiza que no haya operaciones IO involucradas en el "gran bucle".
El problema vuelve a surgir:
Si nuestro juego sólo tiene un bucle grande, es fácil de solucionar, porque proporciona una sincronización perfecta y sin bloqueos.
Pero el entorno real del juego coexiste con la concurrencia y el "gran bucle", que son los dos entornos mencionados anteriormente. Entonces, no importa cómo lo diseñemos, inevitablemente encontraremos que habrá un bloqueo en el caché.
3. ¿Cómo resolver la concurrencia y el "gran bucle" y eliminar los bloqueos?
Sabemos que si desea evitar operaciones de bloqueo en un "gran bucle", utilice "asíncrono" y entregue la operación al hilo para su procesamiento. Combinando estas dos características, cambié ligeramente la estructura de la base de datos.
La capa de caché original debe tener bloqueos, por ejemplo:
TableCache público
{
privado HashMaplt, Objectgt cachés; = new ConcurrentHashMaplt; String, Objectgt ();
}
Esta estructura es inevitable y garantiza que el caché se pueda operar con precisión en un entorno concurrente. Sin embargo, el "gran bucle" no puede operar directamente este caché para modificarlo, por lo que se debe iniciar un hilo para actualizar el caché, por ejemplo:
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); p>
EXECUTOR.execute(new LatencyProcessor(logs));
la clase LatencyProcessor implementa Runnable
{
public void run()
{
//Aquí puedes modificar los datos de la memoria de forma arbitraria. Se utiliza asíncrono.
}
}
Vale, se ve precioso. Pero surge otro problema. Durante el proceso de acceso de alta velocidad, es muy probable que el caché aún no se haya actualizado, otras solicitudes lo recuperen nuevamente y se obtengan los datos antiguos.