Você já viu uma construção como
using (var scope = new TransactionScope(TransactionScopeOption.Required))
em C #? Isso significa que o código em execução no bloco
using
está na transação e, após sair desse bloco, as alterações serão confirmadas ou revertidas. Parece compreensível até você começar a cavar mais fundo. E quanto mais fundo você cavar, mais e mais estranho se torna. De qualquer forma, quando me familiarizei com a classe
TransactionScope
e, em geral, com as transações .NET, surgiram várias perguntas.
O que é a classe
TransactionScope
? Assim que usamos a construção
using (var scope = new TransactionScope())
, tudo em nosso programa se torna imediatamente transacional? O que são "Resource Manager" e "Transaction Manager"? Posso escrever meu próprio gerenciador de recursos e como ele se "conecta" à instância criada do
TransactionScope
? O que é uma transação distribuída e é verdade que uma transação distribuída no SQL Server ou Oracle Database é igual a uma transação .NET distribuída?
Nesta publicação, tentei coletar material que ajuda a encontrar respostas para essas perguntas e a entender as transações no mundo .NET.
1. Introdução
O que são transações e quais problemas eles resolvem?
As transações em questão aqui são operações que transferem o sistema de um estado aceitável para outro e são garantidas para não deixar o sistema em um estado inaceitável, mesmo que surjam situações imprevistas. Que tipo de condições aceitáveis são essas, no caso geral, depende do contexto. Aqui consideraremos uma situação aceitável na qual os dados que processamos são integrais. Entende-se que as alterações que compõem a transação são confirmadas juntas ou não confirmadas. Além disso, as alterações em uma transação podem ser isoladas das alterações feitas no sistema por outra transação. Os requisitos básicos para transações são indicados pelo acrônimo ACID. Para o primeiro contato com eles,
um artigo na Wikipedia é adequado.
Um exemplo clássico de uma transação é a transferência de dinheiro entre duas contas. Nessa situação, retirar dinheiro da conta número 1 sem creditar na conta número 2 é inaceitável, da mesma maneira que depositar na conta número 2 sem retirar a conta número 1. Em outras palavras, queremos que ambas as operações sejam tanto retirada quanto creditando - realizado imediatamente. Se um deles falhar, a segunda operação não deve ser executada. Você pode chamar esse princípio de "tudo ou nada". Além disso, é desejável que as operações sejam executadas de forma síncrona, mesmo no caso de falhas sistêmicas, como falta de energia, ou seja, que o sistema seja visto em um estado aceitável assim que estiver disponível após a restauração.
Em termos matemáticos, podemos dizer que, com relação ao sistema, existe um invariante que gostaríamos de preservar. Por exemplo, o valor em ambas as contas: é necessário que após a transação (transferência de dinheiro) o valor permaneça o mesmo de antes. A propósito, no exemplo clássico de transferência de dinheiro, a contabilidade também aparece - uma área de assunto em que o conceito de transação surgiu naturalmente.
Ilustramos o exemplo de transferência de dinheiro entre duas contas. A primeira imagem mostra a situação em que a transferência de 50 rublos da conta 1 para a conta 2 foi concluída com êxito. A cor verde indica que o sistema está em um estado aceitável (os dados estão completos).
Agora imagine que a transferência é realizada fora da transação e após a retirada do dinheiro da conta nº 1, ocorreu uma falha, devido à qual o dinheiro retirado não foi creditado na conta nº 2. O sistema estará em um estado inaceitável (cor vermelha).
Se ocorreu um erro entre as operações de retirada e obtenção de créditos, mas a transferência foi realizada como parte de uma transação, a operação de retirada será cancelada. Como resultado, o sistema permanecerá em seu estado aceitável original.
Vou dar exemplos de situações da experiência de nossa empresa nas quais as transações são úteis: contabilizar mercadorias (contabilizar o número de mercadorias de vários tipos que estão em determinadas lojas e a caminho), contabilizar recursos de armazenamento (contabilizar o volume de uma sala ocupada por mercadorias de um determinado tipo, volume de um livre para a colocação de mercadorias, a quantidade de mercadorias que os funcionários e os sistemas de armazenamento automatizados podem mover por dia).
Os problemas que surgem quando a integridade dos dados é violada são óbvios. As informações fornecidas pelo sistema não se tornam apenas falsas - elas perdem contato com a realidade e se transformam em bobagens.
Quais transações são consideradas aqui
Os benefícios fornecidos pelas transações são conhecidos. Portanto, para manter a integridade dos dados, precisamos de um banco de dados relacional, porque é aí que as transações são feitas? Na verdade não. Foi dito acima que o conceito de transação depende do contexto, e agora consideraremos brevemente de que transações podemos falar ao discutir sistemas de informação.
Para começar, separamos os conceitos de transações de domínio do assunto (transações comerciais) e transações do sistema. O segundo pode ser implementado em locais diferentes e de maneiras diferentes.
Vamos do nível mais alto - a área de assunto. A pessoa interessada pode declarar que existem alguns estados aceitáveis e não deseja ver o sistema de informação fora desses estados. Não apresentaremos exemplos extras: a transferência de dinheiro entre contas é adequada aqui. Esclarecemos apenas que uma transferência não é necessariamente uma transferência de dinheiro entre as contas de liquidação de dois clientes bancários. Não menos importante é a tarefa da contabilidade, quando as contas devem refletir as fontes e o objetivo dos fundos da organização, e a transferência deve refletir a mudança na distribuição de fundos por essas fontes e o objetivo. Este foi um exemplo de uma
transação de domínio do assunto .
Agora vamos ver os exemplos mais comuns e interessantes da implementação de transações do sistema. Nas transações do sistema, vários meios técnicos fornecem os requisitos da área de assunto. Uma solução clássica comprovada desse tipo é uma
transação de DBMS relacional (primeiro exemplo). Os sistemas modernos de gerenciamento de banco de dados (relacionais
e não muito ) fornecem um mecanismo de transação que permite salvar (confirmar) todas as alterações feitas no período especificado de trabalho ou descartá-las (retroceder). Ao usar esse mecanismo, operações de retirada de dinheiro de uma conta e crédito para outra conta que compõem a transação da área de assunto, os meios do DBMS serão combinados em uma transação do sistema e serão executados juntos ou não serão executados.
Usar um DBMS, é claro, não é necessário. Grosso modo, você geralmente pode implementar o mecanismo de transação DBMS na sua linguagem de programação favorita e aproveitar o análogo instável e com erros das ferramentas existentes. Mas sua “bicicleta” pode ser otimizada para situações específicas na área de assunto.
Existem opções mais interessantes. As linguagens de programação industrial modernas (C # e Java em primeiro lugar) oferecem ferramentas projetadas especificamente para organizar transações envolvendo subsistemas completamente diferentes, e não apenas o DBMS. Nesta publicação, chamaremos esses softwares de transações. No caso de C #, essas são
transações do espaço para nome System.Transactions (o segundo exemplo) e são descritas abaixo.
Antes de passar para as transações
System.Transactions
, não se pode deixar de mencionar mais um fenômeno interessante.
System.Transactions
ferramentas
System.Transactions
permitem que o programador implemente independentemente a
memória de transação programática . Nesse caso, as operações do programa que afetam o estado do sistema (no caso das linguagens de programação imperativas clássicas, é uma operação de atribuição) são incluídas por padrão nas transações que podem ser confirmadas e revertidas da mesma maneira que as transações do DBMS. Com essa abordagem, a necessidade de usar mecanismos de sincronização (em C # -
lock
, em Java -
synchronized
) é significativamente reduzida. Um desenvolvimento adicional dessa idéia é a
memória transacional de software, suportada no nível da plataforma (terceiro exemplo). Espera-se que esse milagre seja encontrado em uma linguagem cuja elegância supere sua aplicabilidade industrial - Clojure. E para linguagens trabalhador-camponesas, existem bibliotecas de plug-in que fornecem a funcionalidade da memória transacional programática.
As transações do sistema podem incluir vários sistemas de informação, caso em que são distribuídos. Distribuídos podem ser transações e software DBMS; tudo depende de qual funcionalidade uma ferramenta de transação específica suporta. Transações distribuídas mais detalhadas são discutidas na seção correspondente. Vou dar uma imagem para facilitar a compreensão dos tópicos discutidos.
Seção TL; DR
Existem processos que consistem em várias operações indivisíveis (atômicas) aplicadas ao sistema, no caso geral, não necessariamente informativas. Cada operação indivisível pode deixar o sistema em um estado inaceitável quando a integridade dos dados é comprometida. Por exemplo, se uma transferência de dinheiro entre duas contas for representada por duas operações indivisíveis de retirada da conta nº 1 e crédito na conta nº 2, apenas uma dessas operações violará a integridade dos dados. O dinheiro desaparece no meio do nada ou aparece no meio do nada. Uma transação combina operações indivisíveis para que sejam executadas todas juntas (é claro, sequencialmente, se necessário) ou não sejam executadas. Podemos falar sobre transações de domínio e transações em sistemas técnicos que normalmente implementam transações de domínio.
Transações baseadas em System.Transactions
O que é isso
No mundo .NET, existe uma estrutura de software projetada pelos criadores de uma plataforma de gerenciamento de transações. Da perspectiva de um programador transacional, essa estrutura consiste nos
System.Transactions
TransactionScope
,
TransactionScopeOption
,
TransactionScopeAsyncFlowOption
e
TransactionOptions
System.Transactions
espaço para nome
System.Transactions
. Se falarmos sobre o .NET Standard, tudo isso estará disponível a partir da
versão 2.0 .
As transações do namespace
System.Transactions
são baseadas no
padrão X / Open XA do The Open Group . Esta norma introduz muitos dos termos discutidos abaixo e, o mais importante, descreve transações distribuídas, que também são cobertas nesta publicação em uma seção especial. A implementação de transações de software em outras plataformas, por exemplo
, Java, é baseada no mesmo padrão.
Um caso de uso de transação típico para um programador C # é o seguinte:
using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) {
Dentro do bloco
using
, está o código que executa o trabalho, cujos resultados devem ser confirmados ou cancelados todos juntos. Exemplos clássicos desse trabalho estão lendo e gravando no banco de dados ou enviando e recebendo mensagens da fila. Quando o controle sair do bloco
using
, a transação será confirmada. Se você remover a chamada
Complete
, a transação será revertida. Bem simples.
Acontece que, durante uma reversão de transação, todas as operações realizadas dentro de um bloco
using
serão canceladas? E se eu atribuí uma variável a um valor diferente, essa variável restaurará o valor antigo? Quando vi pela primeira vez um design semelhante, pensei que sim. De fato, é claro, nem todas as alterações serão revertidas, mas apenas muito
especiais . Se todas as alterações fossem revertidas, essa seria a memória transacional do software descrita acima. Agora vamos ver quais são essas alterações especiais que podem participar de transações do programa com base em
System.Transactions
.
Gerentes de recursos
Para que algo
System.Transactions
suporte a transações baseadas no
System.Transactions
, é necessário que ele possua informações de que uma transação está em andamento no momento e que esteja registrada em algum registro dos participantes da transação. Você pode obter informações sobre se o trabalho transacional está em andamento verificando a propriedade estática
Current
da classe
System.Transactions.Transaction
. Digitar o bloco
using
do tipo indicado acima apenas define essa propriedade, se ainda não tiver sido definida anteriormente. E para se registrar como participante de uma transação, você pode usar métodos do tipo
Transaction.Enlist Smth
. Além disso, você precisa implementar a interface exigida por esses métodos. Gerenciador de recursos - este é apenas um "algo" que suporta a interação com transações do
System.Transactions
(uma definição mais específica é fornecida abaixo).
O que são gerenciadores de recursos? Se trabalhamos em C # com um DBMS, por exemplo, SQL Server ou Oracle Database, geralmente usamos os drivers apropriados, e eles são os recursos de gerenciamento. No código, eles são representados pelos tipos
System.Data.SqlClient.SqlConnection
e
Oracle.ManagedDataAccess.Client.OracleConnection
.
Eles também dizem que o MSMQ oferece suporte a transações baseadas em
System.Transactions
. Guiado pelo conhecimento e exemplos retirados da Internet, você pode criar seu próprio gerenciador de recursos. O exemplo mais simples é dado na próxima seção.
Além dos gerenciadores de recursos, também devemos ter um Gerenciador de Transações, que monitorará a transação e dará ordens aos gerentes de recursos em tempo hábil. Dependendo de quais gerenciadores de recursos estão envolvidos na transação (quais características eles têm e onde estão localizados), diferentes gerenciadores de transações são conectados ao trabalho. Nesse caso, a seleção da versão apropriada é automática e não requer a intervenção de um programador.
Mais especificamente, o gerenciador de recursos é uma instância de uma classe que implementa a interface especial
System.Transactions.IEnlistmentNotification
. A instância da classe, conforme direcionado pelo cliente, é registrada como participante da transação usando a propriedade estática
System.Transactions.Transaction.Current
. Posteriormente, o gerenciador de transações chama métodos da interface especificada, conforme necessário.
É claro que, no tempo de execução, o conjunto de gerenciadores de recursos envolvidos na transação pode mudar. Por exemplo, depois de inserir o bloco
using
, podemos primeiro fazer algo no SQL Server e depois no Oracle Database. Dependendo desse conjunto de gerenciadores de recursos, o gerenciador de transações usado é determinado. Para ser mais preciso, o protocolo de transação usado é determinado pelo conjunto de gerenciadores de recursos, e o gerenciador de transações que o suporta é determinado com base no protocolo. Veremos os protocolos transacionais
mais tarde, quando falarmos sobre transações distribuídas. O mecanismo para selecionar automaticamente o gerenciador de transações apropriado no tempo de execução ao alterar os gerenciadores de recursos envolvidos na transação é chamado Promoção de Transação.
Tipos de gerenciadores de recursos
Os gerentes de recursos podem ser divididos em dois grandes grupos: duráveis e variáveis.
Durable Resource Manager - um gerenciador de recursos que suporta uma transação, mesmo que o sistema de informações não esteja disponível (por exemplo, quando o computador reiniciar). Volatile Resource Manager - Um gerenciador de recursos que não suporta uma transação se o sistema de informações não estiver disponível. Um gerenciador de recursos inconsistente suporta apenas transações na memória.
Os gerenciadores de recursos clássicos de longo prazo são o DBMS (ou o driver DBMS da plataforma de software). Não importa o que aconteça - pelo menos um mau funcionamento do sistema operacional, pelo menos uma falta de energia - o DBMS garantirá a integridade dos dados após retornar à condição de trabalho. Por isso, é claro, você deve pagar alguns inconvenientes, mas neste artigo não os consideraremos. Um exemplo de um gerenciador de recursos não persistente é a memória transacional do software mencionada acima.
Usando o TransactionScope
Ao criar um objeto do tipo
TransactionScope
você pode especificar alguns parâmetros.
Primeiramente, há uma configuração que informa ao tempo de execução o que ele precisa:
- Use uma transação que já existe no momento;
- Certifique-se de criar um novo;
- por outro lado, execute o código dentro de um bloco
using
fora de uma transação.
A enumeração
System.Transactions.TransactionScopeOption
é responsável por tudo isso.
Em segundo lugar, você pode definir o nível de isolamento da transação. Este é um parâmetro que permite encontrar um compromisso entre a independência da mudança e a velocidade. O nível mais independente - serializável - garante que não haja situações em que as alterações feitas em uma transação que ainda não foram confirmadas possam ser vistas em outra transação. Cada próximo nível adiciona uma situação específica, quando a execução simultânea de transações pode afetar uma à outra.
Por padrão, uma transação é aberta no nível serializável, o que pode ser desagradável (consulte, por exemplo, este comentário ).Definir o nível de isolamento da transação durante a criação TransactionScope
é um aconselhamento para os gerenciadores de recursos. Eles podem até não suportar todos os níveis listados System.Transactions.IsolationLevel
. Além disso, deve-se ter em mente que, ao usar o pool de conexões para trabalhar com o banco de dados, a conexão para a qual o nível de isolamento da transação foi alterado manterá esse nível ao retornar ao pool . Agora, quando o programador receber essa conexão do pool e confiar nos valores padrão, ele observará um comportamento inesperado.Cenários típicos de trabalho cTransactionScope
e armadilhas significativas (ou seja, transações aninhadas) são bem cobertas neste artigo no "Habr" .Aplicabilidade de transação de software
Deve-se dizer que em quase qualquer sistema de informações em operação comercial, são iniciados processos que podem levar o sistema a um estado inaceitável. Portanto, pode ser necessário controlar esses processos, descobrir se o estado atual do sistema é aceitável e, se não, restaurá-lo. Transações de software - uma ferramenta pronta para manter o sistema em um estado aceitável.Em cada caso, seria construtivo considerar o custo:- integrar processos à infraestrutura de transações de software (esses processos também precisam estar cientes
TransactionScope
de muitas outras coisas); - manutenção dessa infraestrutura (por exemplo, o custo do aluguel de equipamentos com o Windows a bordo);
- treinamento de funcionários (já que o tópico de transações do .NET não é comum).
Não devemos esquecer que o processo de transação pode ser solicitado a relatar seu progresso ao "mundo externo", por exemplo, para manter um diário de ações fora da transação.Obviamente, a rejeição de transações de software exigirá a criação ou implementação de outros meios de manutenção da integridade dos dados, que também terão seu valor. No final, pode haver casos em que as violações à integridade dos dados são tão raras que é mais fácil restaurar um estado aceitável do sistema por intervenções cirúrgicas do que manter um mecanismo de recuperação automática.Um exemplo de um gerenciador de recursos inconstante
Agora vamos ver um exemplo de um gerenciador de recursos simples que não oferece suporte à recuperação de uma falha do sistema. Teremos um bloco de memória transacional de software que armazena algum valor que pode ser lido e gravado. Na ausência de uma transação, esse bloco se comporta como uma variável regular e, na presença de uma transação, armazena o valor inicial, que pode ser restaurado após a reversão da transação. O código para esse gerenciador de recursos é apresentado abaixo: internal sealed class Stm<T> : System.Transactions.IEnlistmentNotification { private T _current; private T _original; private bool _enlisted; public T Value { get { return _current; } set { if (!Enlist()) { _original = value; } _current = value; } } public Stm(T value) { _current = value; _original = value; } private bool Enlist() { if (_enlisted) return true; var currentTx = System.Transactions.Transaction.Current; if (currentTx == null) return false; currentTx.EnlistVolatile(this, System.Transactions.EnlistmentOptions.None); _enlisted = true; return true; } #region IEnlistmentNotification public void Commit(System.Transactions.Enlistment enlistment) { _original = _current; _enlisted = false; } public void InDoubt(System.Transactions.Enlistment enlistment) { _enlisted = false; } public void Prepare(System.Transactions.PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); } public void Rollback(System.Transactions.Enlistment enlistment) { _current = _original; _enlisted = false; } #endregion IEnlistmentNotification }
Pode-se observar que o único requisito formal é a implementação da interface System.Transactions.IEnlistmentNotification
. Dos interessantes, métodos Enlist
(que não fazem parte System.Transactions.IEnlistmentNotification
) e Prepare
. O método Enlist
apenas verifica se o código fornecido funciona dentro da estrutura da transação e, nesse caso, registra uma instância de sua classe como um gerenciador de recursos não constante. O método Prepare
é chamado pelo gerenciador de transações antes de confirmar as alterações. Nosso gerente de recursos sinaliza sua prontidão para confirmar chamando um método System.Transactions.PreparingEnlistment.Prepared
.A seguir, um código que mostra um exemplo de uso do nosso gerenciador de recursos: var stm = new Stm<int>(1); using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { stm.Value = 2; scope.Complete(); }
Se você using
ler a propriedade imediatamente após sair do bloco stm.Value
, o valor esperado será esperado lá 2
. E se você remover a chamada scope.Complete
, a transação será revertida e a propriedade stm.Value
terá o valor 1
definido antes do início da transação.Uma sequência simplificada de chamadas ao trabalhar com transações é System.Transactions
mostrada no diagrama abaixo.Pode-se observar que, neste exemplo, nem todas as possibilidades oferecidas pela infraestrutura são consideradas System.Transactions
. Vamos considerá-los mais completamente depois de nos familiarizarmos com protocolos transacionais e transações distribuídas na próxima seção.Seção TL; DR
Um programador pode usar uma classe TransactionScope
para executar algum código em uma transação existente ou nova. Uma transação é confirmada se e somente se o TransactionScope
método for chamado em uma instância existente da classe Dispose
, mesmo que o método tenha sido chamado antesComplete
. Um programador pode indicar se deseja iniciar uma nova transação, tirar proveito de uma transação existente ou, inversamente, executar código fora de uma transação existente. Somente gerenciadores de recursos estão envolvidos na transação - componentes de software que implementam determinadas funcionalidades. Os gerenciadores de recursos podem ser de longo prazo (recuperação de uma falha do sistema) e intermitentes (sem recuperação). Um DBMS é um exemplo de um gerenciador de recursos de longa duração. O gerenciador de recursos é coordenado por um gerenciador de transações - um componente de software que é selecionado automaticamente pelo tempo de execução sem a participação de um programador.O gerenciador de recursos inconsistente é uma classe que implementa a interface System.Transactions.IEnlistmentNotification
no métodoPrepare
confirmando sua disponibilidade para confirmar alterações ou, inversamente, sinalizando uma reversão de alterações. Quando o chamador está fazendo algo para gerir os recursos, ele verifica se a transação está agora aberto, e, se aberto, é registrado pelo método System.Transactions.Transaction.EnlistVolatile
.Transações Distribuídas
O que é isso
Uma transação distribuída envolve vários subsistemas de informações (na verdade, nem tudo é tão simples, mais sobre isso abaixo). Entende-se que as alterações em todos os sistemas envolvidos em uma transação distribuída devem ser confirmadas ou revertidas.Vários meios de implementação de transações foram apresentados acima: DBMS, infraestrutura System.Transactions
e memória transacional programática incorporada à plataforma. As transações distribuídas também podem ser fornecidas com essas ferramentas. Por exemplo, no banco de dados Oracle, alterar (e realmente ler) dados em vários bancos de dados em uma única transação automaticamente os transforma em distribuídos. A seguir, falaremos sobre transações distribuídas por software, que podem incluir gerenciadores de recursos heterogêneos.Protocolos transacionais
Um protocolo transacional é um conjunto de princípios pelos quais os aplicativos envolvidos em uma transação interagem. No mundo .NET, os seguintes protocolos são mais comumente encontrados.Leve. Não é usado mais que um gerenciador de recursos durável. Todas as interações transacionais ocorrem no mesmo domínio de aplicativo ou o gerenciador de recursos suporta promoção e confirmação de fase única (implementos IPromotableSinglePhaseNotification
).OleTx. É permitida a interoperabilidade entre vários domínios de aplicativos e vários computadores. Você pode usar muitos gerenciadores de recursos duráveis. Todos os computadores participantes devem estar executando o Windows. Use chamadas de procedimento remoto (RPCs).WS-AT.É permitida a interoperabilidade entre vários domínios de aplicativos e vários computadores. Você pode usar muitos gerenciadores de recursos duráveis. Os computadores participantes podem estar executando vários sistemas operacionais, não apenas o Windows. O protocolo de transmissão de hipertexto (HTTP) é usado.Observou-se acima que o atual protocolo de transação afeta a escolha do gerenciador de transações, e as características dos recursos de controle envolvidos na transação influenciam a escolha do protocolo. Agora listamos os conhecidos gerenciadores de transações.Gerenciador de transações leves (LTM) . Introduzido no .NET Framework 2.0 e posterior. Gerencia transações usando o protocolo Lightweight.Gerenciador de Transações do Kernel (KTM). Introduzido no Windows Vista e Windows Server 2008. Gerencia transações usando o protocolo Lightweight. Ele pode chamar um Sistema de arquivos transacionais (TxF) e um Registro transacional (TxR) no Windows Vista e Windows 2008.Coordenador de transações distribuídas (MSDTC) . Gerencia transações usando os protocolos OleTx e WS-AT.Também deve-se ter em mente que alguns gerenciadores de recursos não oferecem suporte a todos os protocolos listados. Por exemplo, o MSMQ e o SQL Server 2000 não oferecem suporte ao Lightweight, portanto, as transações envolvendo o MSMQ ou o SQL Server 2000 serão gerenciadas pelo MSDTC, mesmo que sejam os únicos participantes. Tecnicamente, essa limitação decorre do fato de que os gerenciadores de recursos especificados, implementando, é claro, a interfaceSystem.Transactions.IEnlistmentNotification
Não implemente a interface System.Transactions.IPromotableSinglePhaseNotification
. Ele contém, entre outras coisas, um método Promote
que o tempo de execução chama, se necessário, para alternar para um gerenciador de transações mais íngreme.A ambiguidade do conceito de transação distribuída deve agora se tornar aparente. Por exemplo, você pode definir uma transação distribuída como uma transação da qual ela participa:- pelo menos dois de qualquer gerenciador de recursos;
- gerentes de recursos arbitrariamente variáveis e pelo menos dois de longa duração;
- pelo menos dois de quaisquer gerenciadores de recursos necessariamente localizados em computadores diferentes.
Portanto, é melhor sempre esclarecer quais transações específicas estão envolvidas.E, neste contexto, o MSDTC é discutido principalmente. É um componente de software do Windows que gerencia transações distribuídas. Há uma interface gráfica para configurar e monitorar transações, que pode ser encontrada no utilitário "Serviços de componentes", seguindo o caminho "Computadores - Meu computador - Coordenador de transações distribuídas - DTC local".Para configuração, selecione o item “Propriedades” no menu de contexto do nó “Local DTC” e, para monitorar transações distribuídas, selecione o item “Estatísticas de transação” no painel central.Fixação bifásica
Se vários gerenciadores de recursos participarem da transação, os resultados de seu trabalho poderão ser diferentes: por exemplo, um deles foi concluído com êxito e ele está pronto para confirmar as alterações, e o outro tem um erro e reverterá as alterações. No entanto, a essência de uma transação distribuída está no fato de que as alterações em todos os recursos de controle envolvidos na transação são confirmadas todas juntas ou revertidas. Portanto, nesses casos, geralmente é usado um protocolo de fixação em duas fases.Em geral, a essência deste protocolo é a seguinte. Durante a primeira faseOs gerenciadores de recursos envolvidos na transação preparam informações suficientes para se recuperar da falha (se for um gerenciador de recursos de longo prazo) e para a conclusão bem-sucedida como resultado de uma confirmação. Do ponto de vista técnico, o gerenciador de recursos sinaliza que ele concluiu a primeira fase chamando o método System.Transactions.PreparingEnlistment.Prepared
no método Prepare
. Ou gestão dos recursos pode notificar alterações de reversão chamando ForceRollback
.Quando todos os gerenciadores de recursos envolvidos na transação "votaram", ou seja, eles notificaram o gerente de transações se desejam confirmar ou reverter as alterações, a segunda fase começa. No momento, os gerentes de recursos são instruídos a confirmar suas alterações (se todos os participantes votaram na correção) ou a recusar alterações (se pelo menos um participante votou na reversão). Tecnicamente, isso é expresso na invocação dos métodos Commit
e Rollback
implementada pelos gerenciadores de recursos e na qual eles invocam o método System.Transactions.Enlistment.Done
.O gerenciador de recursos também pode chamar o método System.Transactions.Enlistment.Done
durante a primeira fase. Nesse caso, entende-se que ele não fará alterações (por exemplo, trabalha apenas para leitura) e não participará da segunda fase. Leia mais sobre o commit em duas fases na Microsoft .Se a conexão entre o gerenciador de transações e pelo menos um dos gerenciadores de recursos for perdida, a transação ficará congelada ("em dúvida", em dúvida). O gerenciador de transações, chamando métodos InDoubt
, notifica os gerenciadores de recursos disponíveis deste evento que podem responder adequadamente.Ainda existe uma fixação trifásica e suas modificações com suas vantagens e desvantagens. O protocolo de consolidação trifásico é menos comum, talvez porque exija ainda mais custos de comunicação entre os subsistemas em interação.Folha de dicas nas interfaces System.Transactions
Algo está difícil. Para resolver um pouco as coisas, descreverei brevemente as principais interfaces de namespace System.Transactions
necessárias para criar um gerenciador de recursos. Aqui está um diagrama de classes.IEnlistmentNotification. O gerenciador de recursos implementa essa interface. O gerenciador de transações chama os métodos implementados na seguinte ordem. Durante a primeira fase, ele chama o método Prepare
(a menos que as estrelas se unissem para chamá-lo ISinglePhaseNotification.SinglePhaseCommit
, como descrito no próximo parágrafo). Sob esse método, o gerenciador de recursos salva as informações necessárias para se recuperar de uma falha, se prepara para a confirmação final de alterações do seu lado e vota para confirmar ou reverter as alterações. Se chega uma segunda fase, dependendo da disponibilidade de recursos e controle dos resultados da votação da transação controle é um dos três métodos: Commit
, InDoubt
, Rollback
.ISinglePhaseNotification.O gerenciador de recursos implementa essa interface se ele deseja fornecer ao gerente de transações a oportunidade de otimizar a execução, reduzindo a segunda fase de confirmação. Se o gerenciador de transações vir apenas um gerenciador de recursos, na primeira fase de confirmação, ele tentará chamar o método do gerenciador de recursos SinglePhaseCommit
(em vez disso IEnlistmentNotification.Prepare
) e, assim, excluir a votação e a transição para a segunda fase. Essa abordagem tem vantagens e desvantagens, sobre as quais a Microsoft escreveu mais claramente aqui .ITransactionPromoter. O gerenciador de recursos implementa essa interface (não apenas diretamente, mas através da interfaceIPromotableSinglePhaseNotification
), se ele quiser fornecer ao gerente de transações a capacidade de aderir ao protocolo Lightweight, mesmo quando estiver chamando remotamente, até que surjam outras condições que exijam complicação do protocolo. Quando você precisar complicar o protocolo, o método será chamado Promote
.IPromotableSinglePhaseNotification. O gerenciador de recursos implementa essa interface para, em primeiro lugar, implementar a interface ITransactionPromoter
e, em segundo lugar, para que o gerenciador de transações possa usar confirmação de fase única, métodos de chamada IPromotableSinglePhaseNotification.SinglePhaseCommit
e IPromotableSinglePhaseNotification.Rollback
. O gerenciador de transações chama um método IPromotableSinglePhaseNotification.Initialize
para marcar o registro bem-sucedido do gerenciador de recursos de maneira simplificada. Mais ou menos, isso pode ser entendido em um documento da Microsoft .Vamos olhar um pouco maisSystem.Transactions.Enlistment
e seus herdeiros. Esse tipo de instância é fornecido pelo gerenciador de transações quando ele chama os métodos de interface implementados pelo gerenciador de recursos.Alistamento. O gerenciador de recursos pode chamar um único método desse tipo - Done
, - para sinalizar a conclusão bem-sucedida de sua parte do trabalho.PreparingEnlistment. Usando uma instância desse tipo durante a primeira fase de confirmação, o gerenciador de recursos pode sinalizar sua intenção de confirmar ou reverter as alterações. Um gerenciador de recursos de longa duração também pode obter as informações necessárias para se recuperar de uma falha do sistema.SinglePhaseEnlistment. Usando uma instância desse tipo, o gerenciador de recursos pode transmitir informações ao gerente de transações sobre os resultados de seu trabalho usando um esquema simplificado (confirmação de fase única).Limitações e alternativas de transação distribuída por software
Um breve estudo das opiniões encontradas na Internet mostra que, em muitas áreas, as transações distribuídas estão fora de moda. Dê uma olhada neste comentário malicioso , por exemplo . O principal objeto de crítica, que é brevemente mencionado aqui , é a natureza síncrona (bloqueadora) das transações distribuídas. Se o usuário enviou uma solicitação durante o processamento em que uma transação distribuída foi organizada, ele receberá uma resposta somente depois que (com êxito ou com erro) todos os subsistemas incluídos na transação terminarem de funcionar. Ao mesmo tempo, há uma opinião apoiada por pesquisas de que o protocolo de confirmação de duas fases mostra um desempenho ruim, especialmente com um aumento no número de subsistemas envolvidos na transação, como é mencionado, por exemplo, emesta publicação em "Habré" .Se o criador do sistema preferir retornar a resposta ao usuário o mais rápido possível, adiando a coordenação dos dados para mais tarde, então alguma outra solução será mais adequada para ele. No contexto do teorema de Brewer (teorema da CAP ), podemos dizer que as transações distribuídas são adequadas para casos em que a consistência dos dados é mais importante que a disponibilidade.Existem outras restrições práticas no uso de transações distribuídas por software. Por exemplo, foi estabelecido experimentalmente que transações distribuídas usando o protocolo OleTx não deveriam cruzar domínios de rede. De qualquer forma, longas tentativas de fazê-los funcionar não tiveram êxito. Além disso, foi revelado que a interação entre várias instâncias do Oracle Database (transações de banco de dados distribuídas) impõe sérias restrições à aplicabilidade das transações distribuídas por software (novamente, falha ao iniciar).Quais são as alternativas para transações distribuídas? Primeiro, devo dizer que será muito difícil prescindir de transações técnicas (normais, não distribuídas). É provável que existam processos no sistema que possam interromper temporariamente a integridade dos dados e será necessário, de alguma forma, supervisionar esses processos. Da mesma forma, em termos de área de assunto, pode surgir um conceito que inclua um processo implementado por um conjunto de processos em diferentes sistemas técnicos, que deve começar e terminar no campo de dados integrais.Passando para alternativas às transações distribuídas, podemos observar soluções baseadas em serviços de mensagens, como RabbitMQ e Apache Kafka. Em desta publicação em "Habré" visto quatro destas soluções:- , , ;
- , (Transaction Log Tailing);
- , ;
- (Event Sourcing).
Outra alternativa é o modelo Saga. Envolve uma cascata de subsistemas com suas transações locais. Após a conclusão do trabalho, cada sistema chama o seguinte (independentemente ou com a ajuda de um coordenador). Para cada transação, há uma transação de cancelamento correspondente e, em vez de transferir o controle, o subsistema pode iniciar o cancelamento das alterações feitas anteriormente pelos subsistemas anteriores. Em "Habré", existem alguns bons artigos sobre o modelo "Saga". Por exemplo, esta publicação fornece informações gerais sobre a manutenção dos princípios do ACID em microsserviços, e este artigo detalha um exemplo de implementação do modelo Saga com um coordenador.Em nossa empresa, alguns produtos usam com êxito transações distribuídas por software por meio do WCF, mas existem outras opções. Certa vez, quando tentamos fazer dos amigos um novo sistema com transações distribuídas, tivemos muitos problemas, incluindo uma colisão com as limitações descritas acima e problemas paralelos com a atualização da infraestrutura de software. Portanto, em condições de escassez de recursos para execução em outra decisão de capital, aplicamos as seguintes táticas. A parte chamada captura as alterações em qualquer caso, mas observa que elas estão em um estado de rascunho, portanto essas alterações ainda não afetam a operação do sistema chamado. Em seguida, o chamador, ao concluir seu trabalho por meio de uma transação distribuída, o DBMS ativa as alterações feitas pelo sistema chamado. Desta maneiraem vez de transações distribuídas por software, usamos transações DBMS distribuídas, que nesse caso se mostraram muito mais confiáveis.Então está no .NET Core?
No .NET Core (e até no .NET Standard), existem todos os tipos necessários para organizar transações e criar seu próprio gerenciador de recursos. Infelizmente, no .NET Core, as transações baseadas System.Transactions
têm uma limitação séria: elas funcionam apenas com o protocolo Lightweight. Por exemplo, se dois gerenciadores de recursos duráveis forem usados no código, em tempo de execução, o ambiente emitirá uma exceção assim que o segundo gerenciador for chamado.O fato é que eles tentam tornar o .NET Core independente do sistema operacional; portanto, o link para gerenciadores de transações como KTM e MSDTC é excluído, ou seja, eles são necessários para oferecer suporte a transações com as propriedades especificadas. É possível que a conexão dos gerenciadores de transações seja implementada na forma de plug-ins, mas até agora isso foi escrito com um forcado, para que você ainda não possa contar com o uso industrial de transações distribuídas no .NET Core.Por experiência, você pode verificar as diferenças nas transações distribuídas no .NET Framework e no .NET Core escrevendo o mesmo código, compilando e executando-o em plataformas diferentes.Um exemplo desse código que chama o SQL Server e o Oracle Database sequencialmente. private static void Main(string[] args) { using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { MsSqlServer(); Oracle(); scope.Complete(); } } private static void Oracle() { using (var conn = new Oracle.ManagedDataAccess.Client.OracleConnection("User Id=some_user;Password=some_password;Data Source=some_db")) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } } private static void MsSqlServer() { var builder = new System.Data.SqlClient.SqlConnectionStringBuilder { DataSource = "some_computer\\some_db", UserID = "some_user", Password = "some_password", InitialCatalog = "some_scheme", Enlist = true, }; using (var conn = new System.Data.SqlClient.SqlConnection(builder.ConnectionString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } }
Projetos prontos para compilação estão no GitHub .A execução do exemplo para o .NET Core falha. O local e o tipo da exceção lançada dependem da ordem da chamada DBMS, mas, em qualquer caso, essa exceção indica uma operação de transação inválida. A execução do exemplo para o .NET Framework será bem-sucedida se o MSDTC estiver em execução no momento; no entanto, na interface gráfica do MSDTC, é possível observar o registro de uma transação distribuída.Transações Distribuídas e WCF
O Windows Communication Foundation (WCF) é a estrutura .NET para organizar e chamar serviços de rede. Comparado às abordagens mais modernas da Web REST e ASP.NET, ela tem suas próprias vantagens e desvantagens. O WCF é muito amigo das transações do .NET e, no mundo do .NET Framework, é conveniente usá-lo para organizar transações distribuídas entre o cliente e o serviço.No .NET Core, essa tecnologia funciona apenas no lado do cliente, ou seja, você não pode criar um serviço, mas pode se referir apenas a um existente. Isso, no entanto, não é muito importante, porque, como mencionado acima, com transações distribuídas no .NET Core, as coisas não estão indo bem.Como o WCF funcionaPara leitores que não estão familiarizados com o WCF, aqui estão as informações mais curtas sobre o que essa tecnologia é na prática. Contexto - dois sistemas de informação chamados cliente e serviço. O cliente em tempo de execução acessa outro sistema de informações que suporta o serviço de interesse do cliente e requer que alguma operação seja executada. Em seguida, o gerenciamento é retornado ao cliente.Para criar um serviço no WCF, você geralmente precisa escrever uma interface que descreva o contrato para o serviço que está sendo criado e uma classe que implemente essa interface. A classe e a interface são marcadas com atributos especiais do WCF que os distinguem do restante dos tipos e especificam alguns detalhes do comportamento durante a descoberta e chamada do serviço. Esses tipos são agrupados em algo que funciona como um servidor (por exemplo, em uma DLL na qual o IIS está configurado) e são complementados por um arquivo de configuração (existem opções), onde são indicados detalhes da implementação do serviço. Após o início, o serviço pode ser acessado, por exemplo, no endereço de rede; no navegador da Internet, você pode ver os contratos que o serviço solicitado implementa.Um programador que deseja acessar um serviço WCF existente usa um utilitário de console ou uma interface gráfica incorporada ao ambiente de desenvolvimento para formar tipos em C # (ou em outro idioma suportado) que correspondem aos contratos de serviço no endereço do serviço. O arquivo com os tipos obtidos é incluído no projeto do aplicativo cliente e, depois disso, o programador usa os mesmos termos contidos na interface de serviço, aproveitando os benefícios do progresso (digitação estática). Além disso, o arquivo de configuração do cliente especifica as características técnicas do serviço chamado (ele também pode ser configurado no código, sem o arquivo de configuração).O WCF suporta vários tipos de transporte, criptografia e outros parâmetros técnicos mais sutis. A maioria deles está unida pelo conceito de "encadernação" (encadernação). Existem três parâmetros importantes para o serviço WCF:- o endereço em que está disponível;
- vinculativo
- contrato (interfaces).
Todos esses parâmetros são definidos nos arquivos de configuração de serviço e cliente.Em nossa empresa, o WCF (com e sem transações distribuídas) é amplamente utilizado em produtos implementados, no entanto, dadas as tendências da moda, seu uso em novos produtos ainda está em questão.Como iniciar transações distribuídas no WCFPara iniciar transações baseadas no WCF System.Transactions
, o programador precisa definir vários atributos no código, verifique se as ligações usadas suportam transações distribuídas, transactionFlow="true"
que elas sejam gravadas no cliente e no serviço e se um gerenciador de transações adequado esteja sendo executado em todos os computadores envolvidos (provavelmente , será MSDTC).Ligações de transação distribuída: NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding e WSFederationHttpBinding.O método (operação) da interface de serviço deve ser marcado com um atributo System.ServiceModel.TransactionFlowAttribute
. Em seguida, com determinados parâmetros de atributo e ao definir o parâmetro de TransactionScopeRequired
atributo , a System.ServiceModel.OperationBehaviorAttribute
transação será distribuída entre o cliente e o serviço. Além disso, por padrão, o serviço é considerado votado para confirmar a transação, a menos que uma exceção seja lançada no tempo de execução. Para alterar esse comportamento, você deve definir o valor do parâmetro do TransactionAutoComplete
atributo correspondente System.ServiceModel.OperationBehaviorAttribute
.O código para um serviço WCF simples que suporta transações distribuídas. [System.ServiceModel.ServiceContract] public interface IMyService { [System.ServiceModel.OperationContract] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] int DoSomething(string input); } public class MyService : IMyService { [System.ServiceModel.OperationBehavior(TransactionScopeRequired = true)] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] public int DoSomething(string input) { if (input == null) throw new System.ArgumentNullException(nameof(input)); return input.Length; } }
Obviamente, ele difere do código de serviço regular apenas no uso do atributo System.ServiceModel.TransactionFlow
e na configuração especial do atributo System.ServiceModel.OperationBehavior
. Exemplo de configuração para este serviço. <system.serviceModel> <services> <service name="WcfWithTransactionsExample.MyService" behaviorConfiguration="serviceBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="mainWsBinding" contract="WcfWithTransactionsExample.IMyService"/> <endpoint address="mex" contract="IMetadataExchange" binding="mexHttpBinding"/> </service> </services> <bindings> <wsHttpBinding> <binding name="mainWsBinding" maxReceivedMessageSize="209715200" maxBufferPoolSize="209715200" transactionFlow="true" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"> <security mode="None"/> <readerQuotas maxArrayLength="209715200" maxStringContentLength="209715200"/> </binding> </wsHttpBinding> </bindings> </system.serviceModel>
Observe que a ligação é do tipo WSHttpBinding e o atributo é usado transactionFlow="true"
. Seção TL; DR
As transações distribuídas incluem vários gerenciadores de recursos e todas as alterações devem ser confirmadas ou revertidas. Alguns DBMSs modernos implementam transações distribuídas que fornecem um mecanismo conveniente para conectar vários bancos de dados. As transações distribuídas por software (não implementadas no DBMS) podem incluir diferentes combinações de gerenciadores de recursos em computadores diferentes executando sistemas operacionais diferentes, mas possuem limitações que devem ser levadas em consideração antes de confiar nelas. Uma alternativa moderna às transações distribuídas são as soluções de mensagens. No .NET Core, transações distribuídas ainda não são suportadas.O WCF é uma das ferramentas padrão e comprovadas para criar e acessar serviços no mundo .NET, suportando vários tipos de transporte e criptografia. O WCF é muito amigo de transações distribuídas com base em System.Transactions
. A configuração de transações distribuídas para o WCF consiste em marcar o código com vários atributos e adicionar algumas palavras nos arquivos de configuração de serviço e cliente. Nem todas as ligações do WCF suportam transações distribuídas. Além disso, é claro que as transações no WCF têm as mesmas limitações que sem o uso do WCF. Até agora, a plataforma .NET Core permite acessar serviços no WCF, em vez de criá-los.Conclusão Berço
Esta postagem é uma visão geral dos conceitos básicos das transações de software .NET. Algumas conclusões sobre tendências nas transações de software podem ser encontradas nas seções sobre aplicabilidade e limitações dos assuntos discutidos e, em conclusão, são coletadas as principais teses da publicação. Suponho que eles possam ser usados como uma cábula ao considerar as transações de software como uma das opções para implementar um sistema técnico ou atualizar informações relevantes na memória.Transações (área de assunto, DBMS, software). Às vezes, os requisitos de domínio são formulados na forma de transações - operações que, começando no campo de dados integrais, após a conclusão (inclusive sem êxito) também devem entrar no campo de dados integrais (possivelmente já diferentes). Esses requisitos geralmente são implementados como transações do sistema. Um exemplo clássico de uma transação é a transferência de dinheiro entre duas contas, consistindo em duas operações indivisíveis - retirar dinheiro de uma conta e creditar para outra. Além das transações conhecidas implementadas pelo DBMS, também existem transações de software, por exemplo, no mundo .NET. Os gerenciadores de recursos são componentes de software que estão cientes da existência de tais transações e têm a capacidade de serem incluídos nelas, ou seja, confirmar ou reverter as alterações feitas.Os gerentes de recursos recebem instruções sobre como confirmar e reverter alterações do gerenciador de transações, que é a base da infraestruturaSystem.Transactions
.Gerentes de recursos duráveis e intermitentes. Os gerentes de recursos de longo prazo oferecem suporte à recuperação de dados após uma falha do sistema. Os drivers DBMS para .NET geralmente oferecem essa funcionalidade. Os gerenciadores de recursos intermitentes não oferecem suporte à recuperação de desastres. A memória transacional programática - uma maneira de gerenciar objetos na RAM - pode ser vista como um exemplo de um gerenciador de recursos inconstante.Transações e gerenciadores de recursos .NET. O programador .NET usa transações de software e cria seus próprios gerenciadores de recursos usando tipos do espaço para nomeSystem.Transactions
. Essa infraestrutura permite o uso de transações de vários aninhamentos e isolamentos (com limitações conhecidas). O uso de transações não é complicado e consiste em agrupar o código em um bloco using
com certas características. No entanto, os gerenciadores de recursos incluídos dessa maneira na transação devem manter a funcionalidade necessária para sua parte. Usar gerenciadores de recursos heterogêneos em uma transação ou usar um gerenciador de maneiras diferentes pode transformar automaticamente uma transação em distribuída.Transações distribuídas (DBMS, software).Uma transação distribuída abrange vários subsistemas, cujas mudanças devem ser sincronizadas, ou seja, elas são confirmadas todas juntas ou revertidas. As transações distribuídas são implementadas em alguns DBMSs modernos. As transações distribuídas por software (não são as implementadas pelo DBMS) impõem restrições adicionais aos processos e plataformas em interação. As transações distribuídas gradualmente saem de moda, dando lugar a soluções baseadas em serviços de mensagens. Para transformar uma transação comum em uma distribuída, o programador não precisa fazer nada: quando um gerenciador de recursos com certas características é incluído na transação no tempo de execução, o gerenciador de transações faz automaticamente o que for necessário. As transações regulares de software estão disponíveis no .NET Core e .NET Standard, e as transações distribuídas não estão disponíveis.Transações distribuídas através do WCF. O WCF é uma das ferramentas .NET padrão para criar e chamar serviços, que também suporta protocolos padronizados. Em outras palavras, os serviços WCF configurados de uma maneira específica podem ser acessados a partir de qualquer aplicativo, não apenas .NET ou Windows. Para criar uma transação distribuída sobre o WCF, é necessário marcar os tipos que compõem o serviço com atributos adicionais e fazer alterações mínimas nos arquivos de configuração do serviço e do cliente. Você não pode criar serviços WCF no .NET Core e .NET Standard, mas pode criar clientes WCF.Exemplo para verificar System.Transactions no GitHub