среда, 27 июня 2018 г.

Sistema de negociação akka


Sistema de negociação Akka
Vamos imaginar um sistema hipotético de HFT em Java, exigindo (muito) baixa latência, com muitos objetos pequenos de vida curta, um pouco devido à imutabilidade (Scala?), Milhares de conexões por segundo e um número obsceno de mensagens passando em um arquitetura orientada a eventos (akka e amqp?).
Para os especialistas lá fora, qual seria (hipoteticamente) o melhor ajuste para o JVM 7? Que tipo de código ficaria feliz? Scala e Akka estariam prontas para esse tipo de sistema?
Nota: Tem havido algumas questões semelhantes, como esta, mas eu ainda não encontrei uma abrangendo Scala (que tem sua própria pegada idiossincrática na JVM).
No meu laptop a latência média de mensagens de ping entre atores Akka 2.3.7 é.
300ns e é muito menos do que a latência esperada devido ao GC ser pausado nas JVMs.
Código (incl. Opções da JVM) & amp; resultados de testes para Akka e outros atores no Intel Core i7-2640M aqui.
P. S. Você pode encontrar muitos princípios e dicas para computação de baixa latência no site de Dmitry Vyukov e no blog de Martin Thompson.
É possível obter um desempenho muito bom em Java. No entanto, a questão precisa ser mais específica para fornecer uma resposta confiável. Suas principais fontes de latência virão da seguinte lista não exaustiva:
Quanto lixo você cria e o trabalho do GC para coletar e promover. Designs imutáveis ​​em minha experiência não se encaixam bem com baixa latência. O ajuste de GC precisa ser um grande foco.
Aqueça a JVM para que as classes sejam carregadas e o JIT tenha tempo para fazer seu trabalho.
Projete seus algoritmos para serem O (1) ou pelo menos O (log2 n) e ter testes de desempenho que confirmem isso.
Seu design precisa ser livre de trava e seguir o "Princípio do Escritor Único".
Um esforço significativo precisa ser feito para entender toda a pilha e mostrar simpatia mecânica em seu uso.
Projete seus algoritmos e estruturas de dados para serem amigáveis ​​ao cache. As falhas de cache nos dias de hoje são o maior custo. Isso está intimamente relacionado à afinidade do processo, que, se não for configurada corretamente, pode resultar em poluição significativa do cache. Isso envolverá simpatia pelo sistema operacional e até mesmo alguns códigos JNI em alguns casos.
Certifique-se de ter núcleos suficientes para que qualquer encadeamento que precise ser executado tenha um núcleo disponível sem precisar esperar.
Eu recentemente bloguei sobre um estudo de caso de tal exercício.

