【问题标题】:Service Broker messages start to get hung up after about a day大约一天后,Service Broker 消息开始挂断
【发布时间】:2013-07-11 18:15:14
【问题描述】:

我有一个使用 Service Broker 的应用程序是 SQL 2008。大约每天一次,数据库的性能开始受到明显影响,我确定这是因为 Service Broker。如果我使用以下命令硬重置所有代理连接:

ALTER DATABASE [RegencyEnterprise] SET OFFLINE WITH ROLLBACK IMMEDIATE
ALTER DATABASE [RegencyEnterprise] SET ONLINE

然后性能恢复正常,直到大约第二天。我还注意到,当性能很差时,运行以下查询会返回大量(目前大约 1000 个)卡在 STARTED_OUTBOUND 状态的对话:

SELECT * FROM sys.conversation_endpoints

此外,以下查询不会返回其中的任何条目:

SELECT * FROM sys.dm_qn_subscriptions
SELECT * FROM sys.transmission_queue

在此查询返回大量项目的情况下,性能似乎还不错。唯一出现问题的情况是存在 STARTED_OUTBOUND 的连接停留在此状态。

我对 SQL Server 2008 实例上的 Service Broker 所做的唯一配置是运行以下命令:

ALTER DATABASE RegencyEnterprise SET ENABLE_BROKER

翻阅 SQL 错误日志,我也发现了这个条目超过 1000 次:

07/11/2013 01:00:02,spid27s,Unknown,The query notification dialog on conversation handle '{6DFE46F5-25E9-E211-8DC8-00221994D6E9}.' closed due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-cb4e7a77-58f3-4f93-95c1-261954d3385a&apos; because it does not exist.</Description></Error>'.

我在整个日志中也看到这个错误十几次,但我相信我可以通过在数据库中创建一个主密钥来解决这个问题:

06/26/2013 14:25:01,spid116,Unknown,Service Broker needs to access the master key in the database '<Database name>'. Error code:26. The master key has to exist and the service master key encryption is required.

我认为这些错误的数量可能与停留在队列中的对话数量有关。这是我用来订阅查询通知的 C# 代码:

private void EstablishSqlConnection(
    String storedProcedureName,
    IEnumerable<SqlParameter> parameters,
    Action sqlQueryOperation,
    String serviceCallName,
    Int32 timeout,
    params MultipleResult[] results)
{
    SqlConnection storeConnection = (SqlConnection) ((EntityConnection) ObjectContext.Connection).StoreConnection;
    try
    {
        using (SqlCommand command = storeConnection.CreateCommand())
        {
            command.Connection = storeConnection;
            storeConnection.Open();

            SqlParameter[] sqlParameters = parameters.ToArray();
            command.CommandText = storedProcedureName;
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.AddRange(sqlParameters);

            if (sqlQueryOperation != null)
            {
                // Register a sql dependency with the SQL query.
                SqlDependency sqlDependency = new SqlDependency(command, null, timeout);
                sqlDependency.OnChange += OnSqlDependencyNotification;
            }

            using (DbDataReader reader = command.ExecuteReader())
            {
                results.ForEach(result => result.MapResults(this, reader));
            }
        }
    }
    finally
    {
        storeConnection.Close();
    }
}

这是我处理通知的方式:

    public static void OnSqlDependencyNotification(object sender, SqlNotificationEventArgs e)
    {
        if (e.Info == SqlNotificationInfo.Invalid)
        {
            // If we failed to register the SqlDependency, log an error
            <Error is loged here...>

            // If we get here, we are not in a valid state to requeue the sqldependency. However,
            // we are on an async thread and should NOT throw an exception. Instead we just return
            // here, as we have already logged the error to the database. 
            return;
        }

        // If we are able to find and remove the listener, invoke the query operation to re-run the query.
        <Handle notification here...>
    }

有谁知道什么会导致代理的连接进入这种状态?或者我可以使用什么工具来试图找出导致这种情况的原因?我目前只有一个 Web 服务器注册到它的通知,所以我的场景并不太复杂。

更新:

好的,所以我从this post 确定错误“找不到远程服务......因为它不存在”是由于 SqlDependency 没有正确清理自身。服务结束后,代理仍在尝试向我的应用程序发送通知。所以现在,听起来我只需要找到一种方法来清除在调用 SqlDependency.Start() 之前我的应用程序启动时没有正确清理的任何内容,但除了我的原始方法之外,我还没有找到其他方法上面,这会使数据库脱机并且是不可接受的。有谁知道清理这个?

