Udostępnij za pośrednictwem


Implement throughput control in BizTalk orchestration when calling long-running wcf/web service

There is a common scenario that sometimes we have to call some long-running wcf/web services through BizTalk orchestration but the backend wcf/web services cannot handle the concurrent requests sent from BizTalk. In this case, the built-in throttling feature is not an ideal way to go because it cannot guarantee the stability of outgoing load. One workable appraoch is to implement a 'Retry Pattern' in your orchestration to resend the message while a call is not successful. However a more straightforward design should be to implement a simple throughout control logic in ochestration:

1. Use a database table to log concurrent count of instances and the threshold. (for example: when the threshold is set to 4, it means a subsequent instance will always wait for the condition: (concurrent requests < 4) = true before sending the request message.

2. In the orchestration, before calling the send port, query database to check concurrent instance count first. If the concurrent volume < threshold, set concurrent count = concurrent count + 1 and then go ahead to call the send port. Otherwise, delay for 5 seconds and then check again in a loop.

3. When the call finishes or fails, set concurrent count = concurrent count - 1 .

4. In this case, ideally each time only 4 calls are fired to invoke the WCF service which implements the throughput control function as the customer needs.

5. Some difficulty of the implementation is we cannot put the DB check and update logic inside orchestration because BizTalk is a multi-threading server application. Otherwise you will see a batch of SELECT queries(15+) get executed at the same time and returns the same concurrent results. After that, a batch of UPDATE queries are execute to do the +1 action. Then the concurrent value will be far higher than the threshold setting which no longer makes sense. The solution is using a stored procedure to perform the concurrent volume checking and +1 action and set readcommited  lock on it.

Expression_1:

 

The Stored Procedure:

==================================

  
 USE [ThroughputControl]
 GO
 /****** Object: StoredProcedure [dbo].[controlConcurrent] Script Date: 03/20/2013 17:02:53 ******/
 SET ANSI_NULLS ON
 GO
 SET QUOTED_IDENTIFIER ON
 GO
 -- =============================================
 -- Author: <Author,,Name>
 -- Create date: <Create Date,,>
 -- Description: <Description,,>
 -- =============================================
 ALTER PROCEDURE [dbo].[controlConcurrent] 
 
 @returnvalue int = 0 out
 
 AS
 declare @concurrent int
 declare @threshold int 
 declare @retVal int
 
 BEGIN
 -- SET NOCOUNT ON added to prevent extra result sets from
 -- interfering with SELECT statements.
 
 
 exec @retVal = sp_getapplock 'Control', 'Exclusive', 'Session', 0 
 if (@retVal < 0 ) -- Not Granted. Some other thread is trying to cleanup this process
 BEGIN
 set @returnvalue = 1
 return @returnvalue
 END
 
 SET NOCOUNT ON;
 
 -- Insert statements for procedure here
 SELECT TOP 1 @concurrent = Concurrent_count, @threshold = Threshold from dbo.Control WITH(READCOMMITTED)
 
 If @concurrent < @threshold
 begin
 UPDATE Control SET Concurrent_count = (Concurrent_count + 1), Last_modified = GETDATE() where ID = 1
 set @returnvalue = 0
 end
 else 
 begin
 set @returnvalue = 1
 end
 
 return @returnvalue
 
 END
 exec sp_releaseapplock 'Control', 'Session'
  
 C# component to call SP: 
  ================================== 
  
  public int callSp(string spname)
 {
 
 SqlConnection sqlConnection1 = new SqlConnection(connectionString);
 SqlCommand cmd = new SqlCommand();
 int returnValue = 0;
 
 cmd.CommandText = spname;
 cmd.CommandType = CommandType.StoredProcedure;
 cmd.Connection = sqlConnection1; 
 
 
 cmd.Parameters.Add("@returnvalue", SqlDbType.Int).Direction = ParameterDirection.Output;
 
 sqlConnection1.Open();
 
 cmd.ExecuteScalar();
 
 returnValue = (int)cmd.Parameters["@returnvalue"].Value;
 
 sqlConnection1.Close();
 
 return returnValue;
 }
 
 Best regards,
 WenJun Zhang

Comments

  • Anonymous
    September 10, 2014
    Hi - Can you comment on how you implemented the "When the call finishes or fails, set concurrent count = concurrent count - 1". Does it need to call another stored proc with sp_getapplock  in a while loop to first get the lock and then call update or can we just update the table directly via a ADO.NET call without going through the while loop and stored proc? Would appreciate any feedback as I am currently looking at your solution for our implementation where we have same needs.Thanks.