Por que o Disruptor é tão rápido

O Disruptor é uma estrutura de código-fonte aberto e eficiente produtor-consumidor.É difícil explicar diretamente o que essa estrutura faz, mas pode ser entendida como Java BlockingQueue. Isto não é muito mais fácil de entender, este é um produtor - fila de consumidor, mas o seu desempenho do que o BloockingQueuemuito mais conhecido como uma única máquina pode ter milhões de TPS.

Recursos do disruptor

O disruptor possui as três características principais a seguir:

  • Multicast de evento
  • Aloque memória antecipadamente para eventos
  • Operação sem bloqueio

Geralmente, quando usamos a fila, a mensagem na fila será 消费者usada por apenas um , mas no Disruptor, a mesma mensagem pode ser processada por vários consumidores ao mesmo tempo e vários consumidores são paralelos.

Ao mesmo tempo quanto possível, uma pluralidade de dados para ser utilizado em locais, tais como a necessidade de pedido de dados é armazenado no registo , a cópia de segurança para o computador remoto , processamento de serviço , como se mostra abaixo:

Se uma mensagem puder ser processada apenas por um consumidor, as três lógicas de processamento acima deverão ser concluídas linearmente ou colocadas no consumidor e processadas de forma assíncrona. Se essas operações puderem ser divididas em vários consumidores para consumir a mesma mensagem em paralelo , a eficiência do processamento será muito melhorada.

Dessa forma, é necessário garantir que uma mensagem seja processada por todos os consumidores antes de começar a processar a próxima. Consumidores não podem processar mensagens diferentes ao mesmo tempo, portanto, ferramentas como CyclicBarrier em Java são necessárias para garantir que todos os consumidores possam processar a próxima mensagem ao mesmo tempo , SequenceBarrier é implementado no Disruptor para realizar essa função.

O objetivo do Disruptor é aplicar em um ambiente de baixa latência. Em sistemas de baixa latência, é necessário reduzir ou não alocar memória.Em Java, é reduzir o tempo de pausa causado pela coleta de lixo. No Disruptor, use o RingBuffer para atingir esse objetivo, crie objetos no RingBuffer antecipadamente e use esses objetos repetidamente para evitar a coleta de lixo.Esta implementação é segura para threads.

No Disruptor, a implementação da segurança do encadeamento basicamente não usa bloqueios, mas usa mecanismos sem bloqueios, como o CAS, para garantir a segurança do encadeamento.

Conceito básico

Antes de entendermos formalmente o Disruptor, precisamos entender alguns conceitos principais: o código do Disruptor não é complicado, basicamente gira em torno desses conceitos.

  • Produtor: O produtor dos dados, o próprio produtor não tem nada a ver com o Disruptor, pode ser qualquer código que produz dados, ou mesmo um loop for
  • Evento: Produtor produz dados que os usuários podem definir de acordo com suas próprias necessidades
  • RingBuffer: Usado para armazenar a estrutura de dados do Event, alocar memória antecipadamente e evitar a criação de objetos durante a execução do programa.
  • EventHandler: Consumidor, implementado pelos próprios usuários
  • Sequência: usada para identificar os componentes no disruptor, a coordenação entre vários componentes depende dele
  • Sequenciador: o mecanismo principal do Disruptor, que implementa o algoritmo de simultaneidade principal para garantir a entrega correta de mensagens entre produtores e consumidores
  • SequenceBarrier: usado para garantir que todos os consumidores possam processar novas mensagens ao mesmo tempo
  • WaitStrategy: Estratégia de espera do consumidor
  • EventProcessor: implementação concreta de entrega de mensagens aos consumidores

Os componentes acima compõem o Disruptor.O tamanho do código de toda a estrutura é realmente muito pequeno, deve ter menos de 7000 linhas e o código é muito limpo. O código basicamente não usa herança, mas usa programação e composição orientada a interface ; portanto, o acoplamento entre os códigos é muito baixo.

RingBuffer e Sequencer são os dois componentes mais importantes: o primeiro é usado para armazenar mensagens e o segundo controla a produção e o consumo ordenados de mensagens.

