SQL Service Broker example not working - sql

I have two databases on the same instance.
One called ICMS, one called CarePay_DEV1
When a change happens in ICMS (Source), it needs to send a message to CarePay_Dev1 (Destination).
I am new to Broker Services, and am trying to make a message go to the queue. Once that works, I will hopefully get the data into a table in the destination, and that will then be processed by .Net code. But I just want something to appear in the destination first.
So, step 1: I enable the service on the two databases
-- Enable Broker on CarePay
ALTER DATABASE CarePay_Dev1
SET ENABLE_BROKER;
-- Enable Broker on Source
ALTER DATABASE ICMS_TRN
SET ENABLE_BROKER;
Step 2: Create the message type on the source and destination.
-- Create Message Type on Receiver:
USE CarePay_DEV1
GO
CREATE MESSAGE TYPE [IcmsCarePayMessage]
VALIDATION=WELL_FORMED_XML;
-- Create Message Type on Sender:
USE ICMS_TRN
GO
CREATE MESSAGE TYPE [IcmsCarePayMessage]
VALIDATION=WELL_FORMED_XML;
I then create the Contacts, on both databases:
-- Create Message Type on Receiver:
USE CarePay_DEV1
GO
CREATE MESSAGE TYPE [IcmsCarePayMessage]
VALIDATION=WELL_FORMED_XML;
-- Create Message Type on Sender:
USE ICMS_TRN
GO
CREATE MESSAGE TYPE [IcmsCarePayMessage]
VALIDATION=WELL_FORMED_XML;
I then create the message queues on both databases:
-- CREATE Sending Messagw Queue
USE ICMS_TRN
GO
CREATE QUEUE CarePayQueue
-- CREATE Receiving Messagw Queue
USE CarePay_Dev1
GO
CREATE QUEUE CarePayQueue
And finally, I create the services on both databases:
-- Create the message services
USE ICMS_TRN
GO
CREATE SERVICE [CarePayService]
ON QUEUE CarePayQueue
USE CarePay_DEV1
GO
CREATE SERVICE [CarePayService]
ON QUEUE CarePayQueue
Now, the queues should be ready, so then I try and send something from the source to the destination:
-- SEND THE MESSAGE!
USE ICMS_TRN
GO
DECLARE #InitDlgHandle UNIQUEIDENTIFIER
DECLARE #RequestMessage VARCHAR(1000)
BEGIN TRAN
BEGIN DIALOG #InitDlgHandle
FROM SERVICE [CarePayService]
TO SERVICE 'CarePayService'
ON CONTRACT [IcmsCarePayContract]
SELECT #RequestMessage = N'<Message>The eagle has landed!</Message>';
SEND ON CONVERSATION #InitDlgHandle
MESSAGE TYPE [IcmsCarePayMessage] (#RequestMessage)
COMMIT TRAN
I get:
Command(s) completed successfully.
But then when I try select from the destination queue, it's empty.
/****** Script for SelectTopNRows command from SSMS ******/
SELECT TOP 1000 *, casted_message_body =
CASE message_type_name WHEN 'X'
THEN CAST(message_body AS NVARCHAR(MAX))
ELSE message_body
END
FROM [CarePay_DEV1].[dbo].[CarePayQueue] WITH(NOLOCK)
Can anyone spot the issue? I can't see where I tell the destination which database to send the message to - which could be part of the issue?

I highly recommend you read Adam Machanic's Service Broker Advanced Basics Workbench, specifically the section entitled "Routing and Cross-Database Messaging".
In addition, for future troubleshooting you may want to use SSBDiagnose or also read through Remus Rusanu's numerous articles on the topic

I think the initiator service sent a message to yourself. Try to change the name of destination (terget) service.

Related

Triggering actions on SQL Login Pw Change

I am attempting to implement the event notification code from https://www.mssqltips.com/sqlservertip/2708/tracking-login-password-changes-in-sql-server/
I am not seeing any results in the PasswordChangeLog table when I change the password. there should be an entry in this table for every time I change the password.
There are no errors that I can see. I will work on this again tomorrow on a different server and see if I can get more from the error log (this server is polluted)
Here is the code I am running, it's mostly a cut & paste from Aaron's post. I have reviewed several times...am I missing something? My one significant change is that I added a trigger on the PasswordChangeLog to run a stored procedure whenever an entry is detected.
--TriggerEventOnPasswordChange
USE msdb
GO
IF OBJECT_ID('dbo.PasswordChangeLog', 'U') IS NOT NULL
DROP TABLE dbo.PasswordChangeLog;
CREATE TABLE dbo.PasswordChangeLog
(
LoginName SYSNAME,
EventTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
GO
CREATE TRIGGER RunSyncer
ON dbo.PasswordChangeLog
AFTER INSERT AS
BEGIN
SET NOCOUNT ON
EXEC sp_Syncer
END
GO
ALTER TABLE dbo.PasswordChangeLog ENABLE TRIGGER RunSyncer
GO
CREATE QUEUE PasswordChangeQueue;
GO
CREATE SERVICE PasswordChangeService ON QUEUE PasswordChangeQueue
([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
GO
CREATE EVENT NOTIFICATION PasswordChangeNotification
ON SERVER WITH FAN_IN
FOR AUDIT_LOGIN_CHANGE_PASSWORD_EVENT
TO SERVICE 'PasswordChangeService', 'current database';
GO
CREATE PROCEDURE dbo.LogPasswordChange
WITH EXECUTE AS OWNER
AS
BEGIN
SET NOCOUNT ON;
DECLARE #message_body XML;
WHILE (1 = 1)
BEGIN
WAITFOR
(
RECEIVE TOP(1) #message_body = message_body
FROM dbo.PasswordChangeQueue
), TIMEOUT 1000;
IF (##ROWCOUNT = 1)
BEGIN
INSERT dbo.PasswordChangeLog(LoginName)
SELECT #message_body.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname');
END
END
END
GO
ALTER QUEUE PasswordChangeQueue
WITH ACTIVATION
(
STATUS = ON,
PROCEDURE_NAME = dbo.LogPasswordChange,
MAX_QUEUE_READERS = 1,
EXECUTE AS OWNER
);
GO
Event Notifications would typically involve a MonitorED server, and a MonitorING server (collection server).
If what you posted is for the server to be monitored, you're missing a few things. The basic outine would be to:
Create an ENDPOINT
Grant CONNECT to the endpoint to specific group
or PUBLIC
Create a ROUTE, referencing the name of the collection
server, and the GUID of the database that will receive the messages
(where your Activation proc resides).
Something like this:
CREATE ROUTE [My_CollectionRoute]
WITH
SERVICE_NAME = 'My_Service'
,BROKER_INSTANCE = <SOME GUID>
,ADDRESS = 'TCP://COLLECTION-SERVER:4022'
Once you have the collection server configured properly, you would create the Event Notification session on the monitorED server.
On the collection server, you need to:
Create an ENDPOINT
Grant CONNECT to the endpoint to specific group or PUBLIC
Create a ROUTE, referencing the name of the monitored server, and
the GUID of the MSDB database
Optionally create tables to persist the events you collect
Create the Activation proc
Create the Queue, which references the Activation proc
Create a Service based on the Queue
Grant send on the service to user or PUBLIC
Hope this helps --

SQL Server Service Broker Deadlock

I'm investigating a process that I did not build. It uses the service broker to create a queue of contacts that then need an action against them.
There is then a handler that receives 10k records and passes them to a stored procedure to process.
What happens if that final process fails for a deadlock with no error handling? Do these go back into the queue? If not what would I need to do to get them to go back into the queue?
Service broker queues can be accessed from within a transaction. So if you do something like this in your code (the below is pseudo-code; actual robust service broker code is a little beyond the scope of your question):
begin tran
receive top(10000) message_body
into #table
from dbo.yourQueue;
while(1=1)
begin
select top(1) #message = message
from #table;
if (#message is null)
break;
exec dbo.processMessage #message;
end
commit tran
… then you're set. What I'm saying is that as long as you're doing your receive and processing in the same transaction, any failure (deadlocks included) will rollback the transaction and put the messages back on the queue. Make sure you read up on poison message handling, though! If you get too many rollbacks, SQL will assume that there's an un-processable message and shut down the queue. That's a bad day when that happens.

MSSQLServer Logging Many Missing Stored Procedure Messages

We deleted some stored procedures related to Query notification service since we are not using them anymore. After deleting the procs we are seeing the error log getting filled with messages like below.
How do we stop such errors.
The activated proc '[dbo].[SqlQueryNotificationStoredProcedure-1c5b775e-e036-4181-8336-ba86d97d763d]' running on queue 'DATABASE.dbo.SqlQueryNotificationService-1c5b775e-e036-4181-8336-ba86d97d763d' output the following: 'Could not find stored procedure 'dbo.SqlQueryNotificationStoredProcedure-1c5b775e-e036-4181-8336-ba86d97d763d'.'
Try this. Run to get the name of the queue
select * from sys.service_queues
Then run
DROP QUEUE queueName

How do I execute a stored procedure whenever an item is added to a service broker queue?

I am using SQL Service Broker. I have a queue that another process is adding items to. I want to run a stored procedure whenever items are added to the queue. The procedure will receive the top item from the queue and use its information within the stored procedure. What is the correct syntax for doing something like this? Do I use a typical SQL Trigger or is there something special to use when working with Service Broker queues?
A triggered stored procedure can be specified as part of the queue definition.
See the documentation for CREATE QUEUE - specifically the ACTIVATION clause.
An example from the documentation:
The following example creates a queue that is available to receive
messages. The queue starts the stored procedure expense_procedure when
a message enters the queue. The stored procedure executes as the user
ExpenseUser. The queue starts a maximum of 5 instances of the stored
procedure.
CREATE QUEUE ExpenseQueue
WITH STATUS=ON,
ACTIVATION (
PROCEDURE_NAME = expense_procedure,
MAX_QUEUE_READERS = 5,
EXECUTE AS 'ExpenseUser' ) ;

Sql Server Service Broker

Currently we are using service broker to send the messages back and forth, which is working fine. But we wanted to group those messages by using the RELATED_CONVERSATION_GROUP. We wanted to use our own database persisted uuid as a RELATED_CONVERSATION_GROUP = #uuid from our database, but even though we use the same uuid every time the conversion_group_id comes different each time we receive the queue.
Do you guys know what is wrong with way i am creating the broker or the receive call, i have provided both the broker creation code and the receive call code below. Thanks
below is the code "Service Broker creation code"
CREATE PROCEDURE dbo.OnDataInserted
#EntityType NVARCHAR(100),
#MessageID BIGINT,
#uuid uniqueidentifier,
#message_body nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #conversation UNIQUEIDENTIFIER
BEGIN DIALOG CONVERSATION #conversation
FROM SERVICE DataInsertSndService
TO SERVICE 'DataInsertRcvService'
ON CONTRACT DataInsertContract
WITH RELATED_CONVERSATION_GROUP = #uuid;
SEND ON CONVERSATION #conversation
MESSAGE TYPE DataInserted
(CAST(#message_body))
below is the code "Receive code"
WHILE 0 < ##TRANCOUNT ROLLBACK; SET NOCOUNT ON
BEGIN TRANSACTION;
DECLARE
#cID as uniqueidentifier,
#conversationHandle as uniqueidentifier,
#conversationGroupId as uniqueidentifier,
#tempConversationGroupId as uniqueidentifier,
#message_body VARBINARY(MAX)
RAISERROR ('Awaiting Message ...', 16, 1) WITH NOWAIT
;WAITFOR (RECEIVE TOP (1)
#cID = Substring(CAST(message_body as nvarchar(max)),4,36),
#conversationHandle = [conversation_handle],
#conversationGroupId = [conversation_group_id],
#message_body = message_body
FROM DataInsertRcvQueue)
RAISERROR ('Message Received', 16, 1) WITH NOWAIT
Select #tempConversationGroupId = conversationGroupID from ConversationGroupMapper where cID = #cID;
declare #temp as nvarchar(max);
Set #temp = CAST(#tempConversationGroupId as nvarchar(max));
if #temp <> ''
BEGIN
MOVE CONVERSATION #conversationHandle TO #tempConversationGroupId;
RAISERROR ('Moved to Existing Conversation Group' , 16, 1) WITH NOWAIT
END
else
BEGIN
insert into ConversationGroupMapper values (#cID,#conversationGroupId);
RAISERROR ('New Conversation Group' , 16, 1) WITH NOWAIT
END
WAITFOR DELAY '000:00:10'
COMMIT
RAISERROR ('Committed' , 16, 1) WITH NOWAIT
Elaboration
Our situation is that we need to receive items from this Service Broker queue in a loop, blocking on WAITFOR, and hand them off to another system over an unreliable network. Items received from the queue are destined for one of many connections to that remote system. If the item is not successfully delivered to the other system, the transaction for that single item should be rolled back and the item will be returned to the queue. We commit the transaction upon successful delivery, unlocking the sequence of messages to be picked up by a subsequent loop iteration.
Delays in a sequence of related items should not affect delivery of unrelated sequences. Single items are sent into the queue as soon as they are available and are forwarded immediately. Items should be forwarded single-file, though order of delivery even within a sequence is not strictly important.
From the loop that receives one message at a time, a new or existing TcpClient is selected from our list of open connections, and the message and the open connection are passed along though the chain of asynchronous IO callbacks until the transmission is complete. Then we complete the DB Transaction in which we received the Item from the Service Broker Queue.
How can Service Broker and conversation groups be used to assist in this scenario?
Conversation groups are a local concept only, used exclusively for locking: correlated conversations belong in a group so that while you process a message on one conversation, another thread cannot process a correlated message. There is no information about conversation groups exchanged by the two endpoints, so in your example all the initiator endpoints end up belonging to one conversation group, but the target endpoints are each a distinct conversation group (each group having only one conversation). The reason the system behaves like this is because conversation groups are designed to address a problem like, say, a trip booking service: when it receives a message to 'book a trip', it has to reserve a flight, a hotel and a car rental. It must send three messages, one to each of these services ('flights', 'hotels', 'cars') and then the responses will come back, asynchronously. When they do come back, the processing must ensure that they are not processed concurrently by separate threads, which would each try to update the 'trip' record status. In messaging, this problem is know as 'message correlation problem'.
However, often conversation groups are deployed in SSB solely for performance reasons: they allow larger RECEIVE results. Target endpoints can be moved together into a group by using MOVE CONVERSATION but in practice there is a much simpler trick: reverse the direction of the conversation. Have your destination start the conversations (grouped), and the source sends its 'updates' on the conversation(s) started by the destination.
Some notes:
Don't use the fire-and-forget pattern of BEGIN/SEND/END. You're making it impossible to diagnose any problem in future, see Fire and Forget: Good for the military, but not for Service Broker conversations.
Never ever use WITH CLEANUP in production code. It is intended for administrative last-resort action like disaster recovery. If you abuse it you deny SSB any chance to properly track the message for correct retry delivery (if the message bounces on the target, for whatever reason, it will be lost forever).
SSB does not guarantee order across conversations, only within one conversation. Starting a new conversation for each INSERT event does not guarantee to preserve, on target, the order of insert operations.