Distuptor, un framework sin bloqueos de alto rendimiento

Introducción a Distuptor

Primera introducción a Distuptor

Es un marco sin bloqueos de alto rendimiento adecuado para escenarios comerciales de alta concurrencia. De hecho, internamente es un modelo de productor-consumidor, algo similar a la cola SPSC segura para subprocesos.

El siguiente es su concepto básico.

Es un marco de concurrencia sin bloqueos de alto rendimiento que utiliza un búfer en anillo como cola de mensajes para lograr un intercambio de datos y un procesamiento de eventos eficientes.

RingBuffer: la implementación de la estructura de datos subyacente de Disruptor, clase central, es el lugar de transferencia para el intercambio de datos entre subprocesos;

Secuenciador: administrador de secuencia, el implementador de la sincronización de producción, responsable de la gestión y coordinación de los números de serie del consumidor/productor y las barreras de números de serie. El secuenciador tiene dos modos diferentes: productor único y productor múltiple, que implementan varias sincronizaciones.

Secuencia: número de secuencia, declara un número de secuencia, utilizado para rastrear los cambios en las tareas en el ringbuffer y el consumo del consumidor. La mayor parte del código concurrente en el disruptor se implementa modificando sincrónicamente el valor de la secuencia, en lugar de bloquearlo. Este es el disruptor Una de las principales razones del alto rendimiento;

SequenceBarrier: barrera del número de secuencia, administra y coordina el número de secuencia del cursor del productor y el número de secuencia de cada consumidor, asegurando que el productor no sobrescriba mensajes que el consumidor no podrá procesar en el futuro, y asegura que los consumidores dependientes se pueden procesar en el orden correcto.

EventProcessor——Procesador de eventos, escucha los eventos de RingBuffer y consume los eventos disponibles. Los eventos leídos de RingBuffer se entregarán a la clase de implementación del productor real para su consumo; seguirá escuchando el siguiente número de secuencia disponible hasta que el número de secuencia corresponda El evento es listo.

EventHandler: procesador de negocios, es la interfaz del consumidor real, completa la implementación de una lógica comercial específica y un tercero implementa la interfaz; representa al consumidor.

Productor: interfaz de productor, un hilo de terceros desempeña esta función y el productor escribe eventos en RingBuffer.

Estrategia de espera: la estrategia de espera determina cómo espera un consumidor a que el productor coloque el evento en el disruptor.


estructura de datos

RingBuffer La capa inferior del búfer circular es una matriz espacial continua. Además de la matriz, también hay un número de secuencia para señalar el siguiente elemento disponible para uso de productores y consumidores.

como muestra la imagen:
Insertar descripción de la imagen aquí


estrategia de espera

Las siguientes son sus cuatro estrategias de espera.

La estrategia de espera de bloqueo BlockingWaitStrategy
es la estrategia predeterminada de Disruptor. Internamente, BlockingWaitStrategy utiliza bloqueos y condiciones para controlar la activación de subprocesos. Cuando un consumidor intenta obtener datos del Disruptor, si no hay datos disponibles, se bloqueará hasta que haya nuevos datos disponibles o se agote el tiempo de espera. BlockingWaitStrategy es la estrategia menos eficiente, pero consume la menor cantidad de CPU y proporciona un rendimiento más consistente en varios entornos de implementación.

Estrategia de espera de suspensión de SleepingWaitStrategy
. SleepingWaitStrategy utiliza un bucle más el método Thread.sleep () para ceder recursos de CPU durante el período de espera, lo que reduce el uso de la CPU durante la espera ocupada. Cuando no hay datos disponibles, el hilo del consumidor girará y esperará un período de tiempo, luego dormirá durante un corto período de tiempo e intentará obtener datos nuevamente. Es adecuado para escenarios con requisitos de latencia altos porque puede reducir el uso de la CPU hasta cierto punto, pero también puede provocar un cierto aumento en la latencia.

YieldingWaitStrategy
YieldingWaitStrategy es una de las estrategias que se pueden utilizar en sistemas de baja latencia. Durante el período de espera, utilice el método Thread.yield() para ceder recursos de CPU y brindar oportunidades de ejecución a otros subprocesos. Esta estrategia se recomienda en escenarios que requieren un rendimiento extremadamente alto y la cantidad de líneas de procesamiento de eventos es menor que la cantidad de núcleos lógicos de la CPU.

BusySpinWaitStrategy
tiene el mejor rendimiento y es adecuado para sistemas de baja latencia. Utilice un bucle para intentar obtener datos continuamente sin renunciar a los recursos de la CPU. Esta estrategia se recomienda en escenarios que requieren un rendimiento extremadamente alto y la cantidad de subprocesos de procesamiento de eventos es menor que la cantidad de núcleos lógicos de la CPU; por ejemplo, la CPU permite hiperprocesamiento.

PhasedBackoffWaitStrategy
utiliza una estrategia spin + yield + personalizada, que se utiliza en escenarios donde los recursos de la CPU son escasos y el rendimiento y la latencia no son importantes.