O objetivo principal do Disruptor é melhorar o rendimento do programa, para que o programa também seja alcançado em torno desses objetivos, principalmente fazendo o seguinte:

  • Reduzir a coleta de lixo
  • Permitir que mensagens sejam processadas em paralelo por vários consumidores
  • Use algoritmos sem bloqueio para obter simultaneidade
  • Preenchimento de linha de cache

RingBuffer é um contêiner para armazenar mensagens, e a implementação interna usa uma matriz:

private final Object[] entries;
复制代码

Antes do Disruptor iniciar, você precisa especificar o tamanho dos dados e inicializar esta matriz:

private void fill(EventFactory<E> eventFactory)
{
    for (int i = 0; i < bufferSize; i++)
    {
        entries[BUFFER_PAD + i] = eventFactory.newInstance();
    }
}
复制代码

Depois que a matriz é inicializada, ela não é mais reciclada.Todas as mensagens reciclam esses objetos já criados, portanto essa é uma matriz circular.A implementação do RingBuffer é mostrada na figura a seguir:

Então, ao operar na matriz circular, é necessário controlar o acesso do produtor e do consumidor à matriz. Por um lado, como o Disruptor suporta vários produtores e vários consumidores, é necessário garantir a segurança do encadeamento. Para garantir o desempenho, nenhum bloqueio é usado para garantir a segurança do encadeamento (apenas o BlockingWaitStrategy usa bloqueios). No controle de acesso do RingBuffer, Use principalmente o CAS para concluir:

protected final E elementAt(long sequence)
{
    return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT));
}
复制代码

Por outro lado, o acesso à velocidade dos produtores e consumidores, os produtores não podem escrever mensagens não consumidas, o que causará a perda de mensagens.No RingBuffer, os ponteiros de cabeça e cauda não são usados ​​para controle, mas através do Sequence Para controlar, quando o produtor grava dados, o número de série atual mais a quantidade de dados a serem gravados serão comparados com a posição do consumidor para verificar se há espaço suficiente para gravar.

No RingBuffer, existe este pedaço de código:

abstract class RingBufferPad
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}
复制代码

Esse código é chamado de preenchimento de linha de cache. Quando se trata disso, é necessário entender o mecanismo de cache da CPU. Como a velocidade de acesso da memória está muito longe da velocidade da CPU, o cache da CPU também é adicionado entre a CPU e a memória. O nível 3 é geralmente adicionado, o primeiro e o segundo níveis são exclusivos do núcleo da CPU e o cache do terceiro nível é compartilhado entre vários núcleos.

Em muitos casos, queremos armazenar em cache alguns valores que não serão alterados no cache da CPU, como a variável final em Java, para que a velocidade do cache da CPU possa ser maximizada, mas o cache da CPU tenha um recurso ao armazenar em cache dados vontade linha de cache da CPU como uma unidade, por isso, se você definir uma variável estará perto de uma mudança final de variáveis, as variáveis mudam a cada vez, os dados serão escritos de volta à memória novamente, então a última variável também não está mais no cache A CPU é armazenada em cache, portanto, é necessário preencher a parte frontal e traseira da linha de cache para garantir que nenhum outro dado seja armazenado em cache:

abstract class RingBufferPad
{
    // 填充缓存行的前部分
    protected long p1, p2, p3, p4, p5, p6, p7;
}
abstract class RingBufferFields extends RingBufferPad{ 
    ......
    // 下面需要被缓存到 CPU 缓存的数据
    private final long indexMask; 
    private final Object[] entries; 
    protected final int bufferSize;
    protected final Sequencer sequencer; 
    ...... 
}
public final class RingBuffer extends RingBufferFields implements Cursored, EventSequencer, EventSink{ 
    ...... 
    // 填充缓存行的后部分
    protected long p1, p2, p3, p4, p5, p6, p7; 
    ......
}
复制代码

Dessa forma, depois que os dados do RingBufferFields são carregados no cache da CPU, não há necessidade de ler a memória.

O disruptor melhora o desempenho por várias medidas, e é por isso que é tão rápido.

Texto original

Siga a conta pública do WeChat e fale sobre outras

Acho que você gosta

Origin juejin.im/post/5e99cb25e51d4546ee76cda8
Recomendado
Clasificación