RabbitMQ - SQL Server

Há uma ou duas semanas, vi uma mensagem no fórum RabbitMQ Users sobre como configurar o envio de mensagens do SQL Server para o RabbitMQ. Como trabalhamos em estreita colaboração com a Derivco , deixei algumas sugestões lá e também disse que estou escrevendo um blog sobre como fazer isso. Parte da minha mensagem não era inteiramente verdadeira - pelo menos até aquele momento (desculpe, irmão, ele estava muito ocupado).

Coisa impressionante, este é o seu SQL Server . É muito fácil colocar informações em um banco de dados. Recuperar dados de um banco de dados usando uma consulta é igualmente fácil. Mas obter os dados atualizados ou colados já é um pouco mais difícil. Pense em eventos em tempo real; uma compra é feita - alguém precisa ser notificado sobre isso no momento exato em que isso aconteceu. Talvez alguém diga que esses dados não devem ser retirados do banco de dados, mas de algum outro lugar. Obviamente, esse é o caso, mas muitas vezes simplesmente não temos escolha.

Tivemos uma tarefa: enviar eventos do banco de dados para fora para processamento adicional, e a pergunta era - como fazer isso?

SQL Server e comunicações externas


Durante a existência do SQL Server, houve várias tentativas de organizar as comunicações fora do banco de dados; Serviços de Notificação do SQL Server (NS), que apareceram no SQL Server 2000 e, posteriormente, no SQL Server 2005, o SQL Server Service Broker (SSB) apareceu. Eu os descrevi em meu livro A First Look at SQL Server 2005 for Developers , juntamente com Bob Boshemen e Dan Sullivan. O NS apareceu no SQL Server 2000, como eu disse, e foi redesenhado na versão beta do SQL Server 2005. No entanto, o NS foi completamente excluído da versão pronta para venda (RTM) do SQL Server 2005.
Nota: Se você ler o livro, encontrará vários recursos que não estavam na versão RTM.
O SSB sobreviveu e a Microsoft lançou o Service Broker External Activator (EA) em seu SQL Server 2008 Feature Pack. Permite através do SSB interagir fora do banco de dados local. Teoricamente, parece bom, mas na prática - é complicado e confuso. Fizemos alguns testes e rapidamente percebemos que não estava fazendo o que precisávamos. Além disso, o SSB não nos deu o desempenho necessário, por isso tivemos que inventar outra coisa.

SQLCLR


O que descobrimos como resultado foi baseado na tecnologia SQLCLR. SQLCLR é uma plataforma .NET integrada ao núcleo do SQL Server e pode ser usada para executar o código .NET dentro do kernel. Como executamos o código .NET, somos capazes de fazer quase tudo como em um aplicativo .NET comum.
Nota: eu escrevi "quase" acima, porque na verdade existem algumas limitações. Nesse contexto, essas restrições quase não têm efeito sobre o que vamos fazer.
O princípio de operação do SQLCLR é o seguinte: o código é compilado em uma biblioteca DLL e, em seguida, essa biblioteca é registrada usando as ferramentas do SQL Server:

Construir Montagem

CREATE ASSEMBLY [RabbitMQ.SqlServer] AUTHORIZATION rmq FROM 'F:\some_path\RabbitMQSqlClr4.dll' WITH PERMISSION_SET = UNSAFE; GO 

Fragmento de código 1: Criando uma montagem ao longo de um caminho absoluto

O código executa as seguintes ações:

  • CREATE ASSEMBLY - Cria uma montagem com o nome fornecido (não importa qual deve ser).
  • AUTHORIZATION - Indica o proprietário da montagem. Nesse caso, rmq é uma função predefinida do SQL Server.
  • FROM - Determina onde a montagem original está localizada. Na FROM , você também pode especificar o caminho nos formatos binário ou UNC. Os arquivos de instalação para este projeto usam uma representação binária.
  • WITH PERMISSION_SET - Define permissões. UNSAFE é o menos rigoroso e é necessário neste caso.

