{"id":316,"date":"2007-10-15T00:00:00","date_gmt":"2007-10-15T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/service-broker-advanced-basics-workbench\/"},"modified":"2021-08-16T15:02:23","modified_gmt":"2021-08-16T15:02:23","slug":"service-broker-advanced-basics-workbench","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/service-broker-advanced-basics-workbench\/","title":{"rendered":"Service Broker Advanced Basics Workbench"},"content":{"rendered":"\n<p>This is the second of three Workshops on Service Broker. Simple-Talk&#8217;s Workbench series are intended to be loaded into SQL Servers Management Studio, read and executed. They are intended to catapault the reader into familiarity with the subject by trying things out. Experiment and generally use them as a starting-off point with an aspect of SQL Server. The actual source code is in a downloadable file at the bottom of the article.<\/p>\n<h3>Contents<\/h3>\n<ul id=\"contents\">\n<li><a href=\"#first\">SSB Workbench part 2: Advanced Basics<\/a> <\/li>\n<li><a href=\"#second\">Setup<\/a> <\/li>\n<li><a href=\"#third\">The Converstion Endpoints view<\/a> <\/li>\n<li><a href=\"#fourth\">Conversation Groups<\/a> <\/li>\n<li><a href=\"#sixth\">Taking a better look at RECEIVE<\/a> <\/li>\n<li><a href=\"#seventh\">Conversation Group Locking<\/a> <\/li>\n<li><a href=\"#nineth\">Message Activation<\/a> <\/li>\n<li><a href=\"#tenth\">Routing and Cross-Database Messaging<\/a> <\/li>\n<\/ul>\n<h3 id=\"f1rst\">SSB Workbench Part 2: Advanced Basics<\/h3>\n<p>In the first part of this workbench series, we covered the foundations: Setting up message types, contracts, queues, and services, and sending and waiting for messages. This second part extends on the first. We&#8217;ll get in to some of the catalog views that you can query to find out what Service Broker is up to, investigate how Service Broker handles transactions and locking, route our messages across databases, and process messages automatically with stored procedures. <\/p>\n<p>Once you&#8217;ve finished with this second workbench, you will have a complete understanding of all of the most common SSB features that you will use on projects again and again. The few remaining interesting features will be covered in the third installment of this mega-workbench, coming soon to a Simple-Talk near you&#8230; <\/p>\n<p>So with that, let&#8217;s jump in!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Set up new DB\r\n--To begin with, we'll create a database to work in.\r\nCREATE DATABASE Simple_Talk_SSB2\r\nGO\r\n\r\nUSE Simple_Talk_SSB2\r\nGO\r\n\r\n--Make sure that SSB is enabled\r\nALTER DATABASE Simple_Talk_SSB2\r\nSET ENABLE_BROKER\r\nWITH ROLLBACK IMMEDIATE\r\nGO\r\n\r\n--Create a master key\r\nCREATE MASTER KEY\r\nENCRYPTION BY PASSWORD = 'onteuhoeu'\r\nGO\r\n<\/pre>\n<h3 id=\"second\">Setup<\/h3>\n<p>Get a few basics in place: A message type without validation (meaning that we can send any binary data), a contract based on the message type, a couple of queues, and a couple of services. See the <a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/service-broker-foundations-workbench\/\">last workbench<\/a> for details on all of this. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE MESSAGE TYPE BLOB\r\nVALIDATION = NONE\r\nGO\r\n\r\nCREATE CONTRACT BLOB_Contract\r\n(BLOB SENT BY ANY)\r\nGO\r\n\r\nCREATE QUEUE BLOB_Queue_Init\r\nCREATE QUEUE BLOB_Queue_Target\r\nGO\r\n\r\nCREATE SERVICE BLOB_Service_Init\r\nON QUEUE BLOB_Queue_Init\r\n(BLOB_Contract)\r\n\r\nCREATE SERVICE BLOB_Service_Target\r\nON QUEUE BLOB_Queue_Target\r\n(BLOB_Contract)\r\nGO\r\n<\/pre>\n<h3 id=\"third\">The Conversation Endpoints View<\/h3>\n<p>The <code>sys.conversation_endpoints<\/code> view is the primary catalog view that you can use to get information about which conversations exist in the current database, and their current status. The view provides quite a bit of information about each conversation, and we can use it to gain some insights into the internals of Service Broker. <\/p>\n<p>Since we have, presumably, just created the database, querying the view at this point should return an empty set of rows: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM sys.conversation_endpoints\r\nGO\r\n<\/pre>\n<p>Although there are almost 30 columns exposed by the view, for the sake of this workbench we&#8217;ll consider only a few: <\/p>\n<dl class=\"dl--wide\">\n<dt><code>conversation_handle <\/code><\/dt>\n<dd>Conversation (or dialog) handles were discussed in detail in the previous workbench. This column is effectively the primary key for the view. The view will have one row for each handle generated, whether by an initiator or a target. <\/dd>\n<dt><code>conversation_id <\/code><\/dt>\n<dd>Each dialog has both an initiator and a target, with different dialog handles. This column is a GUID which relates rows for initiators with the rows for their targets. <\/dd>\n<dt><code>is_iniator <\/code><\/dt>\n<dd>This is a bit column that will return <code>1<\/code> if the initiating handle was the one represented in the <code>conversation_handle<\/code> column, or <code>0<\/code> otherwise. <\/dd>\n<dt><code>conversation_group_id <\/code><\/dt>\n<dd>Conversation groups and this column will be discussed in detail shortly. <\/dd>\n<dt><code>state_desc <\/code><\/dt>\n<dd>Any given side of a dialog, at any given time, can be in any of several states. These include &#8220;CONVERSING&#8221;, which means that the conversation is open for messages, or one of two &#8220;DISCONNECTED&#8221; states that indicate that one or both parties have called <code>END COVERSATION<\/code>. <\/dd>\n<dt><code>far_service<\/code> and <code>far_broker_instance <\/code><\/dt>\n<dd>Both of these columns will be discussed when we get into routing. <\/dd>\n<\/dl>\n<p>To look at some of the data exposed by the view, we&#8217;ll first have to start a conversation. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Start a conversation\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\nGO\r\n<\/pre>\n<p>Now that a conversation has been initiated, the following query should return one row: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT \r\n    conversation_handle,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\n<\/pre>\n<p>Running this query, we see that for the one row returned, <code>is_initiator<\/code> is set to 1 &ndash; which makes sense, given that we have just initiated a conversation. But why was only one row returned? If you&#8217;re following along, you might recall that a dialog has two sides: an initiator and a target. And each of these sides gets their own conversation handle. Furthermore, you might recall that I just mentioned that the conversation handle is effectively the primary key for the Conversation Endpoints view. So wouldn&#8217;t two rows make more sense? <\/p>\n<p>The value of the <code>state_desc<\/code> column should give you a hint. It will read &#8220;STARTED_OUTBOUND&#8221;. Initiating a dialog, as it turns out, only starts up the initiator&#8217;s end of things. The target&#8217;s side of the conversation doesn&#8217;t actually exist until a message joins the fray and there is something for a target to receive&#8230; <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Get the conversation handle\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nSELECT @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\n\r\n--Send a message\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Hello Simple-Talk, pt. 2!'))\r\n\r\n--Run the query again\r\nSELECT \r\n    conversation_handle,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nGO\r\n<\/pre>\n<p>Now we see both the initiator and the target, and for both rows the <code>state_desc<\/code> column&#8217;s value is &#8220;CONVERSING&#8221;. The conversation has started and all is well. Note the <code>conversation_id<\/code> column &ndash; its value is the same for both the initiator and the target. Should you ever need to find the target&#8217;s conversation handle based on the initiator handle, or the other way around, this column is key. <\/p>\n<p>Now that we&#8217;ve seen how a conversation starts, let&#8217;s take a peek at what happens when it ends. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">--First, end the target conversation\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\n;RECEIVE @h=conversation_handle\r\nFROM BLOB_Queue_Target\r\n\r\nEND CONVERSATION @h\r\n\r\n--What happened?\r\nSELECT \r\n    conversation_handle,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nGO<\/pre>\n<p>After running the preceding batch, you&#8217;ll notice that the target&#8217;s side of the conversation is now &#8220;CLOSED&#8221;. The initiator, on the other hand, has now gone into the &#8220;DISCONNECTED_INBOUND&#8221; state. As mentioned in the previous workbench, a disconnected side of a conversation can only retrieve any remaining messages and then end the conversation itself. So let&#8217;s do that. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--End the initiator's side\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\n;RECEIVE @h=conversation_handle\r\nFROM BLOB_Queue_Init\r\n\r\nEND CONVERSATION @h\r\n\r\n--What happened?\r\nSELECT \r\n    conversation_handle,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nGO\r\n\r\n\r\n<\/pre>\n<p>Now you might notice something interesting and wholly unexpected. The initiator&#8217;s side of the conversation is gone, no longer showing up in the result set for the view. But the target is still there, and still closed. <\/p>\n<p>As it turns out, the target will sit there in a closed state for &#8220;about half an hour,&#8221; according to Roger Wolter, the architect behind Service Broker. This is a security precaution, he says in his chapter of &#8220;Inside Microsoft SQL Server 2005: T-SQL Programming,&#8221; in order to prevent replay attacks. <\/p>\n<p>That topic is quite beyond the scope of this workbench, but it&#8217;s important that you know that the target side does stay around for a while even after the conversation has been closed. We&#8217;ll discuss why that&#8217;s so important in the next workbench, so stay tuned&#8230; <\/p>\n<h3 id=\"fourth\">Conversation Groups<\/h3>\n<p>As we&#8217;ve now observed several times throughout the course of the two workbenches, each time a dialog is started a conversation handle is automatically generated by Service Broker. But what you may not have noticed is that in addition to the conversation handle, there is also a second automatically generated GUID, the conversation group ID: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Start a conversation\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\nSELECT \r\n    conversation_handle,\r\n    conversation_group_id,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_handle = @h\r\nGO\r\n\r\n\r\n\r\n<\/pre>\n<p>The first thing that makes this GUID different than the conversation handle is that it doesn&#8217;t have to be auto-generated. You can tell Service Broker which ID to use: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Start a conversation\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH\r\n    RELATED_CONVERSATION_GROUP = '0F0F0F0F-0E0E-0D0D-0C0C-0B0B0B0B0B0B',\r\n    ENCRYPTION=OFF\r\n\r\nSELECT \r\n    conversation_handle,\r\n    conversation_group_id,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_handle = @h\r\nGO<\/pre>\n<p>Running this, you&#8217;ll see that the instantly-identifiable GUID I&#8217;ve created is used as the <code>conversation_group_id<\/code>. But that&#8217;s just the initiator end of the conversation. What about the target? <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER, @i UNIQUEIDENTIFIER\r\n\r\nSELECT \r\n    @h = conversation_handle,\r\n    @i = conversation_id\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_group_id = '0F0F0F0F-0E0E-0D0D-0C0C-0B0B0B0B0B0B'\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'What''s my group?'))\r\n\r\nSELECT\r\n    conversation_handle,\r\n    conversation_group_id,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_id = @i\r\nGO<\/pre>\n<p>Running the preceding batch, you&#8217;ll see that the target conversation is not enlisted in the same group as the initiator. Given the decoupled nature of initiators and targets we&#8217;ve already seen in the world of Service Broker, this should come as no huge shock, but there are some other reasons to keep these separate that I will show you in the section on locking and blocking. <\/p>\n<h3 id=\"sixth\">Taking a Better Look at RECEIVE<\/h3>\n<p>To see the basic utility of conversation groups, we&#8217;ll first have to take a step back and look at the <code>RECEIVE<\/code> statement in a bit more depth than in the <a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/service-broker-foundations-workbench\/\">last workbench<\/a>. Last time, we saw <code>RECEIVE<\/code> in its barest form &ndash; the statement itself and a column list. The optional <code>WHERE<\/code> clause allows you to filter based on a given conversation handle or conversation group ID. <\/p>\n<p>The following batch first uses <code>RECEIVE<\/code> with a conversation handle, then sends a message back to the initiator, where it is received based on the conversation group ID. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--First, get the target handle\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nSELECT \r\n    @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\nWHERE \r\n    conversation_id =\r\n    (\r\n        SELECT conversation_id\r\n        FROM sys.conversation_endpoints\r\n        WHERE conversation_group_id = '0F0F0F0F-0E0E-0D0D-0C0C-0B0B0B0B0B0B'\r\n    )\r\n    AND is_initiator = 0\r\n\r\n--Now, receive the message, and end the conversation\r\n;RECEIVE *\r\nFROM Blob_Queue_Target\r\nWHERE conversation_handle = @h\r\n\r\n;END CONVERSATION @h\r\n\r\n--Now use the conversation group ID to receive \r\n--the end conversation message\r\n;RECEIVE *\r\nFROM Blob_Queue_Init\r\nWHERE conversation_group_id = '0F0F0F0F-0E0E-0D0D-0C0C-0B0B0B0B0B0B'\r\n\r\n--Finally, since we're good Service Broker users, end\r\n--the initiating conversation...\r\nSELECT \r\n    @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_group_id = '0F0F0F0F-0E0E-0D0D-0C0C-0B0B0B0B0B0B'\r\n\r\nEND CONVERSATION @h\r\nGO<\/pre>\n<p>Now that we&#8217;ve seen the power of the <code>RECEIVE<\/code> statement&#8217;s <code>WHERE<\/code> clause, it should be easy to see how it can be put to good use in conjunction with a conversation group ID. Suppose that for a certain task, you want to start several distinct dialogs, but get back all of the responses using a single <code>RECEIVE<\/code> statement. Since <code>RECEIVE<\/code>&#8216;s WHERE clause only supports the equality predicate, you can&#8217;t ask for multiple conversations at once. You can instead group all of the conversations using a single conversation group, and use that to get all of the responses. <\/p>\n<p>This same pattern gets even more powerful if you&#8217;re creating multiple dialogs across more than one service or queue. To see what that looks like, we&#8217;ll create a second target queue and a corresponding service: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE QUEUE BLOB_Queue_Target2\r\n\r\nCREATE SERVICE BLOB_Service_Target2\r\nON QUEUE BLOB_Queue_Target2\r\n(BLOB_Contract)\r\nGO<\/pre>\n<p>Dialogs can now be started between the initiator and both queues, using the same conversation group id: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--Send to the BLOB_Service_Target\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH\r\n    RELATED_CONVERSATION_GROUP = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE',\r\n    ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'First Send'))\r\n\r\n\r\n--Send to the BLOB_Service_Target2\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target2'\r\nON CONTRACT BLOB_Contract\r\nWITH\r\n    RELATED_CONVERSATION_GROUP = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE',\r\n    ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Second Send'))\r\n\r\n\r\n--Get the endpoint data for the conversation group\r\nSELECT\r\n    conversation_handle,\r\n    conversation_group_id,\r\n    conversation_id,\r\n    is_initiator,\r\n    state_desc,\r\n    far_service\r\nFROM sys.conversation_endpoints\r\nWHERE conversation_group_id = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'\r\nGO\r\n<\/pre>\n<p>Looking at the output of the query from the Conversation Endpoints view, you&#8217;ll see that I&#8217;ve added a new column into the mix: <code>far_service<\/code>. This column tells us what service the dialog is targeting, and you should see two dialogs, each conversing to different services but both on the same conversation group. <\/p>\n<p>Each of the targets can receive messages as usual, and send them back to the initiator: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\n;RECEIVE @h = conversation_handle\r\nFROM BLOB_Queue_Target\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Back at you from Queue_Target'))\r\n\r\n;RECEIVE @h = conversation_handle\r\nFROM BLOB_Queue_Target2\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Back at you from Queue_Target2'))\r\nGO<\/pre>\n<p>The initiator can now receive both messages that we&#8217;ve sent back, using only the conversation group ID &ndash; despite the fact that the messages are on different conversations. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nRECEIVE *\r\nFROM BLOB_Queue_Init\r\nWHERE conversation_group_id = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'\r\nGO<\/pre>\n<h3 id=\"seventh\">Conversation Group Locking<\/h3>\n<p>We&#8217;ve now seen the power of conversation groups as a means by which to introduce some logical organization to sets of conversations. But conversation groups play a much bigger role in the overall Service Broker infrastructure. <\/p>\n<p>Since the Service Broker is a database engine technology and supports all of the same ACID rules as any other data in your database, it should come as no surprise to hear that it uses locking (and, as a consequence, blocking) to achieve isolation. One might, at first glance, assume that the granularity of the locks will be at the conversation or dialog level. But the designers of Service Broker realized that some processes are much more distributed than a single conversation &ndash; so locking instead occurs at the granularity of conversation groups. <\/p>\n<p>If you&#8217;ve been following along and running the examples, you should have two conversing dialogs at this point, each with an initiator on the same conversation group. We can use these dialogs to examine the ins and outs of conversation group locking. <\/p>\n<p>First we&#8217;ll send a message from the Target2 service to the initiator: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nSELECT @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\nWHERE \r\n    conversation_id = \r\n    (\r\n        SELECT conversation_id\r\n        FROM sys.conversation_endpoints\r\n        WHERE far_service = 'BLOB_Service_Target2'\r\n    )\r\n    AND is_initiator = 0\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Hello again, from Queue_Target2'))\r\nGO<\/pre>\n<p>To show the effects of locking, open a new SSMS window, begin a transaction and do a <code>RECEIVE<\/code> for the conversation group: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--- RUN THIS IN A NEW WINDOW!\r\nBEGIN TRAN\r\n\r\n;RECEIVE *\r\nFROM BLOB_Queue_Init\r\nWHERE conversation_group_id = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'\r\n---\r\n<\/pre>\n<p>Back in this window, let&#8217;s try sending a message to the Target service: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nSELECT @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\nWHERE\r\n    conversation_group_id = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'\r\n    AND far_service = 'BLOB_Service_Target'\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Hi, Queue_Target'))\r\nGO\r\n\r\n--Once you get sick of waiting for this to finish, go ahead\r\n--and hit the Stop button.\r\n\r\n<\/pre>\n<p>As you can see, we&#8217;re blocked, even though we&#8217;re sending a message on a different conversation than the one that we&#8217;ve received a message on. The net effect is that the entire group represents a single transactional unit &ndash; a powerful concept, if you need to do several subtasks as part of some bigger piece of work. <\/p>\n<p>But now for the cool part. The conversation group that we&#8217;re working with is locked, but remember that a dialog has two sides, each of which operates under its own conversation group. This means that even when one side is locked, the other side is free to continue working, and even free to continue sending messages: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nSELECT @h = conversation_handle\r\nFROM sys.conversation_endpoints\r\nWHERE \r\n    conversation_id = \r\n    (\r\n        SELECT conversation_id\r\n        FROM sys.conversation_endpoints\r\n        WHERE far_service = 'BLOB_Service_Target2'\r\n    )\r\n    AND is_initiator = 0\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Another hello from Queue_Target2'))\r\nGO<\/pre>\n<p>We&#8217;ve now sent a message. Let&#8217;s take a peek and see if it showed up: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM BLOB_Queue_Init WITH (NOLOCK)\r\nGO\r\n<\/pre>\n<p>Running the preceding query should return one row, for the message that we just sent. The <code>NOLOCK<\/code> hint is necessary, in order to get around the locked messages, still held by the transaction in the other window. <\/p>\n<p>What do you think will happen if we try to do a second <code>RECEIVE<\/code> outside the scope of the transaction? <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nRECEIVE *\r\nFROM BLOB_Queue_Init\r\nWHERE conversation_group_id = 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE'\r\n<\/pre>\n<p>If you&#8217;ve just run the preceding query, you might be surprised by the results. We have a message in the queue for the specified conversation group, and the conversation group is locked by the transaction, so shouldn&#8217;t we see some blocking rather than an empty result set? <\/p>\n<p>To help explain this phenomenon, initiate a new conversation, from the Target service to the Init service, and send a message: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Target\r\nTO SERVICE 'BLOB_Service_Init'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Initiating from Target'))\r\n\r\n--What's in the queue now?\r\nSELECT *\r\nFROM BLOB_Queue_Init WITH (NOLOCK)\r\nGO\r\n<\/pre>\n<p>Now there are two messages in the queue, each belonging to different conversation groups. What will be the result, then, of a <code>RECEIVE<\/code> with no <code>WHERE<\/code> clause? <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nRECEIVE *\r\nFROM BLOB_Queue_Init\r\nGO<\/pre>\n<p>This <code>RECEIVE<\/code> is not blocked &ndash; and that&#8217;s a good thing, because we want multiple readers to be able to work with the queue simultaneously. What Service Broker actually does internally as part of the <code>RECEIVE<\/code>, is use the equivalent of a <code>READPAST<\/code> hint. The locked conversation groups are skipped, thereby greatly increasing concurrency. <\/p>\n<p>There is one other side-effect of the way Service Broker handles conversation group locking. To prepare, first go back to the other window and commit the transaction: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\n--- RUN THIS IN THE OTHER WINDOW\r\nCOMMIT\r\n---\r\n<\/pre>\n<p>Now start another dialog and send a message to the Init queue: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Target\r\nTO SERVICE 'BLOB_Service_Init'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Initiating from Target... Again'))\r\n\r\n--What's in the queue now?\r\nSELECT *\r\nFROM BLOB_Queue_Init\r\nGO\r\n<\/pre>\n<p>We once again have two messages in the queue, both on separate conversation groups. But this time the <code>NOLOCK<\/code> hint wasn&#8217;t needed for the <code>SELECT<\/code> &ndash; no transactions are active and neither of the conversation groups are locked. <\/p>\n<p>So once again we pose an all-important question: In this situation, how should a <code>RECEIVE<\/code> with no <code>WHERE<\/code> clause behave? <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nRECEIVE *\r\nFROM BLOB_Queue_Init\r\nGO\r\n<\/pre>\n<p>Running the <code>RECEIVE<\/code>, you&#8217;ll see that only a single message is returned. This doesn&#8217;t mean that <code>RECEIVE<\/code> can&#8217;t return multiple messages at a time &ndash; it can, and as shown in the previous workbench it supports the TOP clause. What we&#8217;re actually seeing in this case is <code>RECEIVE<\/code> only returning messages from a single conversation group at a time. <\/p>\n<p>Yet again, this is an effort by the Service Broker team to promote high concurrency. If you have a farm of workers, each pulling tasks off of a central Service Broker queue, this single group at a time behavior ensures that each worker takes at most a single task, and at the same time the <code>READPAST<\/code> guarantees that workers don&#8217;t block each other. The result is a high performance and highly concurrent queuing system &ndash; exactly what we want from something like Service Broker. <\/p>\n<p>In the next installment of this workbench I&#8217;ll discuss worker farms and considerations for read performance in quite a bit more depth. Until then, you should now have a good feeling for how Service Broker handles locking and blocking. <\/p>\n<h3 id=\"nineth\">Message Activation <\/h3>\n<p>We are programmers, and a prerequisite for being a programmer is being an incredibly lazy person. The whole point of computer programming is to spend a bit of time upfront to make a machine do all of the repetitive tasks while we sit back and watch. Programming is a fantastic career for those of us who prefer surfing the Web to sitting around waiting for data to come in for us to process. We let the computers do this grunt work. Yet we haven&#8217;t really let the computer do anything with Service Broker yet. We have run all of the <code>RECEIVE<\/code> statements ourselves &ndash; seriously cutting into our surfing habit. <\/p>\n<p>Luckily, the creators of Service Broker are also programmers, and they understand that we need time to ourselves. The answer to this burning need for freedom is Service Broker&#8217;s message activation feature. Turning on message activation for a queue tells Service Broker to sit and quietly wait for a message to arrive. When &ndash; and only when &ndash; a message shows up, Service Broker suddenly comes alive, running whatever stored procedure you&#8217;ve told it to start up in the event that a message does appear. <\/p>\n<p>Message activation is nothing difficult to understand now that we&#8217;ve gone through most of the Service Broker infrastructure in detail. A properly written activation stored procedure uses the same <code>RECEIVE<\/code> logic and statements we&#8217;ve already covered. The only difference, as you&#8217;ll see, is that an activation procedure generally wraps these constructs in a loop in order to make things a bit more efficient. <\/p>\n<p>To demonstrate activation, our procedure needs to actually do something that we can observe once it has taken place. So to start with, create the following table, which we&#8217;ll insert some rows into asynchronously: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE TABLE TestActivation\r\n(\r\n    theMessage VARBINARY(MAX)\r\n)\r\nGO\r\n<\/pre>\n<p>Once the table has been created, we can put our very first activation procedure into place. This procedure is nothing more than a slightly-modified version of the code <a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/service-broker-foundations-workbench\/#thirteenth\">from the &#8220;Ending the Conversation, Part 2&#8221; section of the first part of this workbench,<\/a> wrapped in a stored procedure. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE PROCEDURE InsertTestActivation\r\nAS\r\nBEGIN\r\n    SET NOCOUNT ON\r\n\r\n    DECLARE \r\n        @h UNIQUEIDENTIFIER,\r\n        @t sysname,\r\n        @b varbinary(max)\r\n\r\n    --Get all of the messages on the queue\r\n    WHILE 1=1\r\n    BEGIN\r\n        SET @h = NULL\r\n\r\n        --Note the semicolon..!\r\n        ;RECEIVE TOP(1) \r\n            @h = conversation_handle,\r\n            @t = message_type_name,\r\n            @b = message_body\r\n        FROM BLOB_Queue_Target\r\n\r\n        --No message RECEIVEd\r\n        IF @h IS NULL\r\n        BEGIN\r\n            BREAK\r\n        END\r\n        --BLOB message\r\n        ELSE IF @t = 'BLOB'\r\n        BEGIN\r\n            INSERT TestActivation \r\n            VALUES (@b)\r\n        END\r\n        --EndDialog message\r\n        ELSE IF @t = 'http:\/\/schemas.microsoft.com\/SQL\/ServiceBroker\/EndDialog'\r\n        BEGIN\r\n            INSERT TestActivation \r\n            VALUES (CONVERT(varbinary(MAX), 'EndDialog'))\r\n\r\n            END CONVERSATION @h\r\n        END\r\n        --Any other message type\r\n        ELSE\r\n        BEGIN\r\n            INSERT TestActivation\r\n            VALUES (CONVERT(varbinary(MAX), 'Unknown'))\r\n        END\r\n    END        \r\nEND\r\nGO<\/pre>\n<p>This stored procedure loops over all of the messages in the <code>BLOB_Service_Target<\/code> queue, inserting the bodies into the TestActivation table. It also follows all of the rules of being a good Service Broker consumer; if it gets an <code>EndDialog<\/code> message, it ends its own side of the conversation. And if it receives a a message it doesn&#8217;t know how to deal with, it gracefully handles the situation. <\/p>\n<p>Since there is still a message sitting on the queue from a previous run, you can test the procedure now, before actually enabling it for activation. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nEXEC InsertTestActivation\r\nGO\r\n\r\nSELECT *\r\nFROM TestActivation\r\nGO\r\n\r\nDELETE \r\nFROM TestActivation\r\nGO\r\n\r\n<\/pre>\n<p>Actually using the procedure as an activation service is a simple matter of using <code>ALTER QUEUE<\/code> and telling Service Broker about the module: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nALTER QUEUE BLOB_Queue_Target\r\nWITH ACTIVATION\r\n(\r\n    STATUS = ON,\r\n    PROCEDURE_NAME = InsertTestActivation,\r\n    MAX_QUEUE_READERS = 1,\r\n    EXECUTE AS OWNER\r\n)\r\nGO<\/pre>\n<p>Few notes on the options I&#8217;ve used: <\/p>\n<p><code>STATUS = ON<\/code> is simple enough. Setting <code>STATUS<\/code> to <code>ON<\/code> enables activation for the queue, meaning that as soon as a message arrives the activation procedure will be fired. <\/p>\n<p>The <code>PROCEDURE_NAME<\/code> is, of course, the name of the activation procedure. <\/p>\n<p>And <code>EXECUTE AS OWNER<\/code> tells Service Broker to call the activation stored procedure using the credentials of whatever principal owns it &ndash; in this case, presumably your user. <\/p>\n<p>I haven&#8217;t forgotten about <code>MAX_QUEUE_READERS<\/code>, but rather saved it; we&#8217;ll get to it in just a moment, but first let&#8217;s test activation by sending a message and seeing whether it arrives in the table. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'Test TestActivation'))\r\n\r\nSELECT *\r\nFROM TestActivation\r\nGO\r\n<\/pre>\n<p>If you&#8217;ve just run the preceding batch, you might notice that no rows were returned by the query. Is activation broken? The answer, if you&#8217;ve been running all of the code to this point, is of course no. The reason that no rows were returned is that activation is an asynchronous process, kicked off by a queue monitor thread that only checks for new messages every few seconds. Therefore, after a message is sent, you shouldn&#8217;t expect the stored procedure to instantly kick in &ndash; it might take a second or two before the queue monitor passes by and starts things up. <\/p>\n<p>Running the <code>SELECT<\/code> again, you should now see the expected row: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM TestActivation\r\nGO<\/pre>\n<p>As an aside, if activation still wasn&#8217;t working in a real system, you might want to check the Service Queues catalog view, which includes columns to help you determine whether activation is enabled, and which stored procedure has been configured for activation. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT\r\n    name,\r\n    activation_procedure,\r\n    is_activation_enabled\r\nFROM sys.service_queues\r\nWHERE name = 'BLOB_Queue_Target'\r\nGO\r\n<\/pre>\n<p>Back to the main task at hand: At this point we&#8217;ve handled a single message with a single queue reader. Now it&#8217;s time to talk about multiple readers, as specified by the <code>MAX_QUEUE_READERS<\/code> option. <\/p>\n<p>This is the feature that makes activation much more than just a simple background process. Sure, we can do one message, but what about when multiple messages show up? With <code>MAX_QUEUE_READERS<\/code> set to 1, Service Broker will only start up one instance of the procedure at a time. And this is fine for us, since our procedure has a loop; it can handle multiple messages after being called just once. But what if its processing duties are slow, and it&#8217;s not clearing the queue fast enough? This is when a higher <code>MAX_QUEUE_READERS<\/code> value starts to look interesting. <\/p>\n<p>If <code>MAX_QUEUE_READERS<\/code> is set to a value above 1, the same queue monitor that started activation to begin with will eventually kick off more instances of the stored procedure. This will keep going until either the queue empties, or the number of running instances hits the maximum specified by the option. <\/p>\n<p>To see this in action, a <code>WAITFOR DELAY<\/code> can be inserted into the a test stored procedure to slow it down and keep the queue from clearing too quickly. To keep things simple, I am trimming off all of the &#8220;best practice&#8221; code involving ending conversations and dealing with unknown message types. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE PROCEDURE ActivationWithDelay\r\nAS\r\nBEGIN\r\n    SET NOCOUNT ON\r\n\r\n    DECLARE @h UNIQUEIDENTIFIER\r\n\r\n    --Get all of the messages on the queue\r\n    WHILE 1=1\r\n    BEGIN\r\n        SET @h = NULL\r\n\r\n        --Note the semicolon..!\r\n        ;RECEIVE TOP(1) \r\n            @h = conversation_handle\r\n        FROM BLOB_Queue_Target\r\n\r\n        --No message received\r\n        IF @h IS NULL\r\n        BEGIN\r\n            BREAK\r\n        END\r\n        ELSE\r\n        BEGIN\r\n            --Wait one quarter of a second\r\n            WAITFOR DELAY '00:00:00.20'\r\n        END\r\n    END\r\nEND\r\nGO\r\n<\/pre>\n<p>To test activation timing and get a feel for how it works, we&#8217;ll first disable activation on the queue, then send 2500 messages to prime the pump. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nALTER QUEUE BLOB_Queue_Target\r\nWITH Activation\r\n(\r\n    STATUS = OFF\r\n)\r\nGO\r\n\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Target'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(varbinary, 'DELAY'))\r\nGO 2500\r\n<\/pre>\n<p>To monitor the activation, we will use the Broker Activated Tasks view. The following query should return a count of 0, since activation is currently disabled on the queue: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT COUNT(*)\r\nFROM sys.dm_broker_activated_tasks t\r\nWHERE t.queue_id = OBJECT_ID('BLOB_Queue_Target')\r\nGO\r\n<\/pre>\n<p>The next batch will turn activation back on for the queue, with a specified maximum number of activation procedures set to 10. It will then start looping until the first activation procedure kicks in. After that it will report on the number of active activation procedures once every two seconds, until they&#8217;re all gone (meaning that the queue is empty). After starting the batch, you should switch to the Messages pane in SSMS unless you have Results to Text mode enabled. Note that this batch takes around one minute to run on my notebook. Your timing may vary! <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nALTER QUEUE BLOB_Queue_Target\r\nWITH ACTIVATION\r\n(\r\n    STATUS = ON,\r\n    PROCEDURE_NAME = ActivationWithDelay,\r\n    MAX_QUEUE_READERS = 10,\r\n    EXECUTE AS OWNER\r\n)\r\n\r\nDECLARE @startTime DATETIME\r\nSET @startTime = GETDATE()\r\n\r\nWHILE NOT EXISTS \r\n(\r\n    SELECT *\r\n    FROM sys.dm_broker_activated_tasks t\r\n    WHERE t.queue_id = OBJECT_ID('BLOB_Queue_Target')\r\n)\r\n    WAITFOR DELAY '00:00:00.25'\r\n\r\nDECLARE @numReaders INT\r\nDECLARE @count INT\r\n\r\nWHILE 1=1\r\nBEGIN\r\n    SELECT @numReaders = COUNT(*)\r\n    FROM sys.dm_broker_activated_tasks\r\n    WHERE queue_id = OBJECT_ID('BLOB_Queue_Target')\r\n\r\n    IF @numReaders &gt; 0\r\n    BEGIN\r\n        SELECT\r\n            @count = COUNT(*)\r\n        FROM BLOB_Queue_Target WITH (NOLOCK)\r\n\r\n        DECLARE @message VARCHAR(100)\r\n        SET @message = \r\n            'Elapsed Time (seconds): ' +\r\n            CONVERT(VARCHAR, DATEDIFF(ss, @startTime, GETDATE())) +\r\n            ' Number of Readers: ' +\r\n            CONVERT(VARCHAR, @numReaders) +\r\n            ' Remaining Messages: ' +\r\n            CONVERT(VARCHAR, @count)\r\n        RAISERROR(@message, 10, 1) WITH NOWAIT\r\n\r\n        WAITFOR DELAY '00:00:02'\r\n    END\r\n    ELSE\r\n    BEGIN\r\n        RAISERROR('Finished!', 10, 1) WITH NOWAIT\r\n        BREAK\r\n    END\r\nEND\r\nGO\r\n<\/pre>\n<p>By now you should have a feeling for the basics of how activation behaves, but it&#8217;s important to stress that you need to think beyond just getting messages through the queue. We must consider activation in the context of the entire server. Should you set the maximum number of readers to a huge enough number to keep the queue mostly clear all the time? While that would certainly make your services return more quickly, it would also add a large amount of stress to the server. One of the reasons that queues are handy is that they can let work back up if it can&#8217;t be handled right away. When working with Service Broker, make sure to consider all aspects of the system, not just maximization of the one piece you&#8217;re focusing on. <\/p>\n<p>The activation procedures shown here could be made a bit better. I&#8217;ve left out two key parts of the generally accepted activation procedure pattern: Use of <code>WAITFOR<\/code> on the receive and use of a transaction. <\/p>\n<p>To see the utility of <code>WAITFOR(RECEIVE)<\/code>, consider what happened at the end of the test run in the last batch. The moment there were no messages left to process, all of the activation procedures immediately stopped running. What if another batch of messages arrived just afterward? All of the procedures would have to spin back up, and the newly arrived messages would not be handled as quickly as the final messages that were on the queue at the end of the test. By using <code>WAITFOR(RECEIVE)<\/code> with a small timeout &ndash; say, a few seconds &ndash; we can make sure that the stored procedures stick around once the work is done, in case more should arrive. <\/p>\n<p>Another issue is use of a transaction. What if a hardware error occurs between receiving the message from the queue and processing it? The message would be lost &ndash; unless we wrap the entire thing in a transaction. <\/p>\n<p>An updated version of the <code>InsertTestActivation<\/code> stored procedure follows. This version uses a transaction in conjunction with the <code>XACT_ABORT<\/code> setting, to make sure that a rollback will occur in the event of any error. The procedure also uses <code>WAITFOR(RECEIVE)<\/code>, with a timeout of five seconds. <\/p>\n<p>Note that one consequence of transactions is what happens when a certain message keeps causing a rollback. I won&#8217;t get into the details here; search Books Online for &#8220;poison message handling&#8221; for more information. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nALTER PROCEDURE InsertTestActivation\r\nAS\r\nBEGIN\r\n    SET NOCOUNT ON\r\n    SET XACT_ABORT ON\r\n\r\n    DECLARE \r\n        @h UNIQUEIDENTIFIER,\r\n        @t sysname,\r\n        @b varbinary(max)\r\n\r\n    --Get all of the messages on the queue\r\n    WHILE 1=1\r\n    BEGIN\r\n        BEGIN TRANSACTION\r\n\r\n        SET @h = NULL\r\n\r\n        --Wait up to five seconds for a message\r\n        WAITFOR\r\n        (\r\n            RECEIVE TOP(1) \r\n                @h = conversation_handle,\r\n                @t = message_type_name,\r\n                @b = message_body\r\n            FROM BLOB_Queue_Target\r\n        ), TIMEOUT 5000\r\n\r\n        --No message received\r\n        IF @h IS NULL\r\n        BEGIN\r\n            ROLLBACK\r\n            BREAK\r\n        END\r\n        --BLOB message\r\n        ELSE IF @t = 'BLOB'\r\n        BEGIN\r\n            INSERT TestActivation \r\n            VALUES (@b)\r\n        END\r\n        --EndDialog message\r\n        ELSE IF @t = 'http:\/\/schemas.microsoft.com\/SQL\/ServiceBroker\/EndDialog'\r\n        BEGIN\r\n            INSERT TestActivation \r\n            VALUES (CONVERT(varbinary(MAX), 'EndDialog'))\r\n\r\n            END CONVERSATION @h\r\n        END\r\n        --Any other message type\r\n        ELSE\r\n        BEGIN\r\n            INSERT TestActivation\r\n            VALUES (CONVERT(varbinary(MAX), 'Unknown'))\r\n        END\r\n\r\n        \r\n        COMMIT\r\n    END        \r\nEND\r\nGO\r\n<\/pre>\n<h3 id=\"tenth\">Routing and Cross-Database Messaging <\/h3>\n<p>We&#8217;ve now covered just about every aspect of same-database conversations that you&#8217;ll commonly need to be concerned with when you work with Service Broker. But we need to remember that SSB is more than just a queuing system &ndash; it&#8217;s also a messaging infrastructure. <\/p>\n<p>Same-database messaging certainly has its utility, but when we usually talk about messaging we think about communication between remote parties. For the sake of this workbench I won&#8217;t ask you to set up a separate server &ndash; our remote parties will be two databases in the same instance &ndash; but the concepts and all of the code I&#8217;ll show you will be the same if you choose to extend the example across multiple servers or even multiple data centers. <\/p>\n<p>Routing is a complex topic, and I&#8217;ll just cover the basics here. What you need to know is that whenever you specify a target service when creating a message, you are not limited to a service in the local database; you can tell Service Broker about remote services in other databases or on other instances, using the <code>CREATE ROUTE DDL<\/code> statement. These routes are exposed via a catalog view called sys.routes: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM sys.routes\r\nGO<\/pre>\n<p>Running the above query, you should see only one route, which is automatically created for the local database. But what if we want to send a message to another database? Let&#8217;s create one and find out what happens: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE DATABASE Simple_Talk_SSB2a\r\nGO\r\nUSE Simple_Talk_SSB2a\r\nGO\r\n\r\n--Setup all the basics\r\nCREATE MASTER KEY\r\nENCRYPTION BY PASSWORD = 'onteuhoeu'\r\nGO\r\nCREATE MESSAGE TYPE BLOB\r\nVALIDATION = NONE\r\nGO\r\nCREATE CONTRACT BLOB_Contract\r\n(BLOB SENT BY ANY)\r\nGO\r\nCREATE QUEUE BLOB_Queue_Remote\r\nGO\r\nCREATE SERVICE BLOB_Service_Remote\r\nON QUEUE BLOB_Queue_Remote\r\n(BLOB_Contract)\r\nGO\r\n<\/pre>\n<p>A final step is required because we&#8217;ll be talking to a remote database: permissions to set who can send messages to the service. For simplicity we&#8217;ll set it to <code>[Public] (everyone)<\/code>; in your real applications you should configure this to something more realistic based on your security requirements. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nGRANT SEND ON SERVICE::BLOB_Service_Remote TO [Public];\r\nGO\r\n<\/pre>\n<p>Back in the SSB2 database, try sending a message to the remote queue and see if it shows up: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nUSE Simple_Talk_SSB2\r\nGO\r\n\r\nDECLARE @h UNIQUEIDENTIFIER\r\n\r\nBEGIN DIALOG CONVERSATION @h\r\nFROM SERVICE BLOB_Service_Init\r\nTO SERVICE 'BLOB_Service_Remote'\r\nON CONTRACT BLOB_Contract\r\nWITH ENCRYPTION=OFF\r\n\r\n;SEND ON CONVERSATION @h\r\nMESSAGE TYPE BLOB\r\n(CONVERT(VARBINARY, 'Test_Remote'))\r\nGO\r\n<\/pre>\n<p>Once the message is sent, query the remote queue. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM Simple_Talk_SSB2a..BLOB_Queue_Remote\r\nGO\r\n<\/pre>\n<p>You should now see an empty result set. Where did the message go? <\/p>\n<p>The primary tool used when debugging routing issues is the Transmission Queue, a catalog view that we can interrogate to see messages that weren&#8217;t successfully sent. The following query should return one row, for the message that we know has just failed. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT \r\n    to_service_name,\r\n    message_body,\r\n    transmission_status\r\nFROM sys.transmission_queue\r\nGO<\/pre>\n<p>If a message ends up in the transmission queue, it&#8217;s usually not dead. Service Broker will retry the send periodically, waiting up to a minute between each try. This means that once you fix the problem, the messages sitting in the transmission queue will eventually make it to their destination. <\/p>\n<p>The problem in this situation is that Service Broker cannot cross database boundaries without a route, if the database is not set for <code>TRUSTWORTHY<\/code> mode. That&#8217;s a good thing &ndash; it helps ensure security &ndash; so let&#8217;s take the high road and tell Service Broker how to route the message. <\/p>\n<p>The first step in routing is to create a Service Broker endpoint on each instance of SQL Server involved in the route. In this case we have only one instance, so a single endpoint will do. The following <code>CREATE ENDPOINT<\/code> statement creates an endpoint on port 9998. Since the SQL Server service account always has access to its own service, we&#8217;ll use Windows authentication. In your actual environments you can choose either Windows authentication or use of a certificate; see Books Online for more information. Finally, again for simplicity, we&#8217;ll keep encryption disabled. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nCREATE ENDPOINT SSB_Endpoint\r\nSTATE = STARTED\r\nAS TCP \r\n(\r\n    LISTENER_PORT = 9998\r\n)\r\nFOR SERVICE_BROKER\r\n(\r\n    AUTHENTICATION = WINDOWS,\r\n    ENCRYPTION = DISABLED\r\n)\r\nGO\r\n\r\n\r\n\/*\r\nWe can verify that the endpoint was successfully created and\r\nstarted by using the Service Broker Endpoints view:\r\n*\/\r\nSELECT\r\n    name,\r\n    state_desc\r\nFROM sys.service_broker_endpoints\r\n\r\n\r\n<\/pre>\n<p>Once the endpoint has been created, a route can be put into place, using the TCP address of the endpoint: localhost:9998. <\/p>\n<p>The route will be created for messages being sent to the service called <code>BLOB_Service_Remote<\/code>, and we&#8217;ll tell Service Broker which database the remote service is in, by using the Broker Instance GUID that is automatically created for each database. To get the GUID, query <code>sys.databases<\/code>: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT service_broker_guid\r\nFROM sys.databases\r\nWHERE name = 'Simple_Talk_SSB2a'\r\nGO\r\n\r\n\r\n\/*\r\nOnce you've obtained the GUID, replace the one below and create\r\nthe route:\r\n*\/\r\nCREATE ROUTE TestRemoteRoute\r\nWITH\r\n    SERVICE_NAME = 'BLOB_Service_Remote',\r\n    ADDRESS = 'tcp:\/\/localhost:9998',\r\n    --Use the GUID from above\r\n    BROKER_INSTANCE = 'EECFA1EA-EBA9-4042-9C76-6470AD9ED2B3'\r\nGO\r\n\r\n\r\n\/*\r\nTo verify that the route has been successfully created, you can\r\nonce again query the sys.routes view:\r\n*\/\r\nSELECT\r\n    remote_service_name,\r\n    broker_instance,\r\n    address\r\nFROM sys.routes\r\nWHERE name = 'TestRemoteRoute'\r\nGO\r\n<\/pre>\n<p>At this point, assuming that you&#8217;re not an exceptionally fast reader, a minute has probably passed since the message send originally failed. If the following query returns no rows, wait a few more moments and try again. Now that the route is in place, the message should eventually show up. <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\r\nSELECT *\r\nFROM Simple_Talk_SSB2a..BLOB_Queue_Remote\r\nGO\r\n\r\n\/*\r\nClean Up Our Mess!\r\n*\/\r\nUSE master\r\nGO\r\nDROP DATABASE Simple_Talk_SSB2\r\nGO\r\nDROP DATABASE Simple_Talk_SSB2a\r\nGO\r\nDROP ENDPOINT SSB_Endpoint\r\nGO<\/pre>\n<p>We&#8217;ve certainly come a long way from Part 1 of this workbench! After reading this second part, you should now be equipped to handle most Service Broker challenges, including some troubleshooting and maintenance using the catalog views and Service Broker specific DDL. You should also understand some of the transactional internals as well as activation and routing behaviors. <\/p>\n<p>The third and final part of this workbench series will apply what we&#8217;ve already covered, to create some interesting technical solutions that would not be nearly as easy to put together without Service Broker. Stay tuned for Part 3, coming to Simple Talk soon! <\/p>\n<p class=\"note\">&#8212; Text prettified by <a href=\"http:\/\/www.simple-talk.com\/prettifier\/\">the Simple-Talk prettifier<\/a> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>SQL Service Broker, an asynchronous queuing and messaging system for SQL Server 2005, is set to change the way we design and run distributed applications. Adam Machanic  once more makes it all seem so easy in Part 2 of his epic series&#8230; &hellip;<\/p>\n","protected":false},"author":8019,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143525],"tags":[4242,4149,4394,4150,4151,4827,4460],"coauthors":[6815],"class_list":["post-316","post","type-post","status-publish","format-standard","hentry","category-learn","tag-basics","tag-learn-sql-server","tag-service-broker","tag-sql","tag-sql-server","tag-t-sql-tsql-sequel-example-examples","tag-workbench"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/8019"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=316"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/316\/revisions"}],"predecessor-version":[{"id":66370,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/316\/revisions\/66370"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=316"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}