En términos generales, BlockingWaitStrategy es adecuado para escenarios con bajos requisitos de rendimiento, SleepingWaitStrategy es adecuado para escenarios que son relativamente sensibles a la latencia, YieldingWaitStrategy es adecuado para escenarios con baja latencia y alta concurrencia, y BusySpinWaitStrategy es adecuado para escenarios con requisitos de latencia muy altos y alta concurrencia escena


DistuptorRendimiento

La latencia y el rendimiento de Disruptor son mucho mejores que los de ArrayBlockingQueue.
La razón por la que tiene un mejor rendimiento que varias colas integradas en jdk es que adopta la estrategia sin bloqueo de cas y es muy amigable para la CPU.

memoria preasignada

Crea un búfer de anillo espacialmente continuo al principio para almacenar colas de anillo y objetos de eventos, mejora la localidad de los datos y la tasa de aciertos de la caché y evita aplicaciones y liberaciones de memoria posteriores.

Usar caché de CPU

Generalmente, nuestro código está en el nivel de memoria, y Distuptor usa el área de caché cpu-cache. Las velocidades de lectura y escritura de los dos son cientos de veces diferentes. La estrategia de llenado de la línea de búfer se utiliza para garantizar que los datos estén siempre en
el cpu-cache y no se escribirá en la memoria principal. Disfrute de la lectura y escritura de alta velocidad del caché de la CPU.

Llenado de líneas de búfer
La caché de la CPU realiza operaciones de lectura y escritura en unidades de líneas de caché (línea de caché), generalmente de 64 bytes. Disruptor utiliza el concepto de llenado de línea de caché para garantizar que las estructuras de datos relacionadas estén en la misma línea de caché, evitando que varios subprocesos modifiquen datos diferentes en la misma línea de caché al mismo tiempo, reduciendo así el fenómeno del intercambio falso de líneas de caché.

Disruptor realizará la alineación de la línea de caché en los objetos de evento para garantizar que cada objeto de evento ocupe toda la línea de caché . El propósito de esto es evitar múltiples objetos de eventos en la misma línea de caché y reducir el fenómeno del intercambio falso de líneas de caché.

estructura de datos

La estructura de matriz es adecuada para la canalización de múltiples etapas de la CPU y la predicción de bifurcación de la CPU, lo que reduce el costo de tiempo de las operaciones posteriores.


Uso del disuptor

Pasos de configuración del disuptor

1. Defina un objeto de evento: primero, debe definir un objeto de evento que contenga datos que deben pasarse entre diferentes subprocesos.

public class YourEvent {
    // 定义事件数据的成员变量
}

2. Defina controladores de eventos (consumidores): cree uno o más controladores de eventos para procesar objetos de eventos.

public class YourEventHandler implements EventHandler<YourEvent> {
    public void onEvent(YourEvent event, long sequence, boolean endOfBatch) {
        // 处理事件数据
    }
}

3. Cree una instancia de Disruptor: utilice el método de creación de Disruptor para crear una instancia de Disruptor y establezca el tamaño del búfer circular y la fábrica de eventos.

int bufferSize = 1024;
Disruptor<YourEvent> disruptor = new Disruptor<>(YourEvent::new, bufferSize, Executors.defaultThreadFactory());

4. Conecte el procesador de eventos: Conecte el procesador de eventos al Disruptor.

disruptor.handleEventsWith(new YourEventHandler());

5. Inicie el Disruptor: utilice el método de inicio para iniciar el Disruptor e iniciar el procesamiento de eventos.

disruptor.start();

6. Publicar eventos: publique eventos a través del método PublishEvent del Disruptor y envíe datos del evento al búfer circular.

RingBuffer<YourEvent> ringBuffer = disruptor.getRingBuffer();
long sequence = ringBuffer.next();
YourEvent event = ringBuffer.get(sequence);
// 设置事件数据
ringBuffer.publish(sequence);

Estrategia de productor único/múltiple

Usando un solo consumidor:

La forma más sencilla es utilizar solo un hilo de consumidor para consumir mensajes. Esto garantiza que los mensajes se procesen en el orden publicado por el productor, ya que solo un consumidor procesa los mensajes.

Usando múltiples consumidores:

Sin embargo, el orden de los mensajes procesados ​​por cada consumidor sigue siendo el mismo.
Si necesita utilizar varios subprocesos de consumidor para procesar mensajes en paralelo, pero aún desea mantener el orden de los mensajes, puede utilizar la siguiente estrategia:

  • Utilice el WorkProcessor de Disruptor para crear subprocesos de consumo. WorkProcessor garantizará que cada hilo de consumidor solo procese su propia secuencia de mensajes independiente y mantenga el orden de los mensajes. De esta manera, cada hilo de consumidor puede procesar mensajes de forma independiente sin competir con otros hilos de consumidor.
  • Agregue identificadores como números de secuencia o marcas de tiempo a los mensajes y los consumidores podrán ordenarlos en función de estos identificadores al procesar mensajes. Después de recibir los mensajes, el consumidor puede ordenarlos según el identificador para garantizar que se procesen en el orden especificado.

Supongo que te gusta

Origin blog.csdn.net/giveupgivedown/article/details/132503213
Recomendado
Clasificación