Nota: independentemente da função ou login usado na cláusula AUTHORIZATION , a classe appdomain deve ser criada com o mesmo nome que ao carregar o assembly no domínio. É recomendável separar assemblies com nomes diferentes de classes de domínio de aplicativo para que, quando um assembly falhar, o restante não caia. No entanto, se as montagens dependem uma da outra, elas não podem ser divididas em classes diferentes.
Quando o assembly é criado, criamos wrappers de métodos .NET nele:

 CREATE PROCEDURE rmq.pr_clr_PostRabbitMsg @EndpointID int, @Message nvarchar(max) AS EXTERNAL NAME [RabbitMQ.SqlServer].[RabbitMQSqlClr.RabbitMQSqlServer].[pr_clr_PostRabbitMsg]; GO 

Snippet de código 2: Wrapper do método .NET

O código executa as seguintes ações:

  • Cria um procedimento armazenado T-SQL chamado rmq.pr_clr_PostRabbitMsg que usa dois parâmetros; @EndpointID e @Message .
  • Em vez do corpo do procedimento, é usada uma fonte externa, que consiste em:
    • Um assembly chamado RabbitMQ.SqlServer , ou seja, o agregado que criamos acima no snippet de código 1 .
    • Tipo completo (namespace e classe): RabbitMQSqlClr.RabbitMQSqlServer
    • O método do namespace e da classe acima é: pr_clr_PostRabbitMsg .

Quando rmq.pr_clr_PostRabbitMsg , o método pr_clr_PostRabbitMsg será chamado.
Nota: ao criar um procedimento, o nome da montagem não diferencia maiúsculas de minúsculas, ao contrário do nome completo do tipo e método. Não é necessário que o nome do procedimento que está sendo criado corresponda ao nome do método. No entanto, os tipos de dados finais para os parâmetros devem corresponder.
Como eu disse anteriormente, nós da Derivco precisamos enviar dados para fora do SQL Server, então usamos SQLCLR e RabbitMQ (RMQ).

Rabbitmq


O RMQ é um intermediário de mensagens de código aberto que implementa o AMQP (Advanced Message Queuing Protocol) e é gravado em Erlang.

Como o RMQ é um intermediário de mensagens, as bibliotecas do cliente AMQP são necessárias para conectar-se a ele. O aplicativo se refere às bibliotecas clientes e, com a ajuda deles, abre uma conexão e envia mensagens - como, por exemplo, há uma chamada através do ADO.NET para o SQL Server. Mas, diferentemente do ADO.NET, onde, provavelmente, a conexão é aberta toda vez que você acessa o banco de dados, aqui a conexão permanece aberta por todo o período do aplicativo.

Portanto, para poder interagir do banco de dados com o RabbitMQ, precisamos do aplicativo e da biblioteca do cliente .NET para o RabbitMQ.
Nota: na próxima parte deste artigo, fragmentos de código RabbitMQ serão encontrados, mas sem explicações detalhadas do que eles fazem. Se você é novo no trabalho com o RabbitMQ, sugiro dar uma olhada nos vários tutoriais do RabbitMQ para entender o objetivo do código. O tutorial Hello World C # é um bom começo. Uma das diferenças entre livros didáticos e exemplos de código é que os trocadores não são declarados nos exemplos. Eles devem ser predefinidos.

RabbitMQ.SqlServer


RabbitMQ.SqlServer é um assembly que usa a biblioteca cliente .NET para RabbitMQ e fornece a capacidade de enviar mensagens do banco de dados para um ou mais pontos de extremidade RabbitMQ (VHosts e trocadores). O código pode ser baixado / bifurcado no meu repositório RabbitMQ-SqlServer no GitHub. Ele contém fontes de montagem e arquivos de instalação (ou seja, você não precisa compilá-los).
Nota: este é apenas um exemplo para mostrar como o SQL Server pode interagir com o RabbitMQ. Este não é um produto acabado ou mesmo parte dele. Se esse código quebrar seu cérebro - não me culpe, porque este é apenas um exemplo.

Funcionalidade


Quando o assembly é carregado, ou quando sua inicialização é explicitamente chamada, ou quando é chamada indiretamente, no momento em que o procedimento do wrapper é chamado, o assembly carrega a cadeia de conexão no banco de dados local em que foi instalado, bem como nos terminais do RabbitMQ aos quais se conectam:

Ligação

 internal bool InternalConnect() { try { connFactory = new ConnectionFactory(); connFactory.Uri = connString; connFactory.AutomaticRecoveryEnabled = true; connFactory.TopologyRecoveryEnabled = true; RabbitConn = connFactory.CreateConnection(); for (int x = 0; x < channels; x++) { var ch = RabbitConn.CreateModel(); rabbitChannels.Push(ch); } return true; } catch(Exception ex) { return false; } } 

