Estoy tratando de entender la disruptor patrón. He visto a la InfoQ de vídeo y trató de leer su artículo. Entiendo que hay un búfer de anillo de por medio, que es inicializado como una gran matriz de tomar ventaja de caché de la localidad, eliminar la asignación de memoria nueva.

Parece que hay uno o más atómica enteros que mantener un seguimiento de las posiciones. Cada «evento» parece un identificador único y su posición en el anillo se encuentra encontrando su módulo con respecto al tamaño del anillo, etc., etc.

Por desgracia, no tengo un sentido intuitivo de cómo funciona. He hecho muchas aplicaciones comerciales y estudió la actor, modelo, miró SEDA, etc.

En su presentación mencionó que este patrón es, básicamente, cómo los routers de trabajo; sin embargo no he encontrado ninguna buena descripción de cómo los routers de trabajo.

Hay algunos buenos consejos para una mejor explicación?

InformationsquelleAutor Shahbaz | 2011-07-02

5 Comentarios

  1. 208

    El Código de Google proyecto referencia de un documento técnico sobre la aplicación de la búfer de anillo, sin embargo es un poco seco, académico y duro ir para alguien que quiere aprender cómo funciona. Sin embargo, hay algunos posts que han comenzado a explicar el funcionamiento interno de una manera más legible. Hay un explicación de búfer de anillo que es el núcleo de la disruptor patrón, un descripción de el consumidor barreras (la parte relativa a la lectura de la disruptor) y algunos información sobre el manejo de múltiples productores disponible.

    La descripción más simple de la Disruptor es: es una forma de enviar mensajes entre los hilos de la manera más eficiente posible. Puede ser utilizado como una alternativa a una cola, pero también comparte una serie de características con SEDA y Actores.

    En comparación con las Colas:

    El Disruptor proporciona la capacidad de transmitir un mensaje en otro de los hilos, el despertar de seguridad si se requiere (similar a un BlockingQueue). Sin embargo, hay 3 distintas diferencias.

    1. El usuario de la Disruptor define cómo se almacenan los mensajes mediante la extensión de clase de Entrada y proporcionar una fábrica para hacer el preallocation. Esto permite, ya sea de la memoria de reutilización (copiar) o de la Entrada puede contener una referencia a otro objeto.
    2. Poner mensajes en el Disruptor es un 2-fase de proceso, en primer lugar una ranura se reivindica en el búfer de anillo, que proporciona al usuario con la Entrada que puede ser llenado con los datos apropiados. A continuación, la entrada debe estar comprometido, este 2 de la fase de enfoque es necesario para permitir el uso flexible de la memoria se mencionó anteriormente. Es la confirmación que hace que el mensaje visible para el consumidor hilos.
    3. Es la responsabilidad del consumidor para mantener un seguimiento de los mensajes que se han consumido desde el búfer de anillo. Trasladar esta responsabilidad de distancia desde el búfer de sí mismo ayudó a reducir la cantidad de escritura disputa cada subproceso mantiene su propio contador.

    En comparación con Actores

    El Actor modelo está más cerca de la Disruptor de la mayoría de los otros modelos de programación, especialmente si utiliza el BatchConsumer/BatchHandler clases que se proporcionan. Estas clases ocultar todas las complejidades de mantener el consumo de los números de secuencia y proporcionar un conjunto de simples devoluciones de llamada cuando se producen eventos. Sin embargo, hay un par de diferencias sutiles.

    1. El Disruptor utiliza un 1 hilo – 1 modelo de consumo, donde los Actores utilizan un N:M modelo, es decir, usted puede tener tantos actores como te gusta y se distribuirá a través de un número fijo de hilos (generalmente de 1 por cada núcleo).
    2. La BatchHandler interfaz proporciona un adicional (y muy importante) de devolución de llamada onEndOfBatch(). Esto permite que la lentitud de los consumidores, por ejemplo, aquellos que hacen de e/S para eventos de lote en conjunto para mejorar el rendimiento. Es posible hacer lotes en otro Actor marcos, sin embargo, como casi todos los otros marcos no proporcionan una devolución de llamada en la final del lote deberá utilizar un tiempo de espera para determinar el final del lote, lo que genera una baja latencia.

    En comparación con SEDA

    LMAX construido el Disruptor patrón para reemplazar una SEDA enfoque basado.

    1. Una de las principales mejoras que proporcionó más de la SEDA fue la habilidad para trabajar en paralelo. Para ello el Disruptor soporta multi-fundición de los mismos mensajes (en el mismo orden) a varios consumidores. Esto evita la necesidad de bifurcación de las etapas del pipeline.
    2. También permitir a los consumidores a esperar los resultados de otros consumidores, sin tener que hacer otra cola de fase entre ellos. Un consumidor puede simplemente ver el número de secuencia de un consumidor que es dependiente. Esto evita la necesidad de unirse a las etapas de pipeline.

    En comparación con Barreras de Memoria

    Otra forma de pensar es como una forma estructurada, ordenada barrera de memoria. Donde el productor de la barrera de las formas de escritura de la barrera y el consumidor barrera es la lectura de la barrera.

    • Gracias Michael. Su escritura-up y los enlaces en los que siempre me han ayudado a tener una mejor idea de cómo funciona. El resto, creo que sólo tengo que entenderlo.
    • hola Michael, por favor revise mi respuesta para los errores.
    • Todavía tengo preguntas: (1) ¿cómo funciona el ‘commit’ trabajo? (2) Cuando el búfer está lleno, ¿cómo es que el productor detectar que todos los consumidores se han visto los datos de manera que el productor puede re-utilizar las entradas?
    • probablemente vale la pena publicar una nueva pregunta.
    • No debería la primera frase de la última viñeta (número 2) en en Comparación con SEDA en lugar de la lectura «Nosotros también permitir a los consumidores a esperar los resultados de otros consumidores con el hecho de tener que poner otra cola de fase entre ellos» leer «también permitir a los consumidores a esperar los resultados de otros consumidores sin de tener que poner otra cola de fase entre ellos» (es decir. «con» debería sustituirse por «sin»)?
    • sí que debería.
    • el enlace para el papel técnico es obsoleto

  2. 135

    Primero nos gustaría entender el modelo de programación que ofrece.

    Hay uno o más de los escritores. Hay uno o más lectores. Hay una línea de entradas, totalmente ordenado de lo viejo a lo nuevo (como en la foto de izquierda a derecha). Los escritores pueden agregar nuevas entradas en el extremo derecho. Cada lector lee las entradas de forma secuencial de izquierda a derecha. Los lectores no pueden leer los últimos escritores, obviamente.

    No existe el concepto de entrada de eliminación. Yo uso el «lector» en lugar de «consumidor» para evitar la imagen de las entradas de ser consumido. Sin embargo, entendemos que las entradas a la izquierda de el último lector convertido en inútil.

    Generalmente, los lectores pueden leer simultáneamente y de forma independiente. Sin embargo, podemos declarar las dependencias entre los lectores. Lector de dependencias puede ser arbitraria gráfico acíclico. Si el lector de B depende del lector, lector de B no puede leer los últimos lector A.

    Lector de dependencia surge porque el lector puede anotar una entrada, y lector de B depende de que la anotación. Por ejemplo, hace algunos cálculo en una entrada, y almacena el resultado en el campo a en la entrada. Una, a continuación, seguir adelante, y ahora B puede leer la entrada, y el valor de a Un almacenados. Si el lector de C no depende de a, C no debe intentar leer a.

    Este es de hecho un interesante modelo de programación. Independientemente de los resultados, el modelo solo puede beneficiar a un montón de aplicaciones.

    De curso, LMAX es el principal objetivo es el rendimiento. Se utiliza una pre-asignados anillo de entradas. El anillo es lo suficientemente grande, pero es limitada por lo que el sistema no se ha cargado más allá de la capacidad de diseño. Si el anillo está lleno, escritor(s) habrá que esperar hasta el más lento de los lectores de antelación y hacer espacio.

    Entrada de objetos están pre-asignados y vivir para siempre, para reducir el costo de la recogida de basura. Nosotros no inserte la nueva entrada de objetos o eliminar la entrada anterior de los objetos, en cambio, un escritor pide un pre-existentes a la entrada, rellenar sus campos, y notificar a los lectores. Esta aparente 2-fase de acción es realmente simplemente una acción atómica

    setNewEntry(EntryPopulator);
    
    interface EntryPopulator{ void populate(Entry existingEntry); }
    

    De Pre-asignación de las entradas también significa adyacentes entradas (muy probable) localizar en la zona adyacente de células de memoria, y porque los lectores de leer las entradas de forma secuencial, esto es importante utilizar cachés de la CPU.

    Y muchos de los esfuerzos para evitar el bloqueo, CAS, incluso la memoria de barrera (por ejemplo, el uso de un no-volátil variable de secuencia de si hay un solo escritor)

    Para los desarrolladores de los lectores: las Diferentes anotaciones que los lectores deben escribir en diferentes campos, para evitar escribir en disputa. (En realidad, se debe escribir en diferentes líneas de caché.) Una anotación lector no debe tocar nada que otros no dependiente de los lectores pueden leer. Esta es la razón por la que me dicen que estos lectores anotar entradas, en lugar de modificar entradas.

    • Se ve bien para mí. Me gusta el uso del término anotar.
    • +1 esta es la única respuesta que intenta describir cómo el disruptor patrón funciona realmente, como el OP preguntó.
    • WOW! Descripción excelente!
    • Si el anillo está lleno, escritor(s) habrá que esperar hasta el más lento de los lectores avanzar y hacer de la habitación. – uno de los problemas w/ FIFO de colas es conseguir muy fácilmente bajo carga, ya que realmente no intentar de nuevo la presión hasta que obtener rellenos y la latencia ya es alto.
    • Se puede también escribir explicación similar para el escritor? ¿
    • Me gusta, pero he encontrado este » escritor pide un pre-existentes a la entrada, rellenar sus campos, y notificar a los lectores. Esta aparente 2-fase de acción es realmente simplemente una acción atómica» confuso y posiblemente mal? No hay ningún «notificar» a la derecha? También no es atómica, es tan solo un eficaz/visible escribir, ¿correcto? Gran respuesta sólo el lenguaje ambiguo?
    • Gracias – la Mejor explicación hasta ahora he visto en disruptor patrón – Podría explicar, por último párrafo un poco más … o convertir esto en un artículo, ya que necesita un poco de visualización !

  3. 17

    De hecho, me tomé el tiempo para estudiar el origen real, por pura curiosidad, y la idea detrás de esto es bastante simple. La versión más reciente en el momento de escribir este post es 3.2.1.

    Hay un buffer de almacenamiento de pre-asignados eventos que contendrá los datos de los consumidores a leer.

    El buffer está respaldado por una matriz de indicadores (matriz de enteros) de su longitud que se describe la disponibilidad de búfer de ranuras (ver más detalles). La matriz se accede como un java#AtomicIntegerArray, por lo que para el propósito de este explenation usted puede asumir que es uno.

    Puede haber cualquier número de productores. Cuando el productor quiere escribir en el búfer, un largo número es generado (como en llamar AtomicLong#getAndIncrement, el Disruptor en realidad utiliza su propia aplicación, pero funciona de la misma manera). Vamos a llamar a esto generó una larga producerCallId. De manera similar, un consumerCallId se genera cuando un consumidor TERMINA la lectura de una ranura de un búfer. El más reciente consumerCallId se accede.

    (Si hay muchos consumidores, la llamada con el menor id es elegido.)

    Estos identificadores se comparan a continuación, y si la diferencia entre los dos es menor que el búfer lado, el productor puede escribir.

    (Si el producerCallId es mayor que la reciente consumerCallId + bufferSize, significa que el buffer está lleno, y el productor se ve obligado a bus-esperar hasta que el punto esté disponible.)

    El productor a continuación, se asigna la ranura en el búfer basado en su callId (que es prducerCallId modulo bufferSize, pero desde el bufferSize es siempre una potencia de 2 (límite impuesta en el búfer de la creación), la realidad de la operación utilizado es producerCallId & (bufferSize – 1)). Es entonces libre de modificar el evento en que la ranura.

    (El algoritmo es un poco más complicado, la participación de almacenamiento en caché de los últimos consumerId en un atómicas independiente de referencia, para la optimización de los propósitos).

    Cuando el evento fue modificado, el cambio es «publicado». Cuando la publicación de la respectiva ranura en la bandera de la matriz se rellena con la actualización de la bandera. El valor del indicador es el número del bucle (producerCallId dividido por bufferSize (de nuevo desde bufferSize es potencia de 2, la operación es un desplazamiento derecha).

    De una manera similar puede haber cualquier número de consumidores. Cada vez que un consumidor quiere acceder al buffer, una consumerCallId se genera (dependiendo de cómo los consumidores se han añadido a la disruptor atómica utilizada en la generación de identificadores puede ser compartida o separada para cada uno de ellos). Este consumerCallId se compara con el más reciente producentCallId, y si es menor de los dos, el lector se deja progresar.

    (Del mismo modo, si el producerCallId incluso a la consumerCallId, significa que el buffer se empety y el consumidor se ve obligado a esperar. El modo de espera se define por un WaitStrategy durante disruptor de la creación.)

    Para los consumidores individuales (los que tienen su propio número de identificación de generador), la siguiente cosa que se comprueba es la capacidad de lote de consumir. Las ranuras en el búfer se examinan en el orden de la correspondiente a la consumerCallId (el índice se determina de la misma manera que para los productores), a la correspondiente a la reciente producerCallId.

    Son examinados en un bucle al comparar el valor del indicador escrito en la bandera de la matriz, en contra de un indicador de valor generado para el consumerCallId. Si los flags que significa que los productores de llenado de las ranuras que se ha comprometido sus cambios. Si no, el bucle se rompe, y el más alto comprometidos changeId se devuelve. Las ranuras de ConsumerCallId a recibido en changeId puede ser consumido en lote.

    Si un grupo de consumidores de leer juntos (con id compartido generador), cada uno de ellos sólo tiene un único callId, y sólo la ranura para que solo callId se comprueba y se volvió.

  4. 7

    De este artículo:

    El disruptor patrón es una dosificación de la cola de copia de seguridad a través de una circular
    de la matriz (es decir, el búfer de anillo) lleno de pre-asignados de transferencia de
    los objetos que utiliza la memoria-barreras para sincronizar los productores y
    los consumidores a través de secuencias.

    De memoria-las barreras son de tipo difícil de explicar y Trisha del blog ha hecho el mejor intento en mi opinión con este post: http://mechanitis.blogspot.com/2011/08/dissecting-disruptor-why-its-so-fast.html

    Pero si no quieren sumergirse en los detalles de bajo nivel sólo puede saber que memoria-barreras en Java son implementadas a través de la volatile palabra clave o a través de la java.util.concurrent.AtomicLong. El disruptor patrón de secuencias de AtomicLongs y se comunican de ida y vuelta entre los productores y los consumidores a través de la memoria-barreras en lugar de los bloqueos.

    Creo que es más fácil entender un concepto a través de código, de modo que el código de abajo es un simple helloworld de CoralQueue, que es un disruptor patrón de la implementación realizada por CoralBlocks con la que estoy afiliado. En el código siguiente se puede ver cómo el disruptor patrón implementa lotes y cómo el anillo-buffer (es decir, la circular de la matriz) permite la basura de la comunicación libre entre dos subprocesos:

    package com.coralblocks.coralqueue.sample.queue;
    
    import com.coralblocks.coralqueue.AtomicQueue;
    import com.coralblocks.coralqueue.Queue;
    import com.coralblocks.coralqueue.util.MutableLong;
    
    public class Sample {
    
        public static void main(String[] args) throws InterruptedException {
    
            final Queue<MutableLong> queue = new AtomicQueue<MutableLong>(1024, MutableLong.class);
    
            Thread consumer = new Thread() {
    
                @Override
                public void run() {
    
                    boolean running = true;
    
                    while(running) {
                        long avail;
                        while((avail = queue.availableToPoll()) == 0); //busy spin
                        for(int i = 0; i < avail; i++) {
                            MutableLong ml = queue.poll();
                            if (ml.get() == -1) {
                                running = false;
                            } else {
                                System.out.println(ml.get());
                            }
                        }
                        queue.donePolling();
                    }
                }
    
            };
    
            consumer.start();
    
            MutableLong ml;
    
            for(int i = 0; i < 10; i++) {
                while((ml = queue.nextToDispatch()) == null); //busy spin
                ml.set(System.nanoTime());
                queue.flush();
            }
    
            //send a message to stop consumer...
            while((ml = queue.nextToDispatch()) == null); //busy spin
            ml.set(-1);
            queue.flush();
    
            consumer.join(); //wait for the consumer thread to die...
        }
    }
    

Dejar respuesta

Please enter your comment!
Please enter your name here