The design uses a custom receive pipeline component to stamp every incoming message with a sequence number. In addition, a singleton (single instance) orchestration resequences messages on the way out. The sequence number must be durable in order to maintain order across shutdowns. The custom pipeline component uses a SQL table to get the next available monotonically increasing sequence number and stamps the sequence number into the global context property for the message. The input messages to the pipeline component are assumed to be ordered using a receive adapter supporting ordered delivery such as MSMQ, MQSeries, MSMQT, and MLLP (in BizTalk Server 2006 R2). You must configure the receive locations to include this custom pipeline component for any orchestrations that you want to participate in ordered delivery.
Business orchestrations have to fulfill these requirements in the orchestrations:
-
Propagate the sequence number contained in the OrderedDelivery.SequenceNumber context property to the outgoing message.
-
Set the OrderedDelivery.PropertySchema.Destination context property to a known value such as “Gatekeeper”. This will ensure that the message is routed to the gatekeeper orchestration, which will resequence the message, because it subscribes to the Destination property.
-
Initialize this correlation set that contains the Destination property in the send shape.
-
The gatekeeper orchestration’s first receive shape is marked to be an activating receive shape that will initialize a correlation set that contains the OrderedDelivery.PropertySchema.Destination property.
/* Biz.odx construct message */
ProcMsg = BizMsg;
/* Propagate sequence number context property */
ProcMsg(OrderedDelivery.SequenceNumber) = BizMsg(OrderedDelivery.SequenceNumber);
/* Set destination context property to GateKeeper */
ProcMsg(OrderedDelivery.PropertySchema.Destination) = "Gatekeeper";
The MessageBox database publishes these stamped messages to orchestrations that subscribe to them. Even though the messages are published to the business orchestrations in the order received at the receive adapter, the message order may be scrambled after they are processed by business orchestrations due to the size of the messages and actual processing time. As a result, the messages are published to the MessageBox database out of order compared to the message order at the receive location.
The gatekeeper orchestration's main function is to act as a resequencer, gathering messages and ensuring outgoing order based on the stamped sequence number in each message. This is a resequencer pattern.
The gatekeeper is a singleton orchestration that is activated by the first message that matches the gatekeeper’s subscription to the OrderedDelivery.PropertySchema.Destination correlation ID. In this case the gatekeeper is initialized with the correlation value of “Gatekeeper” and all messages with this value will be routed to this singleton.
The gatekeeper orchestration performs a straightforward task of resequencing messages based on the receive order defined by the stamped sequence number. Every message received by the gatekeeper is compared with NextSequence to see if it should be sent out next. Messages that are not yet ready to be sent are added to an in-memory queue, implemented as a sorted list of key-value pairs of sequence number and message references. Note that the outgoing sequence number is maintained in memory. The durability of the sequence number is guaranteed by the fact that long-running orchestration states are durable in BizTalk Server. That is, BizTalk Server dehydrates running orchestrations when the server goes down, and restores the persisted state when the orchestrations are rehydrated.
Using a Ticket Dispenser with the Custom Pipeline
The primary function of the custom pipeline component is to stamp a sequence number as a context property in the message. The pipeline component goes out to the database table MyDB.TicketDispenser to retrieve the next available sequence that it will use to stamp the message. A database table is used because message order must be preserved across server shutdowns.
Performance is affected every time a message needs to be stamped, although this is the most straightforward way for order to be maintained during shutdowns. There are, however, ways to reduce the effect on performance. One way is to cache the sequence number in memory and periodically persist it, although this introduces timing windows where the sequence number may not be maintained across shutdowns. A more effective way is to update sequence numbers using SQL rowlock to provide the necessary atomicity for data integrity.
It is important that the ticket dispenser be placed after the pipeline disassembler in case the disassembler splits the message into one or more messages. (For example, where message 1.0 going into a disassembler comes out with messages 1.1 and 1.2.)
The acquisition of the sequence number is done in the Execute method of the pipeline component. As such, it is necessary to participate in the pipeline component’s transaction for the thread of execution. Stored procedures are used to perform the database updates called from the Execute method as follows:
CREATE PROCEDURE UpdateSequence
@ReceiveLocationName as nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
declare @val int
declare @lastSeq int
declare @msgInQue int
UPDATE TicketDispenser set SequenceNumber = SequenceNumber + 1
FROM TicketDispenser WITH (ROWLOCK)
WHERE ReceiveLocationName = @ReceiveLocationName
if @@ROWCOUNT=0
BEGIN
SET @val=1
SET @lastSeq=0
SET @msgInQue=0
INSERT into TicketDispenser(ReceiveLocationName, SequenceNumber, LastSequenceSent, MsgInQueue) values(@ReceiveLocationName, @val, @lastSeq, @msgInQue)
END
SELECT @val=SequenceNumber from TicketDispenser with (ROWLOCK) WHERE ReceiveLocationName = @ReceiveLocationName
SELECT @val
END
GO
MyDB.TicketDispenser
|
ReceiveLocationName
|
SequenceNumber
|
LastSequenceSent
|
MsgInQueue
|
|---|
|
ReceiveLocation1
|
888
|
0
|
0
|
This is the persistent table for sequence numbers stored in SequenceNumber which corresponds to a ReceiveLocationName of your choosing.
The TicketDispenser is created using the CreateTicketDispenserTable.sql script.
CREATE TABLE [TicketDispenser] (
[SequenceNumber] [int] NULL ,
[LastSequenceSent] [int] NULL ,
[MsgInQueue] [int] NULL ,
[ReceiveLocationName] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
CONSTRAINT [PK_TicketDispenser] PRIMARY KEY CLUSTERED
(
[ReceiveLocationName]
) ON [PRIMARY]
) ON [PRIMARY]
GO
Using a Gatekeeper Orchestration
The function of the gatekeeper orchestration is to resequence messages according to the order that they arrive to ensure they are sent out in the same order. The basic algorithm is to queue out-of-order messages that are not ready to be sent out. When a message is received its sequence number is checked to see if it is equal to the last sequence number sent plus one. The message is sent out if this is the case; otherwise the message is queued. The sequence number of queued messages is maintained in memory. This is advantageous because orchestration state is preserved across orchestrations during dehydration and rehydration. Thus, message order is maintained during system restart without maintaining a separate database by leveraging orchestration state management.
The gatekeeper orchestration subscribes to the correlation initialized by the business orchestrations so that the MessageBox database publishes all post-processed messages to it. Figure 2 shows an activating receive shape (FirstBizMsg) where the OrderedDelivery.PropertySchema.Destination correlation field is initialized by the activating message. The other receive shapes will follow on this correlation to receive all messages that have been post-processed by the business orchestrations.
Figure 2. Gatekeeper First Message.gif)
A .NET WaitQueue class implements a sorted list to store XLANG/s message references (excluding the body payload) that is not yet ready to be sent. It is important that this class perform the necessary XLANG/s message lifetime management of reference counting to keep track of whether there is any object referencing a particular message. Normally the XLANG/s runtime performs message reference counting for messages that are part of the orchestration and that are stored in the MessageBox database. In this case, the message is being referenced in .NET code outside of the runtime.
The .NET WaitQueue class method RemoveMessage() is called when a message is found in the queue that is the next sequenced message to be sent out. The RemoveMessage() calls the dispose method of the message to signal to the runtime that this instance is done with the message.
Figure 3 shows the main loop of the gatekeeper orchestration that receives and processes all messages after the first activating message. There are two branches to this loop. One branch checks for message order before the message is sent out and the other branch handles the control message to shut down this singleton.
Figure 3. Gatekeeper Main Loop.gif)
Message order is determined by inspecting the stamped sequence number of each message against the sequence number of the last message sent. There is additional logic to check the WaitQueue every time a message is sent. In other words, every time a message is sent out it checks the WaitQueue to see if the next sequence number is queued. Since this is a sorted list it allows potentially for a batch of sequential messages to all be sent one after another. For example, if the next message to be sent in the sequence is 55, while messages 56 through 100 are queued up, when message 55 eventually arrives and is sent out, the logic path will ensure that messages 56 through 100 are sent out in quick succession to flush the queue.
This gatekeeper orchestration is able to respond to control messages to shut down. Upon receiving the message to shut down it checks to see if there are messages in the WaitQueue. If there are none it writes out the last sequence sent before it shuts down. This value is used to initialize the next sequence number when the gatekeeper is reactivated so that order can be maintained across shutdowns of the singleton gatekeeper. If there are messages in the WaitQueue the gatekeeper suspends and waits for user intervention.
Error Handling
We will consider a couple of error conditions and possible ways to handle them.
-
Hole in the sequence message: This is a scenario where the gatekeeper is waiting for a message with a specific sequence number and the message never arrives. This can happen if a business process consumes one of the messages and does not forward it. There is no easy algorithmic solution to this problem. Depending on the application, a practical approach is to skip the message after a designated time-out value, and to log the event. To accommodate for known latency a retry count can be used in conjunction with the time-out value before a message is skipped.
-
Messages held in the gatekeeper orchestration when it is shutting down: Since XLANG/s messages have to exist within the context of an orchestration, routing messages to a database does not work because the message lifetime context is lost and the runtime does not provide a mechanism to load stored messages. It is possible to route messages to a surrogate orchestration while the gatekeeper restores itself (where it was perhaps shut down due to memory pressure), and to receive the messages back when it comes back up. The restored gatekeeper also reloads the WaitQueue together with the rest of the variable states like next sequence number. This solution, however, is fairly complicated to use as a general solution to handle all such situations.
Design Tradeoffs
We will consider some tradeoffs for the design and implementation described in this white paper.
-
Why is the gatekeeper implemented as an orchestration instead of a send custom pipeline component?
One clear reason against a pipeline implementation is a deadlock that can occur when the pipeline instance gets throttled while BizTalk Server is waiting for a message with the next allowed sequence. Once throttled even when the specific message finally arrives, BizTalk Server does not give the thread of execution to the pipeline because it has been throttled out already. This happens when BizTalk Server is under high memory pressure and the message queue backlog is high.
By contrast, when you use an orchestration you can leverage the built-in memory management mechanisms in dehydration and rehydration to manage state. You also do not have to maintain a separate persistent store required by a pipeline component for system recovery. Furthermore, orchestrations provide flexibility to model business semantics in the orchestration business logic. An example is correlating messages and patient IDs.
-
Database configuration
The custom pipeline component uses a database that is configured in the BizTalk Server btsntsvc.exe.config file. Optionally, the SSO database can be used to allow central configuration when deploying to a server farm. This enhances security with an encrypted connect string and password.
-
Content-based ordering (CBO)
The ticket dispenser custom pipeline component may be implemented with an orchestration doing in-memory sequence stamping. There is no need to maintain a user database for sequence number persistence, as this is taken care of by orchestration state management in the runtime.
-
Gatekeeper
The advantages of implementing the resequencer (gatekeeper) as an orchestration are that it increases performance due to no database cost, and because of the ability to leverage the orchestration correlation feature for routing.
-
Ticket Dispenser
The disadvantages of implementing the ticket dispenser as an orchestration as opposed to a custom pipeline component, as described in this paper, is that it increases known singleton state bloat and management issues.
Advanced Modeling
The following considerations explain more advanced implementations of a gatekeeper orchestration to further improve ordered delivery for specific business requirements.
-
Multiple Receive Locations
The sample in this white paper demonstrates ordered delivery between a pair of receive and send ports. However, it is common to have more than one pair of receive and send ports. As previously discussed, it is possible to support multiple pairs of ports in the TicketDispenser table, where each row represents a pair of ports. To fully support this feature there needs to be a gatekeeper singleton for each pair of ports. There is an elegant way to achieve this by making the correlation value of each singleton the receive location name.
-
Relative Ordering
It can be argued that businesses need relative ordering of all messages pertaining to a single entity such as a patient ID more than absolute ordering of all messages going through the system for all patients.
This can be easily done by activating a singleton gatekeeper for each patient and correlating messages based on patient ID. The added benefit of the WaitQueue is smaller since it is only dealing with messages for one patient. The lifetime of the orchestration is defined by the patient check-in that activates the singleton and patient check-out which ends the orchestration. This pattern is suitable for other applications like stock trades, processing a buy or sell order through order confirmation, selling or buying orders, funds settlement, and closing the order.
-
Sequence Wraparound
The size of the sequence number in this implementation is an integer. The sequence number will at some point wrap around back to one. This implementation assumes the custom pipeline and ticket dispenser orchestration will be recycled before reaching the integer size boundary before wraparound occurs.
A more robust design is to add a batch field to the context property schema which contains a GUID. The ticket dispenser acquires a batch GUID and stamps messages with sequence numbers within the integer range. When the maximum integer is reached a new GUID is acquired to denote a new batch and the sequence number starts at one. Consequently, the gatekeeper orchestration has to compute message sequence numbers within the context of its batch.
In the custom pipeline, when the maximum size in the integer range is reached, a new batch GUID is generated to be used in the batch field and the sequence number begins with one. It is not necessary to synchronize sequence numbers between the ticket dispenser and gatekeeper in this scheme. The custom pipeline stamps batch and sequence numbers and the gatekeeper orchestration processes the next sequence based on the sequence number in each batch.
Performance
This section contains information about performance and system configuration for the solution. For background information about BizTalk Server performance, see http://go.microsoft.com/fwlink/?LinkId=93137.
Maximum sustainable throughput
-
4.3 msg/sec in a single-computer configuration
-
11 msg/sec in a five-computer configuration
Hardware configurations
-
Dual Proc Xeon 3.40 GHz, 2 GB of RAM
-
HD (SCSI)
Message size
-
Message size varied between 0.5 KB and 18 KB with an average message size of 1 KB.
Software configuration
-
Microsoft Windows Server 2003, SQL Server™ 2005
-
BizTalk Server 2006 R2
MSMQ adapter configuration
-
Receive
-
Batch size 1
-
Transactional
-
Ordered processing
-
Send
-
Transactional
-
Retry count 0
-
Ordered Delivery
-
Stop sending on message failure
-
Enabled routing for failed messages
BizTalk Server configuration
-
Two hosts - one to host the gatekeeper and other for the MSMQ send handler
-
Single pair of transactional private MSMQ queues to send and receive test data
Multicomputer Configuration (five computers)
-
SQL Server 1: BizTalk Server databases
-
SQL Server 2: MyDB
-
Receive computer: local MSMQ receive queue, MSMQ receive handler, BizTalkServerApplication host instance, MyBiz orchestrations
-
GateKeeper computer: GKHost BizTalk host instance, MSMQ send handler, Gatekeeper orchestration
-
Monitoring computer: Send test data and running Perfmon counters/logs