改进SQL Server服务代理的性能


通过重用服务代理会话能够大大提升服务代理的性能。

  为每一个消息创建和关闭会话需要的开销大约是4X,而对于接收消息的性能提升大约能达到10X。Remus Rusanu在他发布在Reusing Conversations的博客上,谈到这个估值可能更大,并且还提出了一种重用会话的解决方案。

  我喜欢Remuss的解决方案,但其中有一个问题:我不愿意为每一个SPID创建不同的会话。如果我使用这种方法,就会有大量的会话打开,这样我还需要去关闭它们。在应用中,许多问题都可能触发一个服务代理消息的发送。而且通常情况下都会有大量的线程在同一时间对数据库进行访问。这就要求我们在实际环境使用Remuss的方法之前要对它进行优化,使之更灵活。我的想法是为我们应用中的每一个进程创建一个会话。并且在一定周期内,有一些会话会被关闭,也有些新的会话被创建。

  首先我先介绍一些关于安装服务代理的背景知识。我已经有2个队列和2个服务。每一个服务都在它自己的队列中。同时我有一个专门的消息类型,它用于将消息从源发送到目标位置。

  我的消息类型是MT_ObjectDelete,
  我的契约:CT_ObjectDelete,
  我的队列:Q_ObjectDelete_Source和Q_ObjectDelete_Destination,
  我的服务:Svc_ObjectDelete_Source和Svc_ObjectDelete_Destination,  
  
  另外我的另一个消息类型是MT_ConversationSwitch,它也是契约CT_ObjectDelete的一部分。

  为了存储我将生成的会话句柄,我会使用现有一个保存各种配置信息的表Setting。我已经添加了一些新的会话终端记录到表中。其中在Setting表中的查询名称是SSB_Session_Delete。我使用GetSystemSettingValue方法来查询Setting表得到配置值。发送数据的存储过程是这样的:


Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->CREATE PROCEDURE [dbo].[SendDelete]
@FileName varchar(1024)
AS
DECLARE @DialogHandle uniqueidentifier,
@msg XML,
@date nvarchar(100)
BEGIN
IF @msg IS NOT NULL
BEGIN
    SET @DialogHandle = cast(dbo.GetSystemSettingValue('SSB_Session_Delete') as uniqueidentifier)
    IF CAST(RAND()*100 AS INT) = 0 OR @DialogHandle IS NULL
BEGIN
IF @DialogHandle IS NOT NULL
SEND ON CONVERSATION @DialogHandle
MESSAGE TYPE [MT_ConversationSwitch]

BEGIN DIALOG CONVERSATION @dialogHandle
FROM SERVICE [Svc_ObjectDelete_Source]
TO SERVICE Svc_ObjectDelete_Destination
ON CONTRACT [CT_ObjectDelete_Multi];
        UPDATE Setting
SET DefaultValue = @DialogHandle
WHERE SettingName = SSB_Session_Delete;
END;
SEND ON CONVERSATION @dialogHandle
MESSAGE TYPE [MT_ObjectDelete]
(@msg)

IF @DialogHandle <> cast(dbo.GetSystemSettingValue(SSB_Session_Delete) as uniqueidentifier)
SEND ON CONVERSATION @DialogHandle
MESSAGE TYPE [MT_ConversationSwitch]
END
END
GO
  你可以从代码中看到,我通过查询获取当前的会话句柄。如果没有找到句柄,我会创建一个新句柄并将句柄值保存到我的Setting表中。如果CAST(RAND()*100 AS INT)返回0,我会使用现在会话中的MT_ConversationSwitch消息类型发送一条消息,它将触发接收程序的逻辑,终止该会话。在我终止该会话后,我再创建新的会话并将它存储在数据库中。

  然后我在该会话句柄上发送一条消息。接着我会再次检查Setting表,以确保我使用的值与表中的值是相匹配的。如果不匹配,我会认为另一个线程已经在相同的代码块中运行,然后关闭该会话,因为我不希望该会话继续存在。
我的接收程序非常简单:


Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->CREATE PROCEDURE [dbo].[util_ProcessDelMonitorData]
@MsgToRead INT = 1000
AS
DECLARE @message_type_name sysname;
DECLARE @message_body VARBINARY(max)
DECLARE @msgTable TABLE
(
message_body VARBINARY(max),
[conversation_handle] UNIQUEIDENTIFIER
);
BEGIN
DECLARE @conversation_handle UNIQUEIDENTIFIER
WHILE 1=1
BEGIN
WAITFOR (RECEIVE TOP (1) @Message_Body = message_body,
@conversation_handle = [conversation_handle],
@message_type_name = message_type_name
FROM [Q_ObjectDelete_Destination]), TIMEOUT 1000
    IF @conversation_handle IS NULL
BEGIN
break
END
IF @message_type_name = MT_ConversationSwitch
END CONVERSATION @conversation_handle
    IF @message_body IS NOT NULL
BEGIN
INSERT INTO @msgTable
(message_body, [conversation_handle])
values
(@message_body, @conversation_handle)
END
    SET @conversation_handle = NULL
SET @message_type_name = NULL
END
/*Business Logic happens here*/
END
GO
  你可以看到,我会对于第一条消息做标准的接收。如果消息没有发现值,我会转而处理我之前存储在@msgTable表变量中的数据。

  如果消息类型message_type_name是MT_ConversationSwitch,我会对该会话做一个中止会话(END CONVERSATION)的操作。

  如果@message_body变量中有数据,我会将该值存储到表变量中,给后面的业务逻辑进行处理。

  另外在发送队列中还有一个激活的程序,它只是对它接收的每一个消息做简单的中止会话(END CONVERSATION)操作。

  我真心希望这个例子能对你有所帮助。如果你对此有任何问题,请提交你的问题,我将尽快地回复你。


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3