ADO.NET persistência de grãos
O código de back-end de armazenamento relacional é construído com base na Orleans funcionalidade ADO.NET genérica e, consequentemente, é independente do fornecedor de banco de dados. O Orleans layout de armazenamento de dados já foi explicado em tabelas de tempo de execução. A configuração das cadeias de conexão é feita conforme explicado no Orleans Guia de Configuração.
Para fazer Orleans o código funcionar com um determinado back-end de banco de dados relacional, é necessário o seguinte:
- A biblioteca ADO.NET apropriada deve ser carregada no processo. Isso deve ser definido como de costume, por exemplo, através do elemento DbProviderFactories na configuração do aplicativo.
- Configure o ADO.NET invariante através da
Invariant
propriedade nas opções. - O banco de dados precisa existir e ser compatível com o código. Isso é feito executando um script de criação de banco de dados específico do fornecedor. Para obter mais informações, consulte Configuração ADO.NET.
O provedor de armazenamento de grãos do ADO .NET permite armazenar o estado do grão em bancos de dados relacionais. Atualmente, os seguintes bancos de dados são suportados:
- SQL Server
- MySQL/MariaDB
- PostgreSQL
- Oracle
Primeiro, instale o pacote base:
Install-Package Microsoft.Orleans.Persistence.AdoNet
Leia o artigo de configuração do ADO.NET para obter informações sobre como configurar seu banco de dados, incluindo o ADO.NET Invariant correspondente e os scripts de instalação.
Segue-se um exemplo de como configurar um fornecedor de armazenamento ADO.NET através ISiloHostBuilderde:
var siloHostBuilder = new HostBuilder()
.UseOrleans(c =>
{
c.AddAdoNetGrainStorage("OrleansStorage", options =>
{
options.Invariant = "<Invariant>";
options.ConnectionString = "<ConnectionString>";
options.UseJsonFormat = true;
});
});
Essencialmente, você só precisa definir a cadeia de conexão específica do fornecedor do banco de dados e um Invariant
(consulte Configuração do ADO.NET) que identifique o fornecedor. Você também pode escolher o formato no qual os dados são salvos, que pode ser binário (padrão), JSON ou XML. Embora o binário seja a opção mais compacta, ele é opaco e você não poderá ler ou trabalhar com os dados. JSON é a opção recomendada.
Pode definir as seguintes propriedades através de AdoNetGrainStorageOptions:
/// <summary>
/// Options for AdoNetGrainStorage
/// </summary>
public class AdoNetGrainStorageOptions
{
/// <summary>
/// Define the property of the connection string
/// for AdoNet storage.
/// </summary>
[Redact]
public string ConnectionString { get; set; }
/// <summary>
/// Set the stage of the silo lifecycle where storage should
/// be initialized. Storage must be initialized prior to use.
/// </summary>
public int InitStage { get; set; } = DEFAULT_INIT_STAGE;
/// <summary>
/// Default init stage in silo lifecycle.
/// </summary>
public const int DEFAULT_INIT_STAGE =
ServiceLifecycleStage.ApplicationServices;
/// <summary>
/// The default ADO.NET invariant will be used for
/// storage if none is given.
/// </summary>
public const string DEFAULT_ADONET_INVARIANT =
AdoNetInvariants.InvariantNameSqlServer;
/// <summary>
/// Define the invariant name for storage.
/// </summary>
public string Invariant { get; set; } =
DEFAULT_ADONET_INVARIANT;
/// <summary>
/// Determine whether the storage string payload should be formatted in JSON.
/// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format the storage string payload.</remarks>
/// </summary>
public bool UseJsonFormat { get; set; }
public bool UseFullAssemblyNames { get; set; }
public bool IndentJson { get; set; }
public TypeNameHandling? TypeNameHandling { get; set; }
public Action<JsonSerializerSettings> ConfigureJsonSerializerSettings { get; set; }
/// <summary>
/// Determine whether storage string payload should be formatted in Xml.
/// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format storage string payload.</remarks>
/// </summary>
public bool UseXmlFormat { get; set; }
}
A persistência ADO.NET tem a funcionalidade de fazer a versão de dados e definir (des)serializadores arbitrários com regras de aplicativo arbitrárias e streaming, mas, atualmente, não há nenhum método para expô-lo ao código do aplicativo.
ADO.NET lógica da persistência
Os princípios para ADO.NET armazenamento de persistência com suporte são:
- Mantenha os dados críticos para os negócios seguros e acessíveis enquanto os dados, o formato dos dados e o código evoluem.
- Aproveite a funcionalidade específica do fornecedor e do armazenamento.
Na prática, isso significa aderir a ADO.NET metas de implementação e alguma lógica de implementação adicionada no ADO. Provedores de armazenamento específicos da NET que permitem evoluir a forma dos dados no armazenamento.
Além dos recursos usuais do provedor de armazenamento, o provedor de ADO.NET tem capacidade integrada para:
- Altere os dados de armazenamento de um formato para outro (por exemplo, de JSON para binário) durante o estado de ida e volta.
- Molde o tipo a ser salvo ou lido do armazenamento de maneiras arbitrárias. Isso permite que a versão do estado evolua.
- Transmita dados para fora do banco de dados.
Ambos 1.
e 2.
podem ser aplicados com base em parâmetros de decisão arbitrários, como ID de grão, tipo de grão, dados de carga útil.
Este é o caso para que você possa escolher um formato de serialização, por exemplo , Simple Binary Encoding (SBE) e implementa IStorageDeserializer e IStorageSerializer. Os serializadores internos foram criados usando este método:
- OrleansStorageDefaultXmlSerializer
- OrleansStorageDefaultXmlDeserializer
- OrleansStorageDefaultJsonSerializer
- OrleansStorageDefaultJsonDeserializer
- OrleansStorageDefaultBinarySerializer
- OrleansStorageDefaultBinaryDeserializer
Quando os serializadores tiverem sido implementados, eles precisarão ser adicionados à StorageSerializationPicker propriedade em AdoNetGrainStorage. Aqui está uma implementação do IStorageSerializationPicker
. Por padrão, StorageSerializationPicker
será usado. Um exemplo de alteração do formato de armazenamento de dados ou uso de serializadores pode ser visto em RelationalStorageTests.
Atualmente, não há nenhum método para expor o seletor de serialização para o Orleans aplicativo, pois não há nenhum método para acessar a estrutura criada AdoNetGrainStorage
.
Objetivos do design
1. Permitir o uso de qualquer back-end que tenha um provedor de ADO.NET
Isso deve abranger o conjunto mais amplo possível de back-ends disponíveis para .NET, que é um fator em instalações locais. Alguns provedores são listados em ADO.NET visão geral, mas nem todos estão listados, como o Teradata.
2. Mantenha o potencial de ajustar consultas e estrutura de banco de dados conforme apropriado, mesmo enquanto uma implantação está em execução
Em muitos casos, os servidores e bases de dados são alojados por um terceiro em relação contratual com o cliente. Não é uma situação incomum encontrar um ambiente de hospedagem que é virtualizado, e onde o desempenho flutua devido a fatores imprevistos, como vizinhos barulhentos ou hardware defeituoso. Pode não ser possível alterar e reimplantar Orleans binários (por motivos contratuais) ou até mesmo binários de aplicativos, mas geralmente é possível ajustar os parâmetros de implantação do banco de dados. Alterar componentes padrão, como Orleans binários, requer um procedimento mais demorado para otimizar em uma determinada situação.
3. Permitir que você faça uso de habilidades específicas do fornecedor e da versão
Os fornecedores implementaram diferentes extensões e recursos em seus produtos. É sensato fazer uso desses recursos quando eles estiverem disponíveis. Esses são recursos como UPSERT nativo ou PipelineDB no PostgreSQL e PolyBase ou tabelas e procedimentos armazenados compilados nativamente no SQL Server.
4. Tornar possível otimizar os recursos de hardware
Ao conceber uma aplicação, é muitas vezes possível antecipar quais os dados que necessitam de ser inseridos mais rapidamente do que outros dados e quais os dados que mais provavelmente poderão ser colocados em armazenamento a frio, o que é mais barato (por exemplo, dividir os dados entre SSD e HDD). Considerações adicionais incluem a localização física dos dados (alguns dados podem ser mais caros (por exemplo, SSD, RAID, visualização HDD, RAID), ou mais seguro), ou alguma outra base de decisão. Relacionado ao ponto 3., alguns bancos de dados oferecem esquemas especiais de particionamento, como tabelas particionadas e índices do SQL Server.
Estes princípios aplicam-se durante todo o ciclo de vida da aplicação. Considerando que um dos princípios em si é a alta disponibilidade, deve ser possível ajustar o sistema de Orleans armazenamento sem interrupção da Orleans implantação, ou deve ser possível ajustar as consultas de acordo com dados e outros parâmetros do aplicativo. Um exemplo de mudanças dinâmicas pode ser visto na postagem do blog de Brian Harry:
Quando uma tabela é pequena, quase não importa qual é o plano de consulta. Quando é médio, um plano de consulta OK é bom, mas quando é enorme (milhões e milhões ou bilhões de linhas), até mesmo uma pequena variação no plano de consulta pode matá-lo. Por esta razão, sugerimos fortemente as nossas questões sensíveis.
5. Nenhuma suposição sobre quais ferramentas, bibliotecas ou processos de implantação são usados nas organizações
Muitas organizações têm familiaridade com um determinado conjunto de ferramentas de banco de dados, sendo exemplos Dacpac ou Red Gate. Pode ser que a implantação de um banco de dados exija permissão ou uma pessoa, como alguém em uma função de DBA, para fazê-lo. Normalmente, isso significa também ter o layout do banco de dados de destino e um esboço aproximado das consultas que o aplicativo produzirá para uso na estimativa da carga. Pode haver processos, talvez influenciados por padrões do setor, que exijam a implantação baseada em scripts. Ter as consultas e estruturas de banco de dados em um script externo torna isso possível.
6. Use o conjunto mínimo de funcionalidades de interface necessárias para carregar as bibliotecas e funcionalidades ADO.NET
Isso é rápido e tem menos superfície exposta às discrepâncias de implementação da biblioteca ADO.NET.
7. Torne o design fragmentável
Quando fizer sentido, por exemplo, em um provedor de armazenamento relacional, torne o design facilmente fragmentável. Por exemplo, isto significa não utilizar dados dependentes da base de dados (por exemplo). IDENTITY
As informações que distinguem os dados de linha devem basear-se apenas nos dados dos parâmetros reais.
8. Torne o design fácil de testar
Idealmente, criar um novo back-end deve ser tão fácil quanto traduzir um dos scripts de implantação existentes para o dialeto SQL do back-end que você está tentando segmentar, adicionar uma nova cadeia de conexão aos testes (assumindo parâmetros padrão), verificar se um determinado banco de dados está instalado e, em seguida, executar os testes em relação a ele.
9. Tendo em conta os pontos anteriores, torne o mais transparente possível tanto a portabilidade de scripts para novos back-ends como a modificação de scripts de back-end já implementados
Realização dos objetivos
A Orleans estrutura não sabe sobre hardware específico de implantação (qual hardware pode mudar durante a implantação ativa), a alteração de dados durante o ciclo de vida da implantação ou certos recursos específicos do fornecedor que só são utilizáveis em determinadas situações. Por esta razão, a interface entre o banco de dados e Orleans deve aderir ao conjunto mínimo de abstrações e regras para atender a esses objetivos, torná-lo robusto contra o uso indevido, e torná-lo fácil de testar, se necessário. Tabelas de tempo de execução, gerenciamento de cluster e a implementação concreta do protocolo de associação. Além disso, a implementação do SQL Server contém ajuste específico da edição do SQL Server. O contrato de interface entre a base de dados e Orleans é definido da seguinte forma:
- A ideia geral é que os dados sejam lidos e escritos através Orleansde consultas específicas. Orleans opera em nomes e tipos de colunas ao ler e em nomes e tipos de parâmetros ao escrever.
- As implementações devem preservar nomes e tipos de entrada e saída. Orleans usa esses parâmetros para ler os resultados da consulta por nome e tipo. O ajuste específico do fornecedor e da implantação é permitido, e as contribuições são incentivadas desde que o contrato de interface seja mantido.
- A implementação entre scripts específicos do fornecedor deve preservar os nomes de restrição. Isso simplifica a solução de problemas, em virtude da nomenclatura uniforme em implementações concretas.
- Versão – ou ETag no código do aplicativo – para Orleans, isso representa uma versão exclusiva. O tipo de sua implementação real não é importante, desde que represente uma versão exclusiva. Na implementação, Orleans o código espera um inteiro de 32 bits assinado.
- Para ser explícito e remover a ambiguidade, Orleans espera que algumas consultas retornem TRUE como > valor 0 ou FALSE como valor 0 . Ou seja, o número de linhas afetadas ou retornadas não importa. Se um erro for gerado ou uma exceção for lançada, a consulta deve garantir que toda a transação seja revertida e pode retornar FALSE ou propagar a exceção.
- Atualmente, todas as consultas, exceto uma, são inserções ou atualizações de linha única (observação, pode-se substituir
UPDATE
consultas porINSERT
, desde que as consultas associadasSELECT
tenham sido executadas na última gravação).
Os mecanismos de banco de dados suportam a programação no banco de dados. Isso é semelhante à ideia de carregar um script executável e invocá-lo para executar operações de banco de dados. Em pseudocódigo pode ser descrito como:
const int Param1 = 1;
const DateTime Param2 = DateTime.UtcNow;
const string queryFromOrleansQueryTableWithSomeKey =
"SELECT column1, column2 "+
"FROM <some Orleans table> " +
"WHERE column1 = @param1 " +
"AND column2 = @param2;";
TExpected queryResult =
SpecificQuery12InOrleans<TExpected>(query, Param1, Param2);
Esses princípios também estão incluídos nos scripts de banco de dados.
Algumas ideias sobre a aplicação de scripts personalizados
- Altere scripts para persistência de
OrleansQuery
grãos paraIF ELSE
que algum estado seja salvo usando o padrãoINSERT
, enquanto alguns estados de grão podem usar tabelas otimizadas para memória. AsSELECT
consultas devem ser alteradas em conformidade. - A ideia pode
1.
ser usada para tirar proveito de outros aspetos específicos da implantação ou do fornecedor, como dividir dados entreSSD
ouHDD
, colocar alguns dados em tabelas criptografadas, ou talvez inserir dados estatísticos via SQL-Server-to-Hadoop, ou até mesmo servidores vinculados.
Os scripts alterados podem ser testados executando o Orleans conjunto de testes ou diretamente no banco de dados usando, por exemplo, o SQL Server Unit Test Project.
Diretrizes para adicionar novos provedores de ADO.NET
- Adicione um novo script de configuração de banco de dados de acordo com a seção Realização das metas acima.
- Adicione o nome invariante do ADO do AdoNetInvariants fornecedor e ADO.NET dados específicos do provedor a DbConstantsStore. Eles são (potencialmente) usados em algumas operações de consulta. por exemplo, para selecionar o modo de inserção de estatísticas correto (ou seja, o
UNION ALL
com ou semFROM DUAL
). - Orleans tem testes abrangentes para todas as lojas do sistema: associação, lembretes e estatísticas. A adição de testes para o novo script de banco de dados é feita copiando e colando classes de teste existentes e alterando o nome invariante do ADO. Além disso, derive de RelationalStorageForTesting para definir a funcionalidade de teste para o ADO invariante.