Fragmento de código 3: conectar-se ao terminal

Ao mesmo tempo, parte da conexão com o terminal também cria IModels na conexão e eles são usados ​​ao enviar (adicionando à fila) mensagens:

Envio de mensagem

 internal bool Post(string exchange, byte[] msg, string topic) { IModel value = null; int channelTryCount = 0; try { while ((!rabbitChannels.TryPop(out value)) && channelTryCount < 100) { channelTryCount += 1; Thread.Sleep(50); } if (channelTryCount == 100) { var errMsg = $"Channel pool blocked when trying to post message to Exchange: {exchange}."; throw new ApplicationException(errMsg); } value.BasicPublish(exchange, topic, false, null, msg); rabbitChannels.Push(value); return true; } catch (Exception ex) { if (value != null) { _rabbitChannels.Push(value); } throw; } } 

O método Post é chamado a partir do método pr_clr_PostRabbitMsg(int endPointId, string msgToPost) , que foi apresentado como um procedimento usando a cláusula CREATE PROCEDURE no fragmento de código 2:

Método de pós-chamada

 public static void pr_clr_PostRabbitMsg(int endPointId, string msgToPost) { try { if(endPointId == 0) { throw new ApplicationException("EndpointId cannot be 0"); } if (!isInitialised) { pr_clr_InitialiseRabbitMq(); } var msg = Encoding.UTF8.GetBytes(msgToPost); if (endPointId == -1) { foreach (var rep in remoteEndpoints) { var exch = rep.Value.Exchange; var topic = rep.Value.RoutingKey; foreach (var pub in rabbitPublishers.Values) { pub.Post(exch, msg, topic); } } } else { RabbitPublisher pub; if (rabbitPublishers.TryGetValue(endPointId, out pub)) { pub.Post(remoteEndpoints[endPointId].Exchange, msg, remoteEndpoints[endPointId].RoutingKey); } else { throw new ApplicationException($"EndpointId: {endPointId}, does not exist"); } } } catch { throw; } } 

Fragmento de código 5: Representando um método como um procedimento

Quando o método é executado, supõe-se que o chamador envie o identificador do terminal para o qual a mensagem deve ser transmitida e, de fato, a própria mensagem. Se o valor -1 for passado como o identificador do nó de extremidade, iteramos sobre todos os pontos e enviamos uma mensagem para cada um deles. A mensagem vem na forma de uma sequência a partir da qual obtemos bytes usando Encoding.UTF8.GetBytes . Em um ambiente de produção, a chamada Encoding.UTF8.GetBytes deve ser substituída pela serialização.

Instalação


Para instalar e executar o exemplo, você precisa de todos os arquivos na pasta src\SQL . Para instalar, siga estes passos:

  • Execute o script 01.create_database_and_role.sql . Ele criará:
    • Banco de dados de teste RabbitMQTest que o assembly será criado.
    • rmq a ser designada como proprietário da montagem
    • esquema, que também será chamado rmq . Neste diagrama, vários objetos de banco de dados são criados.

  • Execute o arquivo 02.create_database_objects.sql . Ele criará:

    • a tabela rmq.tb_RabbitSetting , que armazenará a cadeia de conexão no banco de dados local.
    • A tabela rmq.tb_RabbitEndpoint , na qual um ou mais pontos de extremidade do RabbitMQ serão armazenados.

  • No arquivo 03.create_localhost_connstring.sql altere o valor da variável @connString para a cadeia de conexão correta do banco de dados RabbitMQTest criado na etapa 1 e execute o script.

Antes de continuar, você deve ter uma instância em execução do broker RabbitMQ e do VHost (por padrão, o VHost é representado como /). Como regra, temos vários VHost, apenas para isolamento. Esse host também precisa de um trocador, no exemplo usamos amq.topic . Quando seu broker RabbitMQ estiver pronto, edite os rmq.pr_UpsertRabbitEndpoint procedimento rmq.pr_UpsertRabbitEndpoint , que estão localizados no arquivo 04.upsert_rabbit_endpoint.sql :

Endpoint RabbitMQ

 EXEC rmq.pr_UpsertRabbitEndpoint @Alias = 'rabbitEp1', @ServerName = 'RabbitServer', @Port = 5672, @VHost = 'testHost', @LoginName = 'rabbitAdmin', @LoginPassword = 'some_secret_password', @Exchange = 'amq.topic', @RoutingKey = '#', @ConnectionChannels = 5, @IsEnabled = 1 

