Note: If you read the book, you will find a number of features there that were not in the RTM version.SSB survived, and Microsoft introduced Service Broker External Activator (EA) in the SQL Server 2008 Feature Pack. It allows SSB to interact outside the local database. In theory, it sounds good, but in practice it is cumbersome and confused. We conducted several tests and quickly realized that he was not fulfilling what we needed. In addition, SSB did not give us the performance that was needed, so we had to invent something else.
Note: I wrote “almost” above, because in fact there are some limitations. In this context, these restrictions have little effect on what we are going to do.The principle of operation of SQLCLR is as follows: the code is compiled into a dll library, and then this library is registered by means of SQL Server:
CREATE ASSEMBLY [RabbitMQ.SqlServer] AUTHORIZATION rmq FROM 'F:\some_path\RabbitMQSqlClr4.dll' WITH PERMISSION_SET = UNSAFE; GO
CREATE ASSEMBLY
- creates an assembly with the given name (no matter what it should be).AUTHORIZATION
- Indicates the owner of the assembly. In this case, rmq is a predefined SQL Server role.FROM
- determines where the original assembly is located. In the FROM
, you can also specify the path in binary or UNC formats. The installation files for this project use a binary representation.WITH PERMISSION_SET
- sets permissions. UNSAFE
is the least UNSAFE
, and is needed in this case.Note: regardless of whether the role or the login name was used in theWhen the assembly is created, we make the .NET method wrappers in it:AUTHORIZATION
clause, the appdomain class must be created with the same name as when loading the assembly into the domain. It is recommended to separate assemblies with different names of appdomain classes so that when one assembly falls, the others do not collapse. However, if assemblies have dependencies on each other, they cannot be divided into different classes.
CREATE PROCEDURE rmq.pr_clr_PostRabbitMsg @EndpointID int, @Message nvarchar(max) AS EXTERNAL NAME [RabbitMQ.SqlServer].[RabbitMQSqlClr.RabbitMQSqlServer].[pr_clr_PostRabbitMsg]; GO
rmq.pr_clr_PostRabbitMsg
that takes two parameters; @EndpointID
and @Message
.RabbitMQ.SqlServer
, that is, the aggregate we created above in the code snippet 1 .RabbitMQSqlClr.RabbitMQSqlServer
pr_clr_PostRabbitMsg
.rmq.pr_clr_PostRabbitMsg
procedure is rmq.pr_clr_PostRabbitMsg
, the pr_clr_PostRabbitMsg
method will be called.Note: when creating a procedure, the assembly name is not case-sensitive, unlike the full name of the type and method. It is not necessary that the name of the procedure being created matches the name of the method. However, the final data types for the parameters must match.As I said earlier, we in Derivco need to send data outside of SQL Server, so we use SQLCLR and RabbitMQ (RMQ).
Note: In the following part of this article, RabbitMQ code snippets will appear, but without detailed explanations of what they do. If you are new to working with RabbitMQ, then I suggest looking at various RabbitMQ lessons to understand the purpose of the code. Hello World C # tutorial is a good start. One of the differences between textbooks and code examples is that the examples do not declare exchangers. It is assumed that they are predetermined.
Note: this is just an example to show how SQL Server can interact with RabbitMQ. This is NOT a finished product and not even a part of it. If this code breaks your brain you don’t have to blame me, for this is just an example.
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; } }
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; } }
Post
method is called from the pr_clr_PostRabbitMsg(int endPointId, string msgToPost)
method pr_clr_PostRabbitMsg(int endPointId, string msgToPost)
, which was presented as a procedure using the CREATE PROCEDURE
clause in code snippet 2: 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; } }
Encoding.UTF8.GetBytes
. In a production environment, the Encoding.UTF8.GetBytes
call should be replaced with serialization.src\SQL
folder. To install, follow these steps:01.create_database_and_role.sql
. He will create:RabbitMQTest
test database, where the assembly will be created.rmq
role to be assigned as the owner of the assemblyrmq
. In this scheme, various database objects are created.02.create_database_objects.sql
file. He will create:rmq.tb_RabbitSetting
, which will store the connection string to the local database.rmq.tb_RabbitEndpoint
table, in which one or several RabbitMQ
endpoints will be stored.03.create_localhost_connstring.sql
change the value of the @connString
variable to the correct connection string to the RabbitMQTest
database created in step 1 and run the script.amq.topic
. When your RabbitMQ broker is ready, edit the parameters of the rmq.pr_UpsertRabbitEndpoint
procedure, which is in the 04.upsert_rabbit_endpoint.sql
file: 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
RabbitMQ.Client
). From our assembly we will refer to the RabbitMQ client library. Since we planned to use CLR 2, then our build and RabbitMQ.Client
should be compiled based on .NET 3.5. There are problems.RabbitMQ.Client
compiled for CLR 4, so they cannot be used in our build. The latest version of client libraries for CLR 2 is compiled on .NET 3.4.3. But even if we try to deploy this assembly, we get an error message:RabbitMQ.Client
refers to an assembly that is not part of the CLR SQL Server. This is a WCF assembly, and this is one of the limitations in SQLCLR that I mentioned above: this particular assembly is intended for those types of tasks that are not allowed to be performed in SQL Server. The latest versions of RabbitMQ.Client
do not have these dependencies, so they can be used without any problems, except for the annoying requirements of the CLR 4 environment. What should I do?RabbitMQ.Client
I deleted the references to System.ServiceModel
and recompiled. I had to change a couple of lines of code using the System.ServiceModel
functionality, but these were minor changes.RabbitMQ.Client
releases use Task
'and which are not originally part of .NET 3.5.System.Threading.dll
for .NET 3.5 that includes Task
. I downloaded it, set up links and everything went! Here the main thing is that System.Threading.dll
should be installed along with the assembly.Note: TheTo install the necessary assemblies (RabbitMQ.Client
source, from which I compiled the version on .NET 3.5, is in my repository on the GitHub RabbitMQ Client 3.6.6 .NET 3.5 . The dll binary along with theSystem.Threading.dll
for .NET 3.5 also lies in thelib\NET3.5
repository (RabbitMQ-SqlServer) .
System.Threading
, RabbitMQ.Client
and RabbitMQ.SqlServer
), run the installation scripts from the src\sql
directory in the following order:05.51.System.Threading.sql2k5-12.sql
- System.Threading05.52.RabbitMQ.Client.sql2k5-12.sql
- RabbitMQ.Client05.53.RabbitMQ.SqlServer.sql2k5-12.sql
- RabbitMQ.SqlServerRabbitMQ.Client
, which can be obtained using NuGet . In my example, I use 4.1.1. RabbitMQ.Client
, which is also in the lib\NET4
repository (RabbitMQ-SqlServer) .src\sql
directory in the following order:05.141.RabbitMQ.Client.sql2k14+.sql
- RabbitMQ.Client05.142.RabbitMQ.SqlServer.sql2k14+.sql
- RabbitMQ.SqlServer06.create_sqlclr_procedures.sql
. It will create T-SQL procedures for the three .NET methods:rmq.pr_clr_InitialiseRabbitMq
calls pr_clr_InitialiseRabbitMq
. Used to load and initialize the RabbitMQ.SqlServer assembly.rmq.pr_clr_ReloadRabbitEndpoints
calls pr_clr_ReloadRabbitEndpoints
. Loads various RabbitMQ endpoints.rmq.pr_clr_PostRabbitMsg
calls pr_clr_PostRabbitMsg
. Used to send a message to RabbitMQ.rmq.pr_PostRabbitMsg
, which is applied to rmq.pr_clr_PostRabbitMsg
. This is a wrapper procedure that knows what to do with the data, handles exceptions, etc. In a production environment, we have several similar procedures that handle different types of messages. Read more about this below.rmq.pr_PostRabbitMsg
or rmq.pr_clr_PostRabbitMsg
to send messages to RabbitMQ, passing the endpoint identifier and the message itself as a string. All this, of course, cool, but I would like to see how it will work in reality.rmq.pr_PostRabbitMsg
. Below is a very simplified example of such a procedure: 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
07.create_processing_procedure.sql
from the src\SQL
directory.rmq.tb_RabbitEndpoint
.99.test_send_message.sql
. EXEC rmq.pr_clr_InitialiseRabbitMq;
EXEC dbo.pr_SomeProcessingStuff @id = 101
Source: https://habr.com/ru/post/419457/
All Articles