Simplificando o sistema de negociação com a Akka.
Junte-se à comunidade DZone e obtenha a experiência completa dos membros.
Meus colegas estão desenvolvendo um sistema de negociação que processa fluxos bastante pesados ​​de transações de entrada. Cada transação cobre um Instrumento (pense em bond ou estoque) e tem algumas (agora) propriedades sem importância. Eles estão presos ao Java (& lt; 8), então vamos nos ater a ele:
O instrumento será usado mais tarde como uma chave no HashMap, portanto, para o futuro, implementaremos de forma proativa o Comparable & lt; Instrument & gt; . Este é o nosso domínio, agora os requisitos:
As transações entram no sistema e precisam ser processadas (o que quer que isso signifique), o mais rápido possível. Estamos livres para processá-las em qualquer ordem. no entanto, as transações para o mesmo instrumento precisam ser processadas sequencialmente na mesma ordem em que entraram.
A implementação inicial foi direta: coloque todas as transações de entrada em uma fila (por exemplo, ArrayBlockingQueue) com um único consumidor. Isso satisfaz o último requisito, uma vez que a fila preserva o ordenamento FIFO estrito em todas as transações. Mas tal arquitetura impede o processamento simultâneo de transações não relacionadas para diferentes instrumentos, desperdiçando, assim, uma melhoria atrativa na taxa de transferência. Não é de surpreender que essa implementação, embora indubitavelmente simples, tenha se tornado um gargalo.
Que nojo! Mas o pior ainda está por vir. Como você se certifica de que no máximo um thread processa cada fila por vez? Afinal, caso contrário, duas threads poderiam pegar itens de uma fila (um instrumento) e processá-los em ordem inversa, o que não é permitido. O caso mais simples é ter um Thread por fila - isso não será escalável, pois esperamos dezenas de milhares de instrumentos diferentes. Então podemos dizer N threads e deixar que cada um deles manipule um subconjunto de filas, por exemplo. instrument. hashCode ()% N nos informa qual encadeamento cuida da determinada fila. Mas ainda não é perfeito por três razões:
Um thread deve & quot; observar & quot; muitas filas, mais provavelmente ocupado-esperando, iterando sobre eles o tempo todo. Como alternativa, a fila pode ativar seu thread pai de alguma forma No pior cenário, todos os instrumentos terão códigos hash conflitantes, tendo como alvo apenas um thread - que é efetivamente o mesmo que a nossa solução inicial. Código bonito não é complexo!
A implementação dessa monstruosidade é possível, mas difícil e propensa a erros. Além disso, há outro requisito não-funcional: instrumentos vêm e vão e há centenas de milhares deles ao longo do tempo. Depois de um tempo, devemos remover as entradas em nosso mapa, representando instrumentos que não foram vistos ultimamente. Caso contrário, teremos um vazamento de memória.
Isso é muito simples. Como o nosso agente Dispatcher é efetivamente de thread único, nenhuma sincronização é necessária. Nós mal recebemos Transações, procuramos ou criamos Processadores e passamos adiante Transações. É assim que a implementação do processador pode parecer:
É isso aí! Curiosamente, nossa implementação do Akka é quase idêntica à nossa primeira ideia com o mapa de filas. Afinal, um ator é apenas uma fila e um segmento (lógico) processando itens nessa fila. A diferença é: Akka gerencia um pool de threads limitado e compartilha entre talvez centenas de milhares de atores. E como cada instrumento tem seu próprio ator dedicado (e "single-threaded"), o processamento sequencial de transações por instrumento é garantido.
Claramente, quando o Processador não recebeu nenhuma mensagem por um período de uma hora, ele gentilmente sinaliza isso para seu pai (Despachante). Mas o ator ainda está vivo e pode lidar com transações se elas acontecerem exatamente depois de uma hora. O que o Dispatcher faz é matar o processador e removê-lo de um mapa:
Houve um pequeno inconveniente. instrumentProcessors costumava ser um Map & lt; Instrument, ActorRef & gt; . Isso provou ser insuficiente, pois de repente temos que remover uma entrada neste mapa por valor. Em outras palavras, precisamos encontrar uma chave (Instrument) que mapeie para um determinado ActorRef (Processor). Existem diferentes maneiras de lidar com isso (por exemplo, o Processador ocioso pode enviar um Instrumnt para ele), mas eu usei o BiMap & lt; K, V & gt; de goiaba. Ele funciona porque os dois instrumentos e o AtorRef apontados são únicos (ator por instrumento). Tendo BiMap eu poderia simplesmente inverter () o mapa (de BiMap & lt; Instrument, ActorRef & gt; para BiMap & lt; ActorRef, Instrument & gt; e tratar ActorRef como chave.
Construir vs comprar uma solução de qualidade de dados: o que é melhor para você? A manutenção de dados de alta qualidade é essencial para a eficiência operacional, análises significativas e bons relacionamentos de longo prazo com os clientes. Mas, ao lidar com várias fontes de dados, a qualidade dos dados torna-se complexa, por isso você precisa saber quando deve criar um esforço de ferramentas personalizadas de qualidade de dados sobre soluções em lata. Faça o download do nosso whitepaper para obter mais informações sobre uma abordagem híbrida.

Ainda outro benchmark Akka.
Eu posso dizer que o desempenho de Akka Actors é excelente em comparação com Scala Actors.
Antes de olhar para o benchmark, descreverei brevemente o aplicativo de amostra.
Um sistema de negociação é essencialmente sobre a correspondência de ordens de compra e venda. Uma ordem limitada é uma ordem para comprar uma garantia de não mais, ou vender a não menos que um preço específico. Por exemplo, se um investidor quiser comprar uma ação, mas não quiser pagar mais de US $ 20 por ela, o investidor pode fazer um pedido de limite para comprar a ação em US $ 20 ou melhor. Existem muitos outros tipos de pedidos e restrições especiais. A amostra está lidando apenas com ordens de limite simples.
As encomendas que estão longe do melhor preço atual no mercado são coletadas em um livro de pedidos para a segurança, para posterior execução.
Um mecanismo de correspondência gerencia um ou mais livros de pedidos, ou seja, o mercado é fragmentado por carteira de pedidos. Os mecanismos de correspondência mantêm o estado atual dos livros de pedidos. Os clientes se conectam a um serviço receptor de pedidos, que é responsável por rotear o pedido para o mecanismo de correspondência correto. O receptor do pedido é sem estado e os clientes podem usar qualquer receptor de pedidos independentemente do livro de pedidos.
Para redundância, os mecanismos de correspondência funcionam em pares. Cada pedido é processado por ambos os mecanismos de correspondência. O pedido também é armazenado em um log de transação persistente, pelos dois mecanismos de correspondência. Em uma configuração real, os mecanismos de correspondência principal e de espera geralmente são implantados em datacenters separados.
Agora, para o benchmark. O cenário de teste colocou ordens de compra e venda em 15 livros de pedidos, divididos em três mecanismos de comparação. As ordens estão em diferentes níveis de preço, portanto, é criada uma profundidade de carteira de pedidos, mas no final todas as ordens são negociadas e isso é verificado pelo teste JUnit executando o benchmark.
O cenário foi executado com carga diferente, variando o número de clientes simulados de 1 a 40.
Os resultados de benchmark ilustrados aqui foram realizados em uma caixa real de 8 núcleos (CPU dual Xeon 5500, 2,26 Ghz por núcleo).
Aqui está o resultado do processamento de 750000 pedidos em cada nível de carga.
A solução Basic usa invocações de métodos síncronos comuns. É extremamente rápido, mas não é uma opção para uma solução verdadeiramente escalável. A passagem assíncrona de mensagens é uma alternativa melhor para escalar em nós múltiplos ou múltiplos núcleos.
Nas soluções Scala e Akka Actors, os clientes enviam cada mensagem de pedido para um receptor de pedido e aguardam a resposta Futuro (!? Operador em Scala e !! em Akka). O receptor do pedido encaminha a solicitação para o mecanismo de correspondência responsável pelo livro de pedidos, isto é, o segmento / despachante do recebedor da ordem pode ser imediatamente usado para a próxima solicitação. O mecanismo de correspondência envia a mensagem de pedido para o modo de espera e os dois mecanismos de correspondência processam a lógica correspondente e o log de transação em paralelo. O reconhecimento é respondido ao cliente quando ambos estão prontos.
Os resultados do benchmark mostram que os Akka Actors são capazes de processar três vezes mais pedidos do que os Scala Actors na mesma carga. Resultado semelhante com latência. A latência de Akka Actors é um terço de Scala Actors. Isso vale para carga baixa também. A latência média nem sempre é a melhor medida, portanto, vamos ver alguns percentis.
As operações que estão aguardando a conclusão de um Futuro foram usadas ao enviar mensagens. Isso tem uma tag de preço de escalabilidade, já que o encadeamento é bloqueado enquanto aguarda a conclusão do Futuro. Melhor escalabilidade pode ser obtida com a passagem de mensagem unidirecional, ilustrada pelas soluções unidirecionais Scala / Akka Actor. Ele usa a operação de estrondo (!) Para o envio de todas as mensagens.
Os mecanismos de correspondência gravam cada pedido em um arquivo de log de transação. Esse é um gargalo de E / S de bloqueio. Para empurrar o teste da mensagem passando um passo adiante, o benchmark também foi executado sem o log de transação. A solução Akka brilha ainda mais. Taxa de transação mais de três vezes maior em comparação com os Atores Scala na mesma carga, ao usar a solução com base no envio de mensagens e na espera de resposta. Para as soluções de passagem de uma só via, os Akka Actors são duas vezes mais rápidos que os Scala Actors.
A Akka tem grande flexibilidade quando se trata de especificação de diferentes mecanismos de despacho. O hawt Akka Actor está incluído no benchmark como uma comparação com a solução unidirecional Akka Actor. Ele usa a biblioteca de threads do HawtDispatch, que é um clone de Java do libdispatch. O último teste sem log de transação mostra que o HawtDispatcher tem desempenho um pouco melhor do que o dispatcher baseado em evento que foi usado para o One-way da Akka Actor.
O código-fonte completo para o aplicativo de amostra está localizado em: github / patriknw / akka-sample-trading.
Para executar o benchmark você mesmo pode baixar e descompactar a distribuição, contendo todos os arquivos jar necessários. O README incluso descreve como executar os testes.
Nota de atualização 15 de agosto: Adicionada solução unilateral do Scala Actor e uma nova descrição de como executar o benchmark.
Nota de atualização 22 de agosto: Novo benchmark executado na caixa real de 8 núcleos.

Atores e Mensagens: Os Blocos de Construção do Akka. NET.
Aprenda a entender os atores e as mensagens de maneira operacional no Akka. NET.
O Modelo de Ator é um paradigma de programação que é bem adequado para trabalhar no mundo de aplicativos distribuídos assíncronos. Como descrevi em um artigo anterior, o Modelo de Ator é uma arquitetura dirigida por mensagens, na qual cada entidade dentro da estrutura é um ator. O trabalho é feito por mensagem passando entre os atores. Um ator recebe uma mensagem e reage a ela fazendo algo ou passando outra mensagem para outro ator para fazer o trabalho que o primeiro ator não pode ou não fará.
O Akka. NET, uma tecnologia de código aberto criada pelo pessoal da Petabridge, fornece uma maneira para os programadores. NET codificarem usando o Modelo de Ator no nível corporativo. Akka. NET é a porta. NET da Akka. Akka é uma implementação Java / Scala do Modelo de Ator.
O Akka. NET é um framework muito grande e leva um pouco de tempo para dominar. No entanto, existem dois conceitos essenciais que você precisa entender para começar. Esses conceitos essenciais são atores e mensagens. Entender os atores e as mensagens de uma maneira operacional sob o Akka. NET é o objetivo deste artigo.
Neste artigo, vou mostrar como eu usei o Akka. NET e o Actor Model para criar um programa de demonstração que emula uma negociação de ações. O programa de demonstração ilustra os conceitos básicos da comunicação de mensagens entre os atores.
Para aproveitar ao máximo a leitura do artigo, você deve ter uma boa compreensão do objeto Task e da Task Parallel Library (TPL). Nós não vamos nos aprofundar no Task extensivamente. Mas, o Akka. NET é construído sobre o TPL e nos referiremos ao Task quando cobrimos a comunicação de mensagens via Actor. Ask ().
Atores e Mensagens.
As ações de negociação em uma bolsa de valores do mundo real envolvem vários atores que operam de maneira assíncrona. Os atores se comunicam entre si por meio de mensagens. Por exemplo, no mundo real, um corretor da bolsa envia uma mensagem para um trader de piso para comprar 100 ações da IBM. Esta mensagem pode ser enviada como uma chamada telefônica, um email ou dentro de um sistema comercial. Caso a mensagem venha como um telefonema para o Floor Trader, o Floor Trader aceita a ordem de negociação (a mensagem) e desliga. Então, o Floor Trader faz o trade e chama o corretor da bolsa de volta, informando que o Trade está pronto. A transação comercial é assíncrona e distribuída. Quem sabe onde está o Floor Trader? E, o Corretor da Bolsa não é mantido no telefone durante a realização do Negócio, portanto a assincronia (ver Figura 1).
Figura 1: Em um sistema acionado por mensagens, os Atores se comunicam de forma assíncrona entre si por meio de mensagens.
Entendendo o Cenário Comercial AkkaNetDemo.
O programa AkkaNetDemo, o programa de demonstração deste artigo, emula um cenário comercial da seguinte forma:
Um cliente (console) declara uma negociação que ele ou ela deseja fazer. O trade é definido como uma string delimitada por vírgula, com a estrutura:
100 é o número de ações.
B é um símbolo indicando Comprar. (S indica vender)
A declaração de negociação é submetida a um objeto, TradingSystem, via TradingSystem. Trade (), (1, na Figura 2). O TradingSystem converte a declaração de negociação em uma mensagem Trade (2). O TradingSystem passa a mensagem para o StockBroker como um "pedido". O StockBroker determina se é necessário criar um BuyFloorTrader, uma instância da classe FloorTrader ou um SellFloorTrader, dependendo se a mensagem Trade declara TradeType. Buy ou TradeType. Sell. Em seguida, o StockBroker encaminha a mensagem para o FloorTrader identificado (3). O FloorTrader determina se o número de ações no Trade está acima ou abaixo do limite de negociação. Se abaixo do limite de negociação, o FloorTrader faz a negociação e, em seguida, cria uma nova mensagem de negociação indicando sucesso. No entanto, se o número de ações no negócio exceder o limite de negociação, o FloorTrader cria uma nova mensagem de negociação, definindo Trade. TradeStatus como TradeStatus. Fail (4).
A nova mensagem de negociação é enviada de volta ao StockBroker. O TradingSystem, que está aguardando o StockBroker para retornar uma mensagem, passa a mensagem de resposta Trade para o console, concluindo a transação. (5)
Figura 2: O aplicativo AkkaNetDemo emula uma sessão de negociação entre um Broker de Ações e um Trader de Piso dentro de um Sistema de Negociação.
Entendendo os Atores no Programa de Demonstração.
Antes de entrarmos nos detalhes da interação de mensagens entre os atores no programa de demonstração, você precisa entender os atores que estão no programa. A Tabela 1 descreve esses atores. Por favor, revise a tabela. Ter uma consciência da TradingSystem, StockBroker e FloorTrader é importante à medida que entramos nos detalhes da função dos atores.
Tabela 1: Os Atores do programa AkkaNetDemo.
A Figura 3 mostra um diagrama de modelo de objeto formal dos três atores em execução no programa AkkaNetDemo.
Figura 3: Os atores no programa AkkaNetDemo.
Entendendo a hierarquia do sistema.
Akket. NET suporta um modelo de relacionamento hierárquico. No topo da relação está o objeto Akka. NET, ActorSystem. No AkkaNetDemo, o objeto TradingSystem é o local onde o ActorSystem raiz é criado.
Atores criados pela ActorSystem são atores de alto nível. Atores são objetos que herdam do UntypedActor da Akka. NET. Atores de nível superior criam atores para crianças. Em nosso pequeno exemplo de Stock Trading, o ator StockBroker é um ator de nível superior do TradingSystem. O FloorTrader é um filho do ator StockBroker. Atores supervisionam seus filhos. As estratégias de supervisão são uma parte importante do Modelo de Atores Akka. NET e serão discutidas em artigos futuros. Por enquanto, o importante é entender que, quando um ator cria outro ator, esse ator subsequente é um filho.
Comunicar-se com mensagens.
Agora que abordamos o ActorSystem e o Actors, vamos dar uma olhada no conceito de uma mensagem que se aplica ao Akka. NET. Em Akka. NET, uma mensagem é um Objeto CSharp Old Plain (POCO), uma classe. (A natureza de tipo forte de uma Mensagem é um conceito essencial do Akka. NET.) Por exemplo, o POCO mostrado na Listagem 1 pode ser uma mensagem:
Listagem 1: você define uma mensagem Akka. NET usando classes C #.
AlarmSet é uma mensagem trivial, mas pode-se imaginar o que fazer quando receber tal mensagem. "Ah, sim, eu preciso acertar meu relógio com o valor de RingTime." Ao contrário das classes C # complexas e completas que funcionam, uma classe que define uma mensagem não contém nada além de uma informação, semelhante a uma estrutura.
A importância da imutabilidade.
Por favor note que a mensagem AlarmSet é imutável. A única maneira pela qual a propriedade RingTime pode ser definida é quando a mensagem é criada. Ninguém pode vir mais tarde e mexer com as coisas. Ser imutável é uma prática recomendada muito importante do Akka. NET. Quando a mensagem é passada para quem sabe onde, ser imutável significa que ninguém pode alterar o conteúdo da mensagem quando a mensagem está em trânsito.
O aplicativo AkkaNetDemo possui uma mensagem, Trade, mostrada na Figura 4. Herança comercial da classe abstrata, AbstractTrade. Este modelo de herança é de minha autoria. Não tem nada a ver com o Akka. NET.
Figura 4: A mensagem Trade descreve um trade de ações e seu estado.
A mensagem, Trade, tem propriedades pelas quais um ator determinará o comportamento. Uma mensagem de negociação será enviada entre os atores para realizar o trabalho. Vamos dar uma olhada nos detalhes do envio de mensagens.
Enviando Mensagens.
Como mencionamos acima, um ator envia uma mensagem para outro ator para fazer alguma coisa. Uma das boas coisas sobre o Akka. NET é que o framework usa linguagem intuitiva da natureza para descrever o envio de uma mensagem. Você usa Actor. Tell (mensagem) para enviar uma mensagem a um ator em uma base de ignorar e esquecer. Você usa Actor. Ask (mensagem) quando deseja chegar ao objeto Task subjacente no qual a mensagem é enviada.
Sob o capô: Biblioteca paralela de tarefas.
O Akka. NET usa a biblioteca paralela. NET Task para fazer seu gerenciamento de threads. Como resultado, sempre que você Actor. Ask (mensagem) você receberá a tarefa subjacente pedindo o objeto de retorno. Além disso, você usa Actor. Ask & lt; MyReturnMessageType & gt; (mensagem) para recuperar a mensagem subjacente que o Ator recebe em resposta à pergunta. (Você verá um cenário Ask () mais tarde no código de exemplo.)
Além disso, você usa Ator. Forward (mensagem) quando você quer que um Ator envie uma mensagem recebida para outro Ator. Quando você envia uma mensagem (Forward), as informações sobre o remetente de origem também são transmitidas.
Então, vamos rever. Existem três maneiras de passar uma mensagem de um ator para outro:
Agora que sabemos como enviar uma mensagem, vamos falar sobre o processamento de mensagens em um Ator.
OnReceive (): A Câmara de Compensação de Mensagens de um Ator.
Atores herdam de uma classe abstrata, UntypedActor. UntypedActor define um método abstrato, OnReceive (mensagem. OnReceive (mensagem) precisa ser implementado pela classe que herda de UntypedActor. Os componentes internos do Akka. NET são inteligentes o suficiente para fazer toda a automagia necessária para um Ator receber uma mensagem e o passá-lo para OnReceive (). OnReceive () é o lugar onde o processamento de mensagens ocorre.
Quando um ator recebe uma mensagem em OnReceive (mensagem), o ator pode analisar a mensagem e reagir à análise. Essa análise pode ser para fazer algum processamento e, em seguida, responder ao remetente com outra mensagem. O Ator pode decompor a mensagem e criar uma ou mais novas mensagens para enviar a outros atores ou atores-filhos existentes que ela cria. Ou o ator pode simplesmente encaminhar a mensagem para outro ator.
O importante é lembrar que o OnReceive (mensagem) é o local onde o processamento acontecerá.
Entendendo o contexto.
O Akka. NET foi projetado para ser um sistema assíncrono distribuído, fracamente acoplado. Muita da magia do sistema está escondida no nível de programação. Quando você envia uma mensagem usando Tell (mensagem), por exemplo, pode parecer que você está fazendo nada mais do que enviar um simples POCO. Este não é o caso. Quando você chama Actor. Tell (mensagem), as informações sobre o remetente da mensagem são marcadas (sem trocadilhos). Além disso, dentro de um determinado ator, há uma propriedade implícita, Contexto. Você pode usar o contexto para determinar o pai do ator, usando Context. Parent. Você usa o Context. System para acessar o ActorSystem raiz, sob o qual o ator reside. Você pode obter uma referência ao próprio Ator usando o Context. Self. Quando você começa a programar com o Akka. NET, você se verá trabalhando muito com o Context.
Ok, até este ponto nós exploramos muitos conceitos. Cobrimos Atores, Mensagens, Tell (), Ask (), Forward () e OnReceive (), além de ter um vislumbre de Context. E nós analisamos o fluxo específico do programa e os atores que fazem parte do programa AkkaNetDemo. Agora, vamos além do conceito para realmente programar.
Codificando o Programa AkkaNetDemo.
Conforme mencionado no início deste artigo, quando introduzimos o fluxo do programa do AkkaNetDemo, o aplicativo de console AkkaNetDemo decompõe uma cadeia delimitada por vírgula que descreve uma transação. A cadeia delimitada por vírgula é enviada para o console pelo usuário. A string é dividida em valores ticker, share e B (ou S). Esses três valores são enviados para o TradingSystem por meio do método, TradingSystem. Trade (ticker, share, tradeType). Em seguida, o TradingSystem cria um objeto StockBroker que será Ask () para fazer o comércio. A Listagem 2 mostra o código:
Listagem 2: O código TradingSystem cria um StockBroker e envia ao intermediário uma mensagem Trade via Ask ()
A razão pela qual estamos solicitando que o TradingSystem peça ao corretor para enviar a mensagem de Trade é que queremos esperar até que a negociação seja concluída antes de passar para qualquer outra coisa. Lembre-se de que Actor. Ask & lt; MyResponseType & gt; (message) retorna o objeto Task subjacente. Vamos chamar Task. Wait () para bloquear a tarefa, esperando que ela seja concluída antes de permitir que o fluxo de controle do programa avance.
A maneira como uma tarefa é concluída é quando o ator que está fazendo o "pedido" recebe uma mensagem de volta. Essa mensagem de resposta é refletida no Task. Result.
O corretor recebe a mensagem Trade e cria um objeto Buy or Sell FloorTrader. Em seguida, o intermediário chama FloorTrader. Forward (message), transmitindo a mensagem fornecida pelo TradingSystem (consulte a Listagem 3).
Listagem 3: O StockBroker encaminha uma mensagem para um FloorTrader.
O FloorTrader inspeciona a mensagem. Se Trade. Shares for maior que o TradeLimit, o FloorTrader criará uma nova mensagem Trade, definindo Trade. TradeStatus = TradeStatus. Failure. Caso contrário, o FloorTrader cria a nova mensagem Trade, definindo Trade. TradeStatus = TradeStatus. Success.
O FloorTrader usa o objeto Sender, implícito no objeto Actor, para enviar a nova mensagem Trade para backup da hierarquia (consulte a Listagem 4).
Listagem 4: O objeto FloorTrader envia uma resposta de mensagem de volta ao remetente.
A nova mensagem Trade é enviada para o corretor fazendo o pedido. O TradingSystem extrai a mensagem Trade da Task subjacente ao método Ask (), através do resultado da tarefa.
O TradingSystem retorna a mensagem de troca de resposta para o chamador.
O console converte a mensagem Trade em JSON e copia a saída para a tela.
Listagem 5: O console do aplicativo AkkaNetDemo exibe a mensagem Trade retornada como JSON.
A saída do console é mostrada abaixo nas Figuras 5 e 6.
Figura 5: O programa AkkaNetDemo converte a mensagem de resposta Trade para JSON ao renderizar no console.

Sistema de negociação Akka
Vamos imaginar um sistema hipotético de HFT em Java, exigindo (muito) baixa latência, com muitos objetos pequenos de vida curta, um pouco devido à imutabilidade (Scala?), Milhares de conexões por segundo e um número obsceno de mensagens passando em um arquitetura orientada a eventos (akka e amqp?).
Para os especialistas lá fora, qual seria (hipoteticamente) o melhor ajuste para o JVM 7? Que tipo de código ficaria feliz? Scala e Akka estariam prontas para esse tipo de sistema?
Nota: Houve algumas perguntas semelhantes, como esta, mas ainda não encontrei uma que cubra o Scala (que tem sua própria pegada idiossincrática na JVM).
No meu laptop a latência média de mensagens de ping entre atores Akka 2.3.7 é.
300ns e é muito menos do que a latência esperada devido ao GC ser pausado nas JVMs.
Código (incl. Opções da JVM) & amp; resultados de testes para Akka e outros atores no Intel Core i7-2640M aqui.
P. S. Você pode encontrar muitos princípios e dicas para computação de baixa latência no site de Dmitry Vyukov e no blog de Martin Thompson.
É possível obter um desempenho muito bom em Java. No entanto, a questão precisa ser mais específica para fornecer uma resposta confiável. Suas principais fontes de latência virão da seguinte lista não exaustiva:
Quanto lixo você cria e o trabalho do GC para coletar e promover. Designs imutáveis ​​em minha experiência não se encaixam bem com baixa latência. O ajuste de GC precisa ser um grande foco.
Aqueça a JVM para que as classes sejam carregadas e o JIT tenha tempo para fazer seu trabalho.
Projete seus algoritmos para serem O (1) ou, pelo menos, O (log2 n), e ter testes de desempenho que confirmem isso.
Seu design precisa ser livre de trava e seguir o "Princípio do Escritor Único".
Um esforço significativo precisa ser feito para entender toda a pilha e mostrar simpatia mecânica em seu uso.
Projete seus algoritmos e estruturas de dados para serem amigáveis ​​ao cache. As falhas de cache nos dias de hoje são o maior custo. Isso está intimamente relacionado à afinidade do processo, que, se não for configurada corretamente, pode resultar em poluição significativa do cache. Isso envolverá simpatia pelo sistema operacional e até mesmo alguns códigos JNI em alguns casos.
Certifique-se de ter núcleos suficientes para que qualquer encadeamento que precise ser executado tenha um núcleo disponível sem precisar esperar.
Eu recentemente bloguei sobre um estudo de caso de tal exercício.

Комментариев нет:

Отправить комментарий