Fragmento de código 6: Criando um nó de extremidade no RabbitMQ

Neste ponto, é hora de implantar assemblies. Existem diferenças nas opções de implantação para versões do SQL Server anteriores ao SQL Server 2014 (2005, 2008, 2008R2, 2012) e para 2014 e posteriores. A diferença está na versão suportada do CLR. Antes do SQL Server 2014, a plataforma .NET estava em execução no CLR versão 2 e, no SQL Server 2014 e superior, a versão 4 era usada.

SQL Server 2005 - 2012


Vamos começar com as versões do SQL Server executadas no CLR 2, pois elas têm suas próprias características. Precisamos implantar o assembly criado e, ao mesmo tempo, implantar a biblioteca .NET do cliente RabbitMQ.Client ( RabbitMQ.Client ). Da nossa montagem, nos referiremos à biblioteca do cliente RabbitMQ. Porque Como planejamos usar o CLR 2, nosso assembly e o RabbitMQ.Client devem ser compilados com base no .NET 3.5. Há problemas

Todas as versões mais recentes da biblioteca RabbitMQ.Client são compiladas para o ambiente CLR 4, portanto, não podem ser usadas em nosso assembly. A versão mais recente das bibliotecas cliente para CLR 2 é compilada no .NET 3.4.3. Mas, mesmo se tentarmos implantar esse assembly, receberemos uma mensagem de erro:


Figura 1: Conjunto System.ServiceModel ausente

Esta versão do RabbitMQ.Client refere-se a um assembly que não faz parte do SQL Server CLR. Este é um assembly do WCF e esta é uma das limitações do SQLCLR que eu mencionei acima: esse assembly específico é para tipos de tarefas que não podem ser executadas no SQL Server. As versões recentes do RabbitMQ.Client não possuem essas dependências, portanto podem ser usadas sem problemas, exceto pelos requisitos irritantes do CLR 4. O que devo fazer?

Como você sabe, o RabbitMQ é de código aberto, mas somos desenvolvedores, certo? ;) Então vamos recompilar! Na versão anterior às versões mais recentes (ou seja, versão <3.5.0) do RabbitMQ.Client excluí os links para System.ServiceModel e recompilei. Eu tive que alterar algumas linhas de código usando a funcionalidade System.ServiceModel , mas essas foram pequenas alterações.

Neste exemplo, eu não usei a versão 3.4.3 do cliente, mas peguei a versão estável 3.6.6 e recompilei usando o .NET 3.5 (CLR 2). Quase funcionou :), exceto que as versões posteriores do RabbitMQ.Client usam Task 'e que não fazem parte originalmente do .NET 3.5.

Felizmente, existe uma versão do System.Threading.dll para .NET 3.5 que inclui a Task . Eu baixei, configurei os links e tudo correu! Aqui, o truque principal é que o System.Threading.dll deve ser instalado com o assembly.
Nota: a fonte do RabbitMQ.Client , da qual compilei uma versão do .NET 3.5, está no meu repositório no GitHub RabbitMQ Client 3.6.6 .NET 3.5 . A DLL de binário, juntamente com System.Threading.dll para .NET 3.5, também está no lib\NET3.5 repositório (RabbitMQ-SqlServer) .
Para instalar os assemblies necessários ( System.Threading , RabbitMQ.Client e RabbitMQ.SqlServer ), execute os scripts de instalação no diretório src\sql na seguinte ordem:

  1. 05.51.System.Threading.sql2k5-12.sql - System.Threading
  2. 05.52.RabbitMQ.Client.sql2k5-12.sql - RabbitMQ.Client
  3. 05.53.RabbitMQ.SqlServer.sql2k5-12.sql - RabbitMQ.SqlServer

SQL Server 2014 ou superior


No SQL Server 2014 e posterior, o assembly é compilado no .NET 4.XX (meu exemplo está no 4.5.2) e você pode fazer referência a qualquer uma das versões mais recentes do RabbitMQ.Client , que podem ser obtidas usando o NuGet . No meu exemplo, estou usando o 4.1.1. RabbitMQ.Client , que também está no lib\NET4 repositório (RabbitMQ-SqlServer) .

