Sunday 25 June 2017

Zeromq Sistema De Negociação


Zeromq-dev Uma plataforma de negociação pode contar com a OslashMQ Eu tenho um cliente financeiro exchangetrading onde os dados (de mercado) precisam ser passados ​​de um sistema de negociação central para um depósito sobre TCPIP. O que eu continuo vendo no Guia é que quotIn alguns casos raros MQ gotas mensagens silenciosamente, se não houver nenhuma estratégia óbvia para recuperar a partir do errorquot. Obviamente, não podemos permitir que nenhuma mensagem seja descartada ignorada descartada silenciosamente. Eu estou passando pelo guia, e jogando com vários padrões para ter uma melhor idéia de MQ API e filosofia, mas eu realmente aprecio o conhecimento de alguém que já se sente bastante competente com o produto para me salvar um pouco do oilquot quotmidnight: 1. Existe um mecanismo construído para, por exemplo, Começar a carregar mensagens para o disco, no caso de uma fila está perto de ser transbordado Ou apenas para parar em vez de descartar mensagens silenciosamente 2. Mad Black Box é algo que parece o mais próximo do que precisamos, no entanto, já temos editores próprios sharded. Poderia destruí-los mais para os diferentes candidatos do Subscritor. Quais são os requisitos necessários para evitar uma sobrecarga de quotsubscriber39 (por exemplo, o lado do processamento seria mais lento, pois deserializa provavelmente os protobufs do google e persiste as mensagens no disco) 3. Caso a resposta a 3 seja SIM, não é adicional Sharding interno introduzir um novo ponto de falha (por exemplo, um fio morre, etc), caso em que existe algum tipo de mecanismo de recuperação reconstruída (semelhante ao PGM, mas um pouco mais alto, uma vez que estamos lidando com ZMTP messages) Pesquisa Discussões Martin Sustrik Hi Anatoly, There39s ZMQSWAP opção nas versões 2.x. A aplicação de contrapressão (quotSTOPquot) funciona para os padrões REQREP e PUSHPULL. Com o PUBSUB, a aplicação de contrapressão combinada com o assinante slowdead pode levar a latências ilimitadas, até ao deadlock de todo o sistema de distribuição de mensagens. Eu acho que você está falando de dados de mercado aqui. Se o editor estiver sobrecarregado, pense em criar uma topologia mais complexa com dispositivos no meio para distribuir a carga. Se o ponto que armazena a mensagem em 7 de setembro de 2011 às 6:51 am 1. Existe um mecanismo embutido para, por exemplo, Inicie o carregamento de mensagens no disco, caso uma fila esteja perto de ser transbordado Ou apenas STOP em vez de descartar as mensagens em silêncio. A opção ZMQSWAP em versões 2.x. A aplicação de contrapressão (quotSTOPquot) funciona para os padrões REQREP e PUSHPULL. Com o PUBSUB, a aplicação de contrapressão combinada com o assinante slowdead pode levar a latências ilimitadas, até ao deadlock de todo o sistema de distribuição de mensagens. 2. Mad Black Box é algo que parece o mais próximo do que precisamos, no entanto, já temos editores próprios sharded. Poderia destruí-los mais por meio de QuadPUSHquoters diferentes do Subscritor para evitar uma sobrecarga de quotsubscriber39 (por exemplo, o lado do processamento seria mais lento, pois deserializa provavelmente os protobufs do google e persiste mensagens no disco). Eu acho que você está falando de dados do mercado aqui. Se o editor estiver sobrecarregado, pense em criar uma topologia mais complexa com dispositivos no meio para distribuir a carga. 3. Caso a resposta a 3 seja SIM, um sharding interno adicional não introduz um novo ponto de falha (por exemplo, uma linha morre, etc.), caso em que existe algum tipo de mecanismo de recuperação de recuperação incorporado (semelhante a PGM, mas um pouco de um nível mais alto, uma vez que estamos lidando com mensagens ZMTP) Se o ponto que armazena a mensagem morre, a mensagem é perdida. Isso se aplica a PGM ou qualquer outro mecanismo. A única opção é armazenar as mensagens em um disco, com óbvio penalidade de desempenho. Mesmo assim, se o disco morre, as mensagens são perdidas. Para evitar que você tem que armazená-los em RAID, SAN ou somesuch. Se todo o RAID for destruído, etc. Em Wed, Sep 7, 2011 at 2:51 AM, Martin Sustrik escreveu: 1. Existe um mecanismo embutido para, por exemplo, Inicie o carregamento de mensagens no disco, caso uma fila esteja perto de ser transbordado Ou apenas STOP em vez de descartar as mensagens em silêncio. A opção ZMQSWAP em versões 2.x. A aplicação de contrapressão (quotSTOPquot) funciona para os padrões REQREP e PUSHPULL. Com o PUBSUB, a aplicação de contrapressão combinada com o assinante slowdead pode levar a latências ilimitadas, até ao deadlock de todo o sistema de distribuição de mensagens. 2. Mad Black Box é algo que parece o mais próximo do que precisamos, no entanto, já temos editores próprios sharded. Poderia destruí-los mais por meio de QuadPUSHquoters diferentes do Subscritor para evitar uma sobrecarga de quotsubscriber39 (por exemplo, o lado do processamento seria mais lento, pois deserializa provavelmente os protobufs do google e persiste mensagens no disco). Eu acho que você está falando de dados do mercado aqui. Se o editor estiver sobrecarregado, pense em criar uma topologia mais complexa com dispositivos no meio para distribuir a carga. 3. Caso a resposta a 3 seja SIM, um sharding interno adicional não introduz um novo ponto de falha (por exemplo, uma linha morre, etc.), caso em que existe algum tipo de mecanismo de recuperação de recuperação incorporado (semelhante a PGM, mas um pouco de um nível mais alto, uma vez que estamos lidando com mensagens ZMTP) Se o ponto que armazena a mensagem morre, a mensagem é perdida. Isso se aplica a PGM ou qualquer outro mecanismo. A única opção é armazenar as mensagens em um disco, com óbvio penalidade de desempenho. Mesmo assim, se o disco morre, as mensagens são perdidas. Para evitar que você tem que armazená-los em RAID, SAN ou somesuch. Se todo o RAID for destruído etc. WARNING: Este texto é obsoleto e se refere a uma versão antiga do MQ. Permanece aqui para o interesse histórico. NÃO UTILIZE ESTE APARELHO PARA APRENDER MQ. Introdução Como o MQ é destinado principalmente ao poder de negócios de negociação de ações, weve criou um aplicativo de exemplo que simula o funcionamento interno de uma bolsa de valores. O foco principal deste exemplo é mostrar como o MQ perfroms em real-world-like cenário. O diagrama abaixo mostra a arquitetura do aplicativo: o componente Gateway deve receber ordens de comerciantes na rede (usando o protocolo FIX ou um protocolo proprietário específico) e enviar respostas de volta aos comerciantes. No entanto, como o aplicativo de exemplo é destinado a mostrar quais são as possíveis throughputslatencies de tal sistema, gateway gera ordens aleatórias em vez de recebê-los dos comerciantes. Ao iniciar o gateway, você pode especificar o número de pedidos por segundo a serem gerados. Unidade de correspondência contém o núcleo da lógica de negócios da bolsa de valores. Corresponde às ordens uma a outra e produz negócios e citações. Nossa implementação é minimalista, baseada no algoritmo de correspondência timeprice (implementar algoritmo pro rata é deixado como um excercise para o leitor). Ainda assim, o algoritmo tem complexidade de O (1) e é fortemente otimizado. Nós vimos processo cerca de 18 milhões de pedidos por segundo. O componente Estatísticas recebe informações de desempenho geradas pelo gateway e pelo mecanismo correspondente e exibe-o em forma legível por humanos. Para tornar a leitura de estatísticas ainda mais conveniente, ferramenta gráfica simples é incluída no exemplo. Desempenho A captura de tela a seguir mostra exemplos de desempenho em duas caixas de 8 núcleos (3GHz) de alta qualidade, cada uma com 2 NIC 1GbE dedicadas. Tenha em mente que, se você executá-lo no hardware subestimado ou não conectado, pode ser ainda muito rápido, no entanto, você experimentará menores débitos de mensagens e mais picos de latência. Se você definir a taxa de mensagem muito alta, você pode até mesmo experiência falha gatway como o componente ticker usado para enviar ordens na taxa estável não será capaz de manter o ritmo. A linha amarela mostra latência de ida e volta, ou seja, quanto tempo demorou para passar da ordem do gateway para o mecanismo correspondente, processá-lo e enviar a confirmação do pedido de volta ao gateway. Em nosso teste de latência flutuou cerca de 200 microssegundo média. A menor linha de transferência (900.000 messagessecond) é a taxa de ordens passando do gateway para o mecanismo correspondente. A linha de transferência superior (2.300.000 mensagens) é a taxa de confirmações de pedidos, trocas e cotações de ações passadas do motor correspondente para o gateway. No total, vimos cerca de 3.200.000 mensagens por segundo passando pela rede. Construindo-o Para construir o exemplo, use a opção - with-exchange com configure: Para poder executar a ferramenta gráfica, você deve ter o Perl-Tk instalado (embalado como perl-tk no Debian), bem como Tk :: Graph from CPAN . Executando-o Por exemplo, temos a seguinte topologia de rede para executar o exemplo. As caixas representam máquinas individuais, as setas representam cabos físicos entre interfaces de rede individuais (marcadas por seus respectivos endereços IP): Há três caixas (test01, test02 e test03) conectadas à rede comutada com endereços IP respectivos de 192.168.0.1, 192.168.0.2 E 192.168.0.3. Além disso, há duas conexões diretas entre test02 e test03. Uma conexão conecta a interface de rede 10.0.0.1 em test02 com a interface de rede 10.0.0.2 em test03. Outro liga interface de rede 10.1.0.1 em test02 com interface de rede 10.1.0.2 em test03. Bem executado zmqserver e componente estatístico em test01, correspondência de motor em test02 e gateway em test03. Bem, use uma das conexões diretas entre test02 e test03 para passar as ordens de gatway para o motor de correspondência eo outro para passar confirmações, comércios e cotações de correspondência de motor para gateway. Primeiro, inicie zmqserver em test01: Inicie depois o componente estatístico em test01. Os parâmetros são a caixa onde o zmqserver está sendo executado ea interface de rede para receber informações estatísticas sobre: ​​Alternativamente, você pode canalizar os dados estatísticos para a ferramenta de gráficos: Agora inicie o mecanismo de correspondência. Fornecer o nome do host zmqserver, interface para receber mensagens e interface para enviar mensagens como parâmetros: Finalmente, execute o gatway. Forneça o local do zmqserver e o número de ordens a enviar por segundo como parâmetros: Conclusão O exemplo do Exchange permite testar o desempenho do MQ no cenário do mundo real. No entanto, obter latência estável em throughputs elevados é uma questão complicada dependente de ajuste geral de seu hardware, sistema operacional, ambiente de execução etc. Se você é sério sobre testes de desempenho, entre em contato conosco para ajudá-lo com a tarefa. Escrito: 1218019882Y. m.e Revisado: 1286694428Y. m.e Se você encontrou esta página útil, por favor avalie-o para que outros possam encontrá-lo. OslashMQ é um sistema de mensagens, ou mensagem orientada middleware, se você quiser. É utilizado em ambientes tão diversos como serviços financeiros, desenvolvimento de jogos, sistemas embarcados, pesquisa acadêmica e aeroespacial. Os sistemas de mensagens funcionam basicamente como mensagens instantâneas para aplicativos. Um aplicativo decide comunicar um evento para outro aplicativo (ou vários aplicativos), ele reúne os dados a serem enviados, bate o botão enviar e lá nós gomdashthe sistema de mensagens cuida do resto. Ao contrário das mensagens instantâneas, porém, os sistemas de mensagens não têm GUI e não assumem nenhum ser humano nos pontos finais capazes de intervenção inteligente quando algo der errado. Assim, os sistemas de mensagens precisam ser tolerantes a falhas e muito mais rápidos do que as mensagens instantâneas comuns. OslashMQ foi originalmente concebido como um sistema de mensagens ultra-rápido para negociação de ações e, portanto, o foco estava na otimização extrema. O primeiro ano do projeto foi gasto na elaboração de metodologias de benchmarking e na tentativa de definir uma arquitetura que fosse tão eficiente quanto possível. Mais tarde, aproximadamente no segundo ano de desenvolvimento, o foco mudou para fornecer um sistema genérico para a construção de aplicações distribuídas e suportar padrões de mensagens arbitrárias, vários mecanismos de transporte, ligações arbitrárias de idiomas, etc. Durante o terceiro ano o foco foi principalmente na melhoria da usabilidade E achatamento da curva de aprendizado. Weve adotado o BSD Sockets API, tentou limpar a semântica de padrões de mensagens individuais, e assim por diante. Esperemos que este capítulo dê uma idéia de como os três objetivos acima traduzidos na arquitetura interna do OslashMQ, e fornecer algumas dicas para aqueles que estão lutando com os mesmos problemas. Desde seu terceiro ano, a OslashMQ superou sua base de código, há uma iniciativa para padronizar os protocolos de fio que ele usa, e uma implementação experimental de um sistema de mensagens OslashMQ-like dentro do kernel do Linux, etc. Esses tópicos não são abordados neste livro. No entanto, você pode verificar os recursos on-line para obter mais detalhes: 250bpmconcepts. Groups. googlegroupsp-discuss-group. E 250bpmhits. 24.1. Aplicativo vs. Biblioteca OslashMQ é uma biblioteca, não um servidor de mensagens. Demorou vários anos trabalhando no protocolo AMQP, uma tentativa da indústria financeira de padronizar o protocolo de fio para mensagens de negócios, escrever uma implementação de referência para ele e participar de vários projetos de grande escala fortemente baseados em tecnologia de mensagens para perceber que há algo de errado com o modelo de cliente clássico De servidores de mensagens inteligentes (corretor) e clientes de mensagens mudos. Nossa principal preocupação na época era com o desempenho: Se há um servidor no meio, cada mensagem tem que passar a rede duas vezes (do remetente para o corretor e do corretor para o receptor) induzindo uma pena em termos de latência E throughput. Além disso, se todas as mensagens são passadas através do corretor, em algum momento seu limite para se tornar o gargalo. Uma preocupação secundária estava relacionada a implantações em larga escala: quando a implantação atravessa fronteiras organizacionais, o conceito de uma autoridade central que gerencia todo o fluxo de mensagens não se aplica mais. Nenhuma empresa está disposta a ceder o controle de um servidor em empresa diferente, existem segredos comerciais e há responsabilidade legal. O resultado na prática é que há um servidor de mensagens por empresa, com pontes escritas à mão para conectá-lo a sistemas de mensagens em outras empresas. Todo o ecossistema é, portanto, fortemente fragmentado, e manter um grande número de pontes para cada empresa envolvida doesnt tornar a situação melhor. Para resolver esse problema, precisamos de uma arquitetura totalmente distribuída, uma arquitetura onde cada componente pode ser possivelmente governado por uma entidade de negócios diferente. Dado que a unidade de gerenciamento na arquitetura baseada em servidor é o servidor, podemos resolver o problema instalando um servidor separado para cada componente. Nesse caso, podemos otimizar ainda mais o design, fazendo com que o servidor e o componente compartilhem os mesmos processos. O que acabamos com é uma biblioteca de mensagens. O OslashMQ foi iniciado quando tivemos uma idéia sobre como fazer o trabalho de mensagens sem um servidor central. Era necessário transformar todo o conceito de mensagens de cabeça para baixo e substituir o modelo de um armazenamento centralizado autônomo de mensagens no centro da rede com um endpoint inteligente, uma arquitetura de rede estúpida baseada no princípio de ponta a ponta. A consequência técnica dessa decisão foi que a OslashMQ, desde o início, era uma biblioteca, não uma aplicação. Entretanto, temos sido capazes de provar que essa arquitetura é mais eficiente (menor latência, maior taxa de transferência) e mais flexível (sua fácil de construir topologias complexas arbitrárias ao invés de estar ligada ao modelo hub-and-spoke clássico). Uma das conseqüências não intencionais, no entanto, foi que a opção pelo modelo de biblioteca melhorou a usabilidade do produto. Uma e outra vez os usuários expressam sua felicidade pelo fato de que eles não precisam instalar e gerenciar um servidor de mensagens autônomo. Acontece que não ter um servidor é uma opção preferida, pois reduz o custo operacional (não há necessidade de ter um administrador do servidor de mensagens) e melhora o tempo de lançamento no mercado (não há necessidade de negociar a necessidade de executar o servidor com o cliente, Ou a equipe de operações). A lição aprendida é que, ao iniciar um novo projeto, você deve optar pelo design da biblioteca, se possível. É muito fácil criar um aplicativo de uma biblioteca invocando-o de um programa trivial no entanto, é quase impossível criar uma biblioteca a partir de um executável existente. Uma biblioteca oferece muito mais flexibilidade aos usuários, ao mesmo tempo economizando esforços administrativos não triviais. 24.2. Global State As variáveis ​​globais não funcionam bem com as bibliotecas. Uma biblioteca pode ser carregada várias vezes no processo, mas mesmo assim, há apenas um único conjunto de variáveis ​​globais. A Figura 24.1 mostra uma biblioteca OslashMQ sendo usada a partir de duas bibliotecas diferentes e independentes. Em seguida, o aplicativo usa ambas as bibliotecas. Figura 24.1: OslashMQ sendo usado por bibliotecas diferentes Quando tal situação ocorre, ambas as instâncias de OslashMQ acessam as mesmas variáveis, resultando em condições de corrida, falhas estranhas e comportamento indefinido. Para evitar esse problema, a biblioteca OslashMQ não tem variáveis ​​globais. Em vez disso, um usuário da biblioteca é responsável por criar o estado global explicitamente. O objeto que contém o estado global é chamado de contexto. Enquanto do contexto de perspectiva dos usuários se parece mais ou menos com um conjunto de threads de trabalho, a partir da perspectiva OslashMQs é apenas um objeto para armazenar qualquer estado global que precisamos. Na figura acima, libA teria seu próprio contexto e libB teria seus próprios também. Não haveria nenhuma maneira para um deles quebrar ou subverter o outro. A lição aqui é bastante óbvia: Não use o estado global nas bibliotecas. Se o fizer, a biblioteca é susceptível de quebrar quando acontece de ser instanciado duas vezes no mesmo processo. 24.3. Desempenho Quando o OslashMQ foi iniciado, seu principal objetivo foi otimizar o desempenho. O desempenho dos sistemas de mensagens é expresso usando duas métricas: throughputmdashcomo muitas mensagens podem ser passadas durante um determinado período de tempo e latencymdashhow quanto demora para que uma mensagem passe de um endpoint para o outro. Que métrica devemos focar em qual é a relação entre os dois? Não é óbvio Execute o teste, divida o tempo total do teste por número de mensagens passadas eo que você recebe é a latência. Divida o número de mensagens por tempo e o que você recebe é throughput. Em outras palavras, a latência é o valor inverso do throughput. Trivial, right Em vez de iniciar a codificação de imediato, passamos algumas semanas investigando as métricas de desempenho em detalhes e descobrimos que a relação entre throughput e latência é muito mais sutil do que isso, e muitas vezes as métricas são bastante contra-intuitivas. Imagine A enviar mensagens para B. (Ver Figura 24.2.) O tempo total do teste é de 6 segundos. São 5 mensagens passadas. Portanto, a taxa de transferência é de 0,83 msgs (56) ea latência é de 1,2 segundos (65), à direita. Dê uma olhada no diagrama novamente. Demora um tempo diferente para cada mensagem para obter de A para B: 2 seg, 2,5 seg, 3 seg, 3,5 seg, 4 seg. A média é de 3 segundos, o que está bastante longe do nosso cálculo original de 1,2 segundo. Este exemplo mostra os equívocos que as pessoas estão inclinadas intuitivamente a fazer sobre métricas de desempenho. Agora dê uma olhada no throughput. O tempo total do teste é de 6 segundos. No entanto, em A demora apenas 2 segundos para enviar todas as mensagens. A partir da perspectiva Como o throughput é 2.5 msgssec (52). Em B leva 4 segundos para receber todas as mensagens. Assim, a partir da perspectiva Bs, a taxa de transferência é de 1,25 msgs (54). Nenhum destes números corresponde ao nosso cálculo original de 1.2 msgs. Para fazer uma longa história curta, latência e throughput são duas métricas diferentes que muito é óbvio. O importante é entender a diferença entre os dois e sua relação mútua. A latência pode ser medida apenas entre dois pontos diferentes no sistema. Não existe latência no ponto A. Cada mensagem tem sua própria latência. Você pode média de latências de várias mensagens no entanto, não há tal coisa como a latência de um fluxo de mensagens. A taxa de transferência, por outro lado, pode ser medida apenas em um único ponto do sistema. Há um throughput no remetente, theres um throughput no receptor, theres um throughput em qualquer ponto intermediário entre os dois, mas theres nenhuma coisa como throughput geral de todo o sistema. E throughput fazer sentido apenas para um conjunto de mensagens theres nenhuma coisa como throughput de uma única mensagem. Quanto à relação entre a taxa de transferência e latência, verifica-se que realmente existe um relacionamento no entanto, a fórmula envolve integrais e não vamos discutir aqui. Para obter mais informações, leia a literatura sobre a teoria das filas. Há muitas armadilhas mais em benchmarking os sistemas de mensagens que nós não vamos mais adiante. O estresse deve ser colocado sobre a lição aprendida: Certifique-se de compreender o problema que você está resolvendo. Mesmo um problema tão simples como torná-lo rápido pode levar muito trabalho para entender corretamente. O que é mais, se você não entender o problema, é provável que você construa suposições implícitas e mitos populares em seu código, tornando a solução falha ou pelo menos muito mais complexa ou muito menos útil do que poderia ser. 24.4. Caminho crítico Descobrimos durante o processo de otimização que três fatores têm um impacto crucial no desempenho: Número de alocações de memória Número de chamadas de sistema Modelo de simultaneidade No entanto, nem todas as alocações de memória nem todas as chamadas de sistema têm o mesmo efeito no desempenho. O desempenho que nos interessa nos sistemas de mensagens é o número de mensagens que podemos transferir entre dois pontos de extremidade durante um determinado período de tempo. Alternativamente, podemos estar interessados ​​em quanto tempo leva para uma mensagem chegar de um ponto final para outro. No entanto, dado que OslashMQ é projetado para cenários com conexões de longa duração, o tempo que leva para estabelecer uma conexão ou o tempo necessário para lidar com um erro de conexão é basicamente irrelevante. Esses eventos acontecem muito raramente e, portanto, seu impacto no desempenho geral é insignificante. A parte de uma base de código que é usada com muita freqüência, repetidamente, é chamada de otimização de caminho crítico deve se concentrar no caminho crítico. Vamos dar uma olhada em um exemplo: OslashMQ não é extremamente otimizado com relação às alocações de memória. Por exemplo, ao manipular seqüências de caracteres, muitas vezes aloca uma nova seqüência de caracteres para cada fase intermediária da transformação. No entanto, se olharmos rigorosamente para o pathmdash crítico, a mensagem atual passingmdashwell descobrir que ele usa quase nenhuma alocação de memória. Se as mensagens são pequenas, é apenas uma alocação de memória por 256 mensagens (essas mensagens são mantidas em um único bloco de memória alocada grande). Se, além disso, o fluxo de mensagens é estável, sem picos de tráfego enormes, o número de alocações de memória no caminho crítico cai para zero (os pedaços de memória alocados não são retornados ao sistema, mas reutilizados uma e outra vez) . Lição aprendida: otimizar onde faz diferença. A otimização de pedaços de código que não estão no caminho crítico é um esforço desperdiçado. 24,5. Alocando a memória Assumindo que toda a infra-estrutura foi inicializada e uma conexão entre dois pontos de extremidade foi estabelecida, há apenas uma coisa a alocar ao enviar uma mensagem: a própria mensagem. Assim, para otimizar o caminho crítico, tivemos que analisar como as mensagens são alocadas e passadas para cima e para baixo na pilha. Seu conhecimento comum no campo de rede de alto desempenho que o melhor desempenho é alcançado equilibrando cuidadosamente o custo de alocação de mensagem eo custo de cópia de mensagem (por exemplo, hal. inria. frdocs00292831PDFOpen-MX-IOAT. pdf. Mensagens pequenas, médias e grandes). Para mensagens pequenas, copiar é muito mais barato do que alocar memória. Faz sentido alocar nenhum novo pedaço de memória em todos e em vez de copiar a mensagem para a memória preallocated sempre que necessário. Para mensagens grandes, por outro lado, a cópia é muito mais cara do que a alocação de memória. Faz sentido alocar a mensagem uma vez e passar um ponteiro para o bloco alocado, em vez de copiar os dados. Essa abordagem é chamada de zero-cópia. OslashMQ trata ambos os casos de forma transparente. Uma mensagem OslashMQ é representada por um identificador opaco. O conteúdo de mensagens muito pequenas é codificado diretamente no identificador. Então, fazer uma cópia do identificador realmente copia os dados da mensagem. Quando a mensagem é maior, ela é alocada em um buffer separado eo identificador contém apenas um ponteiro para o buffer. Fazer uma cópia do identificador não resulta em copiar os dados da mensagem, o que faz sentido quando a mensagem é megabytes longa (Figura 24.3). Deve notar-se que, neste último caso, o buffer é contado de referência para que possa ser referenciado por várias alças sem a necessidade de copiar os dados. Lição aprendida: Ao pensar sobre desempenho, não supor theres uma única melhor solução. Pode acontecer que existam várias subclasses do problema (por exemplo, mensagens pequenas versus mensagens grandes), cada uma com seu próprio algoritmo ótimo. 24,6. Loteamento Já foi mencionado que o número total de chamadas de sistema em um sistema de mensagens pode resultar em um gargalo de desempenho. Na verdade, o problema é muito mais genérico do que isso. Há uma penalidade de desempenho não trivial associada ao percorrendo a pilha de chamadas e, portanto, ao criar aplicativos de alto desempenho, é aconselhável evitar o máximo possível de percorrer a pilha. Considere a Figura 24.4. Para enviar quatro mensagens, você tem que percorrer toda a pilha de rede quatro vezes (ou seja, OslashMQ, glibc, limite de espaço do usuário, implementação TCP, implementação de IP, camada Ethernet, a própria NIC e fazer backup da pilha novamente). No entanto, se você decidir juntar essas mensagens em um único lote, haveria apenas uma passagem da pilha (Figura 24.5). O impacto na taxa de transferência de mensagens pode ser esmagadora: até duas ordens de grandeza, especialmente se as mensagens são pequenas e centenas delas podem ser compactadas em um único lote. Por outro lado, o processamento em lotes pode ter impacto negativo na latência. Vamos tomar, por exemplo, o conhecido algoritmo Nagles, como implementado no TCP. Ele atrasa as mensagens de saída por um determinado período de tempo e mescla todos os dados acumulados em um único pacote. Obviamente, a latência end-to-end da primeira mensagem no pacote é muito pior do que a latência do último. Assim, é comum para as aplicações que necessitam de latência consistentemente baixa para desligar o algoritmo Nagles. É mesmo comum para desligar batching em todos os níveis da pilha (por exemplo, NICs interrupção coalescing recurso). Mas, novamente, nenhum batch significa extensa passagem da pilha e resulta em baixa taxa de transferência de mensagens. Parece que estamos presos a um dilema de throughput versus latency. O OslashMQ tenta fornecer consistentemente baixas latências combinadas com alto rendimento usando a seguinte estratégia: quando o fluxo de mensagens é esparso e não excede a largura de banda das pilhas de rede, o OslashMQ desliga todo o lote para melhorar a latência. O trade-off aqui é usagemdashwe CPU um pouco maior ainda tem que atravessar a pilha com freqüência. No entanto, isso não é considerado um problema na maioria dos casos. Quando a taxa de mensagem excede a largura de banda da pilha de rede, as mensagens têm de ser enfileiradas na memória até que a pilha esteja pronta para aceitá-las. Enfileirar significa que a latência vai crescer. Se a mensagem passar um segundo na fila, latência end-to-end será pelo menos um segundo. O que é ainda pior, à medida que o tamanho da fila cresce, latências irão aumentar gradualmente. Se o tamanho da fila não estiver vinculado, a latência pode exceder qualquer limite. Observou-se que mesmo que a pilha de rede esteja ajustada para a latência mais baixa possível (o algoritmo de Nagles desligado, as interrupções de NIC desligadas, etc.) as latências podem ainda ser sombrias devido ao efeito de enfileiramento, como descrito acima. Em tais situações, faz sentido começar batching agressivamente. Não há nada a perder, já que as latências já estão altas. Por outro lado, o processamento agressivo melhora a taxa de transferência e pode esvaziar a fila de mensagens pendentes, o que por sua vez significa que a latência irá diminuir gradualmente à medida que o atraso na fila diminui. Uma vez que não há mensagens pendentes na fila, o lote pode ser desativado para melhorar a latência ainda mais. Uma observação adicional é que o lote só deve ser feito no nível mais alto. Se as mensagens são loteadas lá, as camadas inferiores não têm nada para lote de qualquer maneira, e assim todos os algoritmos de lote abaixo não fazem nada, exceto introduzir latência adicional. Lição aprendida: Para obter um débito ótimo combinado com o tempo de resposta ótimo em um sistema assíncrono, desative todos os algoritmos de lote nas camadas baixas da pilha e lote no nível mais alto. Lote somente quando novos dados estão chegando mais rápido do que eles podem ser processados. 24,7. Visão Geral da Arquitetura Até este ponto, nós nos concentramos em princípios genéricos que tornam OslashMQ rápido. A partir de agora, vamos dar uma olhada na arquitetura real do sistema (Figura 24.6). O usuário interage com o OslashMQ usando soquetes chamados. Eles são bastante semelhantes aos soquetes TCP, a principal diferença é que cada soquete pode lidar com a comunicação com vários pares, um pouco como unbound UDP soquetes fazer. O objeto de soquete vive no segmento de usuários (consulte a discussão de modelos de threading na próxima seção). Além disso, o OslashMQ está executando vários threads de trabalho que lidam com a parte assíncrona da comunicação: leitura de dados da rede, envio de mensagens, aceitação de conexões de entrada, etc. Há vários objetos que vivem nos threads de trabalho. Cada um desses objetos pertence exatamente a um objeto pai (a propriedade é indicada por uma linha simples no diagrama). O pai pode viver em um segmento diferente do que a criança. A maioria dos objetos são propriedade diretamente por sockets no entanto, existem alguns casos em que um objeto é propriedade de um objeto que é propriedade do soquete. O que temos é uma árvore de objetos, com uma tal árvore por soquete. A árvore é usada durante o encerramento nenhum objeto pode fechar-se até que ele fecha todos os seus filhos. Desta forma, podemos garantir que o processo de encerramento funciona conforme esperado, por exemplo, que as mensagens de saída pendentes são enviadas para a rede antes de encerrar o processo de envio. Grosso modo, há dois tipos de objetos assíncronos existem objetos que não estão envolvidos na passagem de mensagens e existem objetos que são. O primeiro tem que fazer principalmente com gerenciamento de conexão. Por exemplo, um objeto ouvinte TCP ouve as conexões TCP recebidas e cria um objeto de sessão de mecanismo para cada nova conexão. Da mesma forma, um objeto de conector TCP tenta se conectar ao peer TCP e quando ele é bem-sucedido, cria um objeto de sessão de mecanismo para gerenciar a conexão. Quando tal conexão falhar, o objeto conector tenta reseta-lo. Estes últimos são objetos que estão manipulando a transferência de dados propriamente dita. Esses objetos são compostos de duas partes: o objeto de sessão é responsável pela interação com o soquete OslashMQ eo objeto do mecanismo é responsável pela comunicação com a rede. Há apenas um tipo de objeto de sessão, mas há um tipo de mecanismo diferente para cada protocolo subjacente suportado pela OslashMQ. Thus, we have TCP engines, IPC (inter-process communication) engines, PGM engines (a reliable multicast protocol, see RFC 3208), etc. The set of engines is extensiblemdashin the future we may choose to implement, say, a WebSocket engine or an SCTP engine. The sessions are exchanging messages with the sockets. There are two directions to pass messages in and each direction is handled by a pipe object. Each pipe is basically a lock-free queue optimized for fast passing of messages between threads. Finally, theres a context object (discussed in the previous sections but not shown on the diagram) that holds the global state and is accessible by all the sockets and all the asynchronous objects. 24.8. Concurrency Model One of the requirements for OslashMQ was to take advantage of multi-core boxes in other words, to scale the throughput linearly with the number of available CPU cores. Our previous experience with messaging systems showed that using multiple threads in a classic way (critical sections, semaphores, etc.) doesnt yield much performance improvement. In fact, a multi-threaded version of a messaging system can be slower than a single-threaded one, even if measured on a multi-core box. Individual threads are simply spending too much time waiting for each other while, at the same time, eliciting a lot of context switching that slows the system down. Given these problems, weve decided to go for a different model. The goal was to avoid locking entirely and let each thread run at full speed. The communication between threads was to be provided via asynchronous messages (events) passed between the threads. This, as insiders know, is the classic actor model . The idea was to launch one worker thread per CPU coremdashhaving two threads sharing the same core would only mean a lot of context switching for no particular advantage. Each internal OslashMQ object, such as say, a TCP engine, would be tightly bound to a particular worker thread. That, in turn, means that theres no need for critical sections, mutexes, semaphores and the like. Additionally, these OslashMQ objects wont be migrated between CPU cores so would thus avoid the negative performance impact of cache pollution (Figure 24.7 ). This design makes a lot of traditional multi-threading problems disappear. Nevertheless, theres a need to share the worker thread among many objects, which in turn means there has to be some kind of cooperative multitasking. This means we need a scheduler objects need to be event-driven rather than being in control of the entire event loop we have to take care of arbitrary sequences of events, even very rare ones we have to make sure that no object holds the CPU for too long etc. In short, the whole system has to become fully asynchronous. No object can afford to do a blocking operation, because it would not only block itself but also all the other objects sharing the same worker thread. All objects have to become, whether explicitly or implicitly, state machines. With hundreds or thousands of state machines running in parallel you have to take care of all the possible interactions between them andmdashmost importantlymdashof the shutdown process. It turns out that shutting down a fully asynchronous system in a clean way is a dauntingly complex task. Trying to shut down a thousand moving parts, some of them working, some idle, some in the process of being initiated, some of them already shutting down by themselves, is prone to all kinds of race conditions, resource leaks and similar. The shutdown subsystem is definitely the most complex part of OslashMQ. A quick check of the bug tracker indicates that some 30--50 of reported bugs are related to shutdown in one way or another. Lesson learned: When striving for extreme performance and scalability, consider the actor model its almost the only game in town in such cases. However, if you are not using a specialised system like Erlang or OslashMQ itself, youll have to write and debug a lot of infrastructure by hand. Additionally, think, from the very beginning, about the procedure to shut down the system. Its going to be the most complex part of the codebase and if you have no clear idea how to implement it, you should probably reconsider using the actor model in the first place. 24.9. Lock-Free Algorithms Lock-free algorithms have been in vogue lately. They are simple mechanisms for inter-thread communication that dont rely on the kernel-provided synchronisation primitives, such as mutexes or semaphores rather, they do the synchronisation using atomic CPU operations, such as atomic compare-and-swap (CAS). It should be understood that they are not literally lock-freemdashinstead, locking is done behind the scenes on the hardware level. OslashMQ uses a lock-free queue in pipe objects to pass messages between the users threads and OslashMQs worker threads. There are two interesting aspects to how OslashMQ uses the lock-free queue. First, each queue has exactly one writer thread and exactly one reader thread. If theres a need for 1-to - N communication, multiple queues are created (Figure 24.8 ). Given that this way the queue doesnt have to take care of synchronising the writers (theres only one writer) or readers (theres only one reader) it can be implemented in an extra-efficient way. Second, we realised that while lock-free algorithms were more efficient than classic mutex-based algorithms, atomic CPU operations are still rather expensive (especially when theres contention between CPU cores) and doing an atomic operation for each message written andor each message read was slower than we were willing to accept. The way to speed it upmdashonce againmdashwas batching. Imagine you had 10 messages to be written to the queue. It can happen, for example, when you received a network packet containing 10 small messages. Receiving a packet is an atomic event you cannot get half of it. This atomic event results in the need to write 10 messages to the lock-free queue. Theres not much point in doing an atomic operation for each message. Instead, you can accumulate the messages in a pre-write portion of the queue thats accessed solely by the writer thread, and then flush it using a single atomic operation. The same applies to reading from the queue. Imagine the 10 messages above were already flushed to the queue. The reader thread can extract each message from the queue using an atomic operation. However, its overkill instead, it can move all the pending messages to a pre-read portion of the queue using a single atomic operation. Afterwards, it can retrieve the messages from the pre-read buffer one by one. Pre-read is owned and accessed solely by the reader thread and thus no synchronisation whatsoever is needed in that phase. The arrow on the left of Figure 24.9 shows how the pre-write buffer can be flushed to the queue simply by modifying a single pointer. The arrow on the right shows how the whole content of the queue can be shifted to the pre-read by doing nothing but modifying another pointer. Lesson learned: Lock-free algorithms are hard to invent, troublesome to implement and almost impossible to debug. If at all possible, use an existing proven algorithm rather than inventing your own. When extreme performance is required, dont rely solely on lock-free algorithms. While they are fast, the performance can be significantly improved by doing smart batching on top of them. 24.10. API The user interface is the most important part of any product. Its the only part of your program visible to the outside world and if you get it wrong the world will hate you. In end-user products its either the GUI or the command line interface. In libraries its the API. In early versions of OslashMQ the API was based on AMQPs model of exchanges and queues. (See the AMQP specification .) From a historical perspective its interesting to have a look at the white paper from 2007 that tries to reconcile AMQP with a brokerless model of messaging. I spent the end of 2009 rewriting it almost from scratch to use the BSD Socket API instead. That was the turning point OslashMQ adoption soared from that point on. While before it was a niche product used by a bunch of messaging experts, afterwards it became a handy commonplace tool for anybody. In a year or so the size of the community increased tenfold, some 20 bindings to different languages were implemented, etc. The user interface defines the perception of a product. With basically no change to the functionalitymdashjust by changing the APImdashOslashMQ changed from an enterprise messaging product to a networking product. In other words, the perception changed from a complex piece of infrastructure for big banks to hey, this helps me to send my 10-byte-long message from application A to application B. Lesson learned: Understand what you want your project to be and design the user interface accordingly. Having a user interface that doesnt align with the vision of the project is a 100 guaranteed way to fail. One of the important aspects of the move to the BSD Sockets API was that it wasnt a revolutionary freshly invented API, but an existing and well-known one. Actually, the BSD Sockets API is one of the oldest APIs still in active use today it dates back to 1983 and 4.2BSD Unix. Its been widely used and stable for literally decades. The above fact brings a lot of advantages. Firstly, its an API that everybody knows, so the learning curve is ludicrously flat. Even if youve never heard of OslashMQ, you can build your first application in couple of minutes thanks to the fact that you are able to reuse your BSD Sockets knowledge. Secondly, using a widely implemented API enables integration of OslashMQ with existing technologies. For example, exposing OslashMQ objects as sockets or file descriptors allows for processing TCP, UDP, pipe, file and OslashMQ events in the same event loop. Another example: the experimental project to bring OslashMQ-like functionality to the Linux kernel turned out to be pretty simple to implement. By sharing the same conceptual framework it can re-use a lot of infrastructure already in place. Thirdly and probably most importantly, the fact that the BSD Sockets API survived almost three decades despite numerous attempts to replace it means that there is something inherently right in the design. BSD Sockets API designers havemdashwhether deliberately or by chancemdashmade the right design decisions. By adopting the API we can automatically share those design decisions without even knowing what they were and what problem they were solving. Lesson learned: While code reuse has been promoted from time immemorial and pattern reuse joined in later on, its important to think of reuse in an even more generic way. When designing a product, have a look at similar products. Check which have failed and which have succeeded learn from the successful projects. Dont succumb to Not Invented Here syndrome. Reuse the ideas, the APIs, the conceptual frameworks, whatever you find appropriate. By doing so you are allowing users to reuse their existing knowledge. At the same time you may be avoiding technical pitfalls you are not even aware of at the moment. 24.11. Messaging Patterns In any messaging system, the most important design problem is that of how to provide a way for the user to specify which messages are routed to which destinations. There are two main approaches, and I believe this dichotomy is quite generic and applicable to basically any problem encountered in the domain of software. One approach is to adopt the Unix philosophy of do one thing and do it well. What this means is that the problem domain should be artificially restricted to a small and well-understood area. The program should then solve this restricted problem in a correct and exhaustive way. An example of such approach in the messaging area is MQTT. Its a protocol for distributing messages to a set of consumers. It cant be used for anything else (say for RPC) but it is easy to use and does message distribution well. The other approach is to focus on generality and provide a powerful and highly configurable system. AMQP is an example of such a system. Its model of queues and exchanges provides the user with the means to programmatically define almost any routing algorithm they can think of. The trade-off, of course, is a lot of options to take care of. OslashMQ opts for the former model because it allows the resulting product to be used by basically anyone, while the generic model requires messaging experts to use it. To demonstrate the point, lets have a look how the model affects the complexity of the API. What follows is implementation of RPC client on top of a generic system (AMQP): On the other hand, OslashMQ splits the messaging landscape into so-called messaging patterns. Examples of the patterns are publishsubscribe, requestreply or parallelised pipeline. Each messaging pattern is completely orthogonal to other patterns and can be thought of as a separate tool. What follows is the re-implementation of the above application using OslashMQs requestreply pattern. Note how all the option tweaking is reduced to the single step of choosing the right messaging pattern ( REQ ): Up to this point weve argued that specific solutions are better than generic solutions. We want our solution to be as specific as possible. However, at the same time we want to provide our customers with as wide a range of functionality as possible. How can we solve this apparent contradiction The answer consists of two steps: Define a layer of the stack to deal with a particular problem area (e. g. transport, routing, presentation, etc.). Provide multiple implementations of the layer. There should be a separate non-intersecting implementation for each use case. Lets have a look at the example of the transport layer in the Internet stack. Its meant to provide services such as transferring data streams, applying flow control, providing reliability, etc. on the top of the network layer (IP). It does so by defining multiple non-intersecting solutions: TCP for connection-oriented reliable stream transfer, UDP for connectionless unreliable packet transfer, SCTP for transfer of multiple streams, DCCP for unreliable connections and so on. Note that each implementation is completely orthogonal: a UDP endpoint cannot speak to a TCP endpoint. Neither can a SCTP endpoint speak to a DCCP endpoint. It means that new implementations can be added to the stack at any moment without affecting the existing portions of the stack. Conversely, failed implementations can be forgotten and discarded without compromising the viability of the transport layer as a whole. The same principle applies to messaging patterns as defined by OslashMQ. Messaging patterns form a layer (the so-called scalability layer) on top of the transport layer (TCP and friends). Individual messaging patterns are implementations of this layer. They are strictly orthogonalmdashthe publishsubscribe endpoint cant speak to the requestreply endpoint, etc. Strict separation between the patterns in turn means that new patterns can be added as needed and that failed experiments with new patterns wont hurt the existing patterns. Lesson learned: When solving a complex and multi-faceted problem it may turn out that a monolithic general-purpose solution may not be the best way to go. Instead, we can think of the problem area as an abstract layer and provide multiple implementations of this layer, each focused on a specific well-defined use case. When doing so, delineate the use case carefully. Be sure about what is in the scope and what is not. By restricting the use case too aggressively the application of your software may be limited. If you define the problem too broadly, however, the product may become too complex, blurry and confusing for the users. 24.12. Conclusion As our world becomes populated with lots of small computers connected via the Internetmdashmobile phones, RFID readers, tablets and laptops, GPS devices, etc. mdashthe problem of distributed computing ceases to be the domain of academic science and becomes a common everyday problem for every developer to tackle. The solutions, unfortunately, are mostly domain-specific hacks. This article summarises our experience with building a large-scale distributed system in a systematic manner. It focuses on problems that are interesting from a software architecture point of view, and we hope that designers and programmers in the open source community will find it useful. Back to top Back to The Architecture of Open Source Applications .

No comments:

Post a Comment