【问题讨论】:

  • 描述您的 Service Broker 配置。你有什么队列,你如何使用它们,另一端在哪里?等等……
  • 我只是使用默认值 - 我还没有创建任何自定义队列。我在我的 Web 应用程序中使用 C# SqlDependency 对象进行连接。是否有具体的配置信息会有所帮助?
  • 你能给我们看看C#代码吗?
  • 那么这些对话的队列是什么?弄清楚这一点,然后他们来自哪里?
  • @Rikalous,我在上面添加了我的 C# 代码。

标签: sql-server service-broker sqldependency query-notifications


【解决方案1】:

我找到了解决此问题的可接受方法。首先,我将我的代码从 SqlDependency 中迁移出来,现在我使用的是 SqlNotificationRequest。这样做可以防止代理队列和服务在意外时间被创建/销毁。

即使这样,当我的应用程序退出时,仍然有一些对话没有被标记为已关闭,因为设置通知的原始端点不再存在。因此,每次我的服务器重新初始化我的代码时,我都会清除现有的对话。

此调整已将我每天拥有的连接数从超过 1000 个(必须手动杀死它们)减少到始终最多大约 20 个。我强烈建议使用 SqlNotificationRequest 而不是 SqlDependency。

【讨论】:

    【解决方案2】:

    我找到了一种方法来清除卡住的对话。我检索仍然存在的所有生成的 SqlDependency 队列,并遍历不属于其中任何一个的对话并结束这些对话。下面是代码:

    SET NOCOUNT OFF;
    DECLARE @handle UniqueIdentifier
    DECLARE @count INT = 0
    
    -- Retrieve orphaned conversation handles that belong to auto-generated SqlDependency queues and iterate over each of them
    DECLARE handleCursor CURSOR
    FOR 
    SELECT [conversation_handle]
    FROM sys.conversation_endpoints WITH(NOLOCK)
    WHERE
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
    
    DECLARE @Rows INT
    SELECT @Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
    WHERE
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
    
    WHILE @ROWS>0
    BEGIN
        OPEN handleCursor
    
        FETCH NEXT FROM handleCursor 
        INTO @handle
    
        BEGIN TRANSACTION
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
    
            -- End the conversation and clean up any remaining references to it
            END CONVERSATION @handle WITH CLEANUP
    
            -- Move to the next item
            FETCH NEXT FROM handleCursor INTO @handle
            SET @count= @count+1
        END
    
        COMMIT TRANSACTION
        print @count
    
        CLOSE handleCursor;
    
        IF @count > 100000
        BEGIN
            BREAK;
        END
    
        SELECT @Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
        WHERE
            far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
            far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
    END
    DEALLOCATE handleCursor;
    

    【讨论】:

    • 附带说明:有一个较低级别的 C# API:SqlNotificaitonRequest,它允许您构建自己的“管道”(目标服务等)。这不是微不足道的,但您可以避免SqlDependency 引起的问题。
    • 是的,我确实找到了那个类,我正在考虑使用它。这将允许我定义自己的服务代理对象,而不是在每次建立新连接时自动生成它。
    • 好的 - 这看起来像定期做的痛苦。稍微回顾一下“思想栈”——你有没有实现任何代码来在你的客户端关闭时停用通知?
    • 我有,但如果应用程序意外结束,问题仍然存在。此外,SqlDependency 不允许您清理打开的对话 - 它们都需要关闭才能清理它们。这是因为它如何动态创建所有 Broker 实体。因此,我正在研究 SqlNotificationRequest。
    【解决方案3】:

    Started Outbound 表示“SQL Server 已为此对话处理了 BEGIN CONVERSATION,但尚未发送任何消息。” (来自在线图书) 您正在创建的对话似乎没有被使用,因此它们永远不会关闭。

    虽然不完全确定为什么会导致性能下降。

    【讨论】:

    • 这确实是有道理的,为什么他们会保持这种状态。不过这很奇怪,因为我的网络服务器只在进程启动时注册会话,并在进程结束时关闭它。我想如果引发了未处理的异常并且 IIS 重新启动了该进程,那么将启动超过 1 个对话,并且第一个对话将永远不会关闭......我同意你的观点,我不希望只有没有传递到的消息像这样影响性能。
    猜你喜欢
    • 1970-01-01
    • 2017-01-20
    • 1970-01-01
    • 2020-04-06
    • 2013-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多