Para instalar, execute os scripts no diretório src\sql na seguinte ordem:

  1. 05.141.RabbitMQ.Client.sql2k14+.sql - RabbitMQ.Client
  2. 05.142.RabbitMQ.SqlServer.sql2k14+.sql - RabbitMQ.SqlServer

Wrappers de método SQL


Para criar procedimentos que serão usados ​​em nossa montagem (3.5 ou 4), execute o script 06.create_sqlclr_procedures.sql . Ele criará procedimentos T-SQL para três métodos .NET:

  • rmq.pr_clr_InitialiseRabbitMq chama pr_clr_InitialiseRabbitMq . Usado para carregar e inicializar o assembly RabbitMQ.SqlServer.
  • rmq.pr_clr_ReloadRabbitEndpoints chama pr_clr_ReloadRabbitEndpoints . Carrega vários pontos de extremidade do RabbitMQ.
  • rmq.pr_clr_PostRabbitMsg chama pr_clr_PostRabbitMsg . Usado para enviar mensagens para RabbitMQ.

O script também cria um procedimento T-SQL simples - rmq.pr_PostRabbitMsg , que se aplica a rmq.pr_clr_PostRabbitMsg . Este é um procedimento de invólucro que sabe o que fazer com dados, manipula exceções etc. Em um ambiente de produção, temos vários procedimentos semelhantes que processam vários tipos de mensagens. Leia mais sobre isso abaixo.

Use


De todas as rmq.pr_PostRabbitMsg acima, é claro que, para enviar mensagens ao RabbitMQ, chamamos rmq.pr_PostRabbitMsg ou rmq.pr_clr_PostRabbitMsg , transmitindo nos parâmetros o identificador do terminal e a própria mensagem como uma string. Tudo isso, é claro, é legal, mas eu gostaria de ver como isso funcionará na realidade.

O que fazemos nos ambientes de produção é que, nos procedimentos armazenados que processam os dados que devem ser enviados ao RabbitMQ, coletamos os dados a serem enviados e, no bloco de conexão, chamamos um procedimento como rmq.pr_PostRabbitMsg . A seguir, é apresentado um exemplo muito simplificado de tal procedimento:

Procedimento de processamento de dados

 ALTER PROCEDURE dbo.pr_SomeProcessingStuff @id int AS BEGIN SET NOCOUNT ON; BEGIN TRY --     DECLARE @endPointId int; --    DECLARE @msg nvarchar(max) = '{' --        SET @msg = @msg + '"Id":' + CAST(@id AS varchar(10)) + ',' --  -  SET @msg = @msg + '"FName":"Hello",'; SET @msg = @msg + '"LName":"World"'; SET @msg = @msg + '}'; -- -  --     -,  -  SELECT @endPointId = 1; --    --     EXEC rmq.pr_PostRabbitMsg @Message = @msg, @EndpointID = @endPointId; END TRY BEGIN CATCH DECLARE @errMsg nvarchar(max); DECLARE @errLine int; SELECT @errMsg = ERROR_MESSAGE(), @errLine = ERROR_LINE(); RAISERROR('Error: %s at line: %d', 16, -1, @errMsg, @errLine); END CATCH END 

No fragmento de código 7, vemos como os dados necessários são capturados e processados ​​no procedimento e enviados após o processamento. Para usar este procedimento, execute o script 07.create_processing_procedure.sql no diretório src\SQL .

Vamos rodar tudo


Neste ponto, você deve estar preparado para enviar algumas mensagens. Antes do teste, verifique se você possui filas no RabbitMQ conectadas ao trocador de terminal em rmq.tb_RabbitEndpoint .

Portanto, para começar, você precisa fazer o seguinte:
Abra o arquivo 99.test_send_message.sql .
Executar

 EXEC rmq.pr_clr_InitialiseRabbitMq; 

para inicializar a montagem e carregar os pontos de extremidade do RabbitMQ. Esta não é uma etapa necessária, mas é recomendável pré-carregar a montagem após criar ou modificar.

Executar

 EXEC dbo.pr_SomeProcessingStuff @id = 101 

(você pode usar qualquer outro identificador que desejar).

Se tudo funcionou sem erros, uma mensagem deve aparecer na fila do RabbitMQ! Então você usou o SQLCLR para enviar uma mensagem ao RabbitMQ.

Parabéns!

Source: https://habr.com/ru/post/pt419457/


All Articles