Alguém tem um bom recurso para implementar uma estratégia de pool de objetos compartilhados para um recurso limitado na veia do pool de conexões SQL? (ou seja, seria totalmente implementado que é seguro para threads).
Para acompanhar a solicitação de esclarecimento do @Aaronaught, o uso do pool seria para solicitações de balanceamento de carga para um serviço externo. Para colocá-lo em um cenário que provavelmente seria mais fácil de entender imediatamente, em oposição à minha situação direta. Eu tenho um objeto de sessão que funciona de maneira semelhante ao ISession
objeto do NHibernate. Que cada sessão exclusiva gerencia sua conexão com o banco de dados. Atualmente, tenho 1 objeto de sessão de execução longa e estou encontrando problemas em que meu provedor de serviços está limitando a taxa de uso dessa sessão individual.
Devido à falta de expectativa de que uma única sessão seja tratada como uma conta de serviço de longa duração, eles aparentemente a tratam como um cliente que está martelando seu serviço. O que me leva à minha pergunta aqui: em vez de ter uma sessão individual, eu criaria um pool de sessões diferentes e dividiria as solicitações até o serviço nessas várias sessões, em vez de criar um único ponto focal, como estava fazendo anteriormente.
Espero que esse plano de fundo ofereça algum valor, mas responda diretamente a algumas de suas perguntas:
P: Os objetos são caros para criar?
R: Nenhum objeto é um conjunto de recursos limitados
P: Eles serão adquiridos / liberados com muita frequência?
R: Sim, mais uma vez, eles podem ser pensados nas NHibernate ISessions, onde 1 é normalmente adquirido e liberado durante toda a solicitação de uma única página.
P: Um simples primeiro a chegar, primeiro a servir é suficiente ou você precisa de algo mais inteligente, ou seja, que evite a fome?
R: Uma distribuição simples do tipo round robin seria suficiente, por inanição, suponho que você queira dizer se não houver sessões disponíveis em que os chamadores sejam bloqueados aguardando liberações. Isso não é realmente aplicável, pois as sessões podem ser compartilhadas por diferentes chamadores. Meu objetivo é distribuir o uso por várias sessões, em vez de uma única sessão.
Acredito que isso seja provavelmente uma divergência em relação ao uso normal de um pool de objetos, motivo pelo qual deixei essa parte de fora e planejei apenas adaptar o padrão para permitir o compartilhamento de objetos, em vez de permitir que uma situação de inanição ocorra.
P: E quanto a coisas como prioridades, carregamento lento ou lento, etc.?
R: Não há priorização envolvida, por uma questão de simplicidade, basta supor que eu criaria o pool de objetos disponíveis na criação do próprio pool.
fonte
Respostas:
Pool de objetos no .NET Core
O núcleo do dotnet possui uma implementação de pool de objetos adicionada à biblioteca de classes base (BCL). Você pode ler o problema original do GitHub aqui e visualizar o código do System.Buffers . Atualmente,
ArrayPool
é o único tipo disponível e é usado para agrupar matrizes. Há um bom post aqui .Um exemplo de seu uso pode ser visto no ASP.NET Core. Por estar no BCL do pontonet core, o ASP.NET Core pode compartilhar seu pool de objetos com outros objetos, como o serializador JSON do Newtonsoft.Json. Você pode ler esta postagem no blog para obter mais informações sobre como a Newtonsoft.Json está fazendo isso.
Pool de objetos no Microsoft Roslyn C # Compiler
O novo compilador Microsoft Roslyn C # contém o tipo ObjectPool , que é usado para agrupar objetos usados com frequência, que normalmente seriam atualizados e o lixo coletado com muita frequência. Isso reduz a quantidade e o tamanho das operações de coleta de lixo que precisam acontecer. Existem algumas sub-implementações diferentes, todas usando o ObjectPool (consulte: Por que existem tantas implementações do Pool de Objetos no Roslyn? ).
1 - SharedPools - Armazena um pool de 20 objetos ou 100 se o BigDefault for usado.
2 - ListPool e StringBuilderPool - Não são implementações estritamente separadas, mas envolvem a implementação do SharedPools mostrada acima, especificamente para List e StringBuilder. Portanto, isso reutiliza o pool de objetos armazenados no SharedPools.
3 - PooledDictionary e PooledHashSet - Eles usam o ObjectPool diretamente e possuem um pool de objetos totalmente separado. Armazena um pool de 128 objetos.
Microsoft.IO.RecyclableMemoryStream
Esta biblioteca fornece pool para
MemoryStream
objetos. É um substituto para o drop-inSystem.IO.MemoryStream
. Tem exatamente a mesma semântica. Foi projetado por engenheiros do Bing. Leia a postagem do blog aqui ou veja o código no GitHub .Observe que
RecyclableMemoryStreamManager
deve ser declarado uma vez e permanecerá durante todo o processo - este é o pool. É perfeitamente bom usar várias piscinas, se desejar.fonte
RecyclableMemoryStream
que é uma adição incrível para otimizações de desempenho ultra alto.Essa pergunta é um pouco mais complicada do que se poderia esperar devido a várias incógnitas: o comportamento do recurso que está sendo agrupado, a vida útil esperada / necessária dos objetos, o motivo real pelo qual o pool é necessário etc. Geralmente, os pools são para fins especiais - thread pools, pools de conexão etc. - porque é mais fácil otimizar um quando você sabe exatamente o que o recurso faz e, mais importante, tem controle sobre como esse recurso é implementado.
Como não é tão simples, o que tentei fazer é oferecer uma abordagem bastante flexível com a qual você possa experimentar e ver o que funciona melhor. Pedimos desculpas antecipadamente pelo longo cargo, mas há muito a ser abordado quando se trata de implementar um conjunto decente de recursos de uso geral. e estou realmente apenas arranhando a superfície.
Um pool de uso geral teria que ter algumas "configurações" principais, incluindo:
Para o mecanismo de carregamento de recursos, o .NET já nos fornece uma abstração limpa - delegados.
Passe isso pelo construtor do pool e estamos prontos para isso. O uso de um tipo genérico com
new()
restrição também funciona, mas isso é mais flexível.Dos outros dois parâmetros, a estratégia de acesso é a besta mais complicada, então minha abordagem foi usar uma abordagem baseada em herança (interface):
O conceito aqui é simples - vamos deixar a
Pool
classe pública lidar com problemas comuns, como segurança de threads, mas usar um "armazenamento de itens" diferente para cada padrão de acesso. O LIFO é facilmente representado por uma pilha, o FIFO é uma fila e eu usei uma implementação de buffer circular não muito otimizada, mas provavelmente adequada usando umList<T>
ponteiro e index para aproximar um padrão de acesso round-robin.Todas as classes abaixo são classes internas do
Pool<T>
- essa foi uma escolha de estilo, mas como elas realmente não devem ser usadas foraPool
dela, faz mais sentido.Estes são os óbvios - pilha e fila. Eu não acho que eles realmente justifiquem muita explicação. O buffer circular é um pouco mais complicado:
Eu poderia ter escolhido uma série de abordagens diferentes, mas a linha inferior é que os recursos devem ser acessados na mesma ordem em que foram criados, o que significa que precisamos manter referências a elas, mas marcá-las como "em uso" (ou não ) No pior cenário, apenas um slot está disponível e é necessária uma iteração completa do buffer para cada busca. Isso é ruim se você tiver centenas de recursos reunidos e estiver adquirindo e liberando-os várias vezes por segundo; não é realmente um problema para um conjunto de 5 a 10 itens e, no caso típico , onde os recursos são pouco usados, ele só precisa avançar um ou dois slots.
Lembre-se de que essas classes são classes internas privadas - é por isso que elas não precisam de muita verificação de erros; o próprio pool restringe o acesso a elas.
Jogue uma enumeração e um método de fábrica e terminamos esta parte:
O próximo problema a ser resolvido é a estratégia de carregamento. Eu defini três tipos:
Os dois primeiros devem ser auto-explicativos; o terceiro é uma espécie de híbrido, carrega preguiçosamente recursos, mas na verdade não começa a reutilizar nenhum recurso até que o pool esteja cheio. Isso seria uma boa escolha se você deseja que o pool esteja cheio (o que parece ser o seu caso), mas quer adiar a despesa de realmente criá-los até o primeiro acesso (ou seja, para melhorar o tempo de inicialização).
Os métodos de carregamento realmente não são muito complicados, agora que temos a abstração da loja de itens:
Os campos
size
ecount
acima se referem ao tamanho máximo do pool e ao número total de recursos pertencentes ao pool (mas não necessariamente disponíveis ), respectivamente.AcquireEager
é o mais simples, assume que um item já está na loja - esses itens seriam pré-carregados na construção, ou seja, noPreloadItems
método mostrado por último.AcquireLazy
verifica se há itens gratuitos no pool e, se não, ele cria um novo.AcquireLazyExpanding
criará um novo recurso, desde que o pool ainda não tenha atingido o tamanho desejado. Eu tentei otimizar isso para minimizar o bloqueio, e eu espero não ter cometido nenhum erro (eu ter testado esta em condições multi-threaded, mas, obviamente, não exaustivamente).Você pode estar se perguntando por que nenhum desses métodos se incomoda em verificar se a loja atingiu ou não o tamanho máximo. Eu vou chegar a isso em um momento.
Agora para a piscina em si. Aqui está o conjunto completo de dados particulares, alguns dos quais já foram mostrados:
Respondendo à pergunta que encobri no último parágrafo - como garantir a limitação do número total de recursos criados -, o .NET já possui uma ferramenta perfeitamente boa para isso, chamada Semaphore, e foi projetada especificamente para permitir uma correção número de threads que acessam um recurso (nesse caso, o "recurso" é o armazenamento interno de itens). Como não estamos implementando uma fila completa de produtores / consumidores, isso é perfeitamente adequado para nossas necessidades.
O construtor fica assim:
Não deve haver surpresas aqui. A única coisa a se notar é o revestimento especial para carregamento rápido, usando o
PreloadItems
método já mostrado anteriormente.Como quase tudo já foi abstraído de maneira limpa até agora, os métodos
Acquire
e osRelease
métodos reais são realmente muito diretos:Como explicado anteriormente, estamos usando o
Semaphore
para controlar a simultaneidade, em vez de verificar religiosamente o status do armazenamento de itens. Desde que os itens adquiridos sejam liberados corretamente, não há com o que se preocupar.Por último, mas não menos importante, há a limpeza:
O objetivo dessa
IsDisposed
propriedade ficará claro em um momento. Todo oDispose
método principal realmente faz é descartar os itens agrupados reais, se eles implementaremIDisposable
.Agora você pode basicamente usar isso como está, com um
try-finally
bloco, mas não gosto dessa sintaxe, porque se você começar a repassar recursos agrupados entre classes e métodos, isso ficará muito confuso. É possível que a classe principal que usa um recurso nem tenha uma referência ao pool. Ele realmente se torna bastante confuso, portanto, uma abordagem melhor é criar um objeto em pool "inteligente".Digamos que começamos com a seguinte interface / classe simples:
Aqui está nosso
Foo
recurso descartável fingido que implementaIFoo
e possui algum código padrão para gerar identidades únicas. O que fazemos é criar outro objeto especial em pool:Isso apenas copia todos os métodos "reais" para seu interior
IFoo
(poderíamos fazer isso com uma biblioteca de Proxy Dinâmico como o Castle, mas não vou entrar nisso). Ele também mantém uma referência aoPool
que o cria, para que, quandoDispose
este objeto, ele seja automaticamente liberado de volta ao pool. Exceto quando o pool já tiver sido descartado - isso significa que estamos no modo "limpeza" e, nesse caso, ele realmente limpa o recurso interno .Usando a abordagem acima, conseguimos escrever código como este:
Isso é uma coisa muito boa de poder fazer. Isso significa que o código que usa o
IFoo
(ao contrário do código que o cria) não precisa realmente estar ciente do pool. Você pode até injetarIFoo
objetos usando sua biblioteca DI favorita ePool<T>
como fornecedor / fábrica.Coloquei o código completo no PasteBin para sua diversão de copiar e colar. Há também um pequeno programa de teste que você pode usar para brincar com diferentes modos de carregamento / acesso e condições multithread, para se certificar de que é seguro para threads e não com erros.
Entre em contato se tiver alguma dúvida ou preocupação sobre isso.
fonte
Algo assim pode atender às suas necessidades.
Exemplo de uso
fonte
Put
método e deixaria de fora por simplicidade algum tipo de verificação para verificar se o objeto está com defeito e criar uma nova instância a ser adicionada ao pool em vez de inserir a anterior?Exemplo do MSDN: Como: criar um pool de objetos usando um ConcurrentBag
fonte
Naquela época, a Microsoft fornecia uma estrutura por meio do Microsoft Transaction Server (MTS) e, mais tarde, do COM + para fazer o pool de objetos para objetos COM. Essa funcionalidade foi transportada para System.EnterpriseServices no .NET Framework e agora no Windows Communication Foundation.
Pool de Objetos no WCF
Este artigo é do .NET 1.1, mas ainda deve ser aplicado nas versões atuais do Framework (mesmo que o WCF seja o método preferido).
Pool de objetos .NET
fonte
IInstanceProvider
interface existe, pois implementarei isso na minha solução. Eu sempre gosto de empilhar meu código atrás de uma interface fornecida pela Microsoft quando eles fornecem uma definição adequada.Eu realmente gosto da implementação do Aronaught - especialmente porque ele lida com a espera do recurso para se tornar disponível através do uso de um semáforo. Gostaria de fazer várias adições:
sync.WaitOne()
parasync.WaitOne(timeout)
e exponha o tempo limite como um parâmetro noAcquire(int timeout)
método Isso também exigiria o tratamento da condição quando o encadeamento expirar, aguardando a disponibilidade de um objeto.Recycle(T item)
método para lidar com situações em que um objeto precise ser reciclado quando ocorrer uma falha, por exemplo.fonte
Essa é outra implementação, com número limitado de objetos no pool.
fonte
Orientado para Java, este artigo expõe o padrão do pool connectionImpl e o padrão do pool de objetos abstraídos e pode ser uma boa primeira abordagem: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool. htm
Padrão de pool de objetos:
fonte
Uma extensão do msdn é como criar um pool de objetos usando um ConcurrentBag.
https://github.com/chivandikwa/ObjectPool
fonte
Você pode usar o pacote nuget
Microsoft.Extensions.ObjectPool
Documentações aqui:
https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-3.1 https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.objectpool
fonte