简体   繁体   English

Azure Topics辅助角色在60秒后停止处理消息

[英]Azure Topics worker role stops processing message after 60 seconds

We have a cloud service using a worker role to process messages it receives from a Topic set up on Azure Service Bus. 我们有一个使用辅助角色的云服务,以处理从Azure服务总线上设置的主题接收到的消息。

The message itself seems to arrive intact and is usually received and processed correctly. 消息本身似乎完好无损,通常可以正确接收和处理。 In some instances however, the message seems to stop processing (Logging abruptly ends and no more references to the message being processed are seen in our WadLogsTable). 但是,在某些情况下,该消息似乎停止处理(记录突然结束,并且在WadLogsTable中看不到对正在处理的消息的更多引用)。 From my research, this might be happening due to the worker role keeping its connection open and idle for longer than seconds. 根据我的研究,这可能是由于工作者角色将其连接保持打开和空闲状态超过几秒钟而发生的。 How would I go about preventing these long-to-process messages from being abandoned? 我将如何防止这些长时间处理的消息被丢弃?

The code for our worker role is below. 我们的工作者角色的代码如下。

public class WorkerRole : RoleEntryPoint
{
    private static StandardKernel _kernel;
    private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);
    private BaseRepository<CallData> _callDataRepository;
    private BaseRepository<CallLog> _callLogRepository;

    private SubscriptionClient _client;
    private NamespaceManager _nManager;
    private OnMessageOptions _options;
    private BaseRepository<Site> _siteRepository;

    public override void Run()
    {
        try
        {
            List<CallInformation> callInfo;
            Trace.WriteLine("Starting processing of messages");

            // Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.

            _client.OnMessage(message =>
            {
                // Process message from subscription.
                Trace.TraceInformation("Call Received. Ready to process message ");
                message.RenewLock();
                callInfo = message.GetBody<List<CallInformation>>();
                writeCallData(callInfo);


                Trace.TraceInformation("Call Processed. Clearing from topic.");
            }, _options);
        }
        catch (Exception e)
        {
            Trace.TraceInformation("Error: " + e.Message + "---" + e.StackTrace);
        }
    }

    private void writeCallData(List<CallInformation> callList)
    {
        try
        {
            Trace.TraceInformation("Calls received: " + callList.Count);
            foreach (var callInfo in callList)
            {
                Trace.TraceInformation("Unwrapping call...");
                var call = callInfo.CallLog.Unwrap();
                Trace.TraceInformation("Begin Processing: Local Call " + call.ID + " with " + callInfo.DataPoints.Length + " datapoints");
                Trace.TraceInformation("Inserting Call...");
                _callLogRepository.ExecuteSqlCommand(/*SNIP: Insert call*/);
                    Trace.TraceInformation("Call entry written. Now building datapoint list...");
                    var datapoints = callInfo.DataPoints.Select(datapoint => datapoint.Unwrap()).ToList();
                    Trace.TraceInformation("datapoint list constructed. Processing datapoints...");
                    foreach (var data in datapoints)
                    {
                        /*SNIP: Long running code. Insert our datapoints one at a time. Sometimes our messages die in the middle of this foreach. */
                    }
                    Trace.TraceInformation("All datapoints written for call with dependable ID " + call.Call_ID);
                Trace.TraceInformation("Call Processed successfully.");
            }
        }
        catch (Exception e)
        {
            Trace.TraceInformation("Call Processing Failed. " + e.Message);
        }
    }

    public override bool OnStart()
    {
        try
        {
            var connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
            _nManager = NamespaceManager.CreateFromConnectionString(connectionString);
            _nManager.Settings.OperationTimeout = new TimeSpan(0,0,10,0);
            var topic = new TopicDescription("MyTopic")
            {
                DuplicateDetectionHistoryTimeWindow = new TimeSpan(0, 0, 10, 0),
                DefaultMessageTimeToLive = new TimeSpan(0, 0, 10, 0),
                RequiresDuplicateDetection = true,
            };
            if (!_nManager.TopicExists("MyTopic"))
            {
                _nManager.CreateTopic(topic);
            }
            if (!_nManager.SubscriptionExists("MyTopic", "AllMessages"))
            {
                _nManager.CreateSubscription("MyTopic", "AllMessages");
            }
            _client = SubscriptionClient.CreateFromConnectionString(connectionString, "MyTopic", "AllMessages",
                ReceiveMode.ReceiveAndDelete);
            _options = new OnMessageOptions
            {
                    AutoRenewTimeout = TimeSpan.FromMinutes(5),

            };
            _options.ExceptionReceived += LogErrors;
            CreateKernel();

            _callLogRepository.ExecuteSqlCommand(/*SNIP: Background processing*/);
        }
        catch (Exception e)
        {
            Trace.TraceInformation("Error on roleStart:" + e.Message + "---" + e.StackTrace);
        }
        return base.OnStart();
    }

    public override void OnStop()
    {
        // Close the connection to Service Bus Queue
        _client.Close();
        _completedEvent.Set();
    }

    void LogErrors(object sender, ExceptionReceivedEventArgs e)
    {
        if (e.Exception != null)
        {
            Trace.TraceInformation("Error: " + e.Exception.Message + "---" + e.Exception.StackTrace);
            _client.Close();
        }
    }

    public IKernel CreateKernel()
    {
        _kernel = new StandardKernel();
        /*SNIP: Bind NInjectable repositories */
        return _kernel;
    }
}

Your Run method does not go on indefinitely. 您的Run方法不会无限期地继续下去。 It should look like this: 它看起来应该像这样:

public override void Run()
{
   try
   {
      Trace.WriteLine("WorkerRole entrypoint called", "Information");
      while (true)
      {
         // Add code here that runs in the role instance
      }

   }
   catch (Exception e)
   {
      Trace.WriteLine("Exception during Run: " + e.ToString());
      // Take other action as needed.
   }
}

Taken from the docs : 取自文档

The Run is considered the Main method for your application. Run被认为是应用程序的Main方法。 Overriding the Run method is not required; 不需要覆盖Run方法; the default implementation never returns. 默认实现永远不会返回。 If you do override the Run method, your code should block indefinitely. 如果您确实覆盖了Run方法,则您的代码应无限期地阻塞。 If the Run method returns, the role is automatically recycled by raising the Stopping event and calling the OnStop method so that your shutdown sequences may be executed before the role is taken offline. 如果Run方法返回,则通过引发Stopping事件并调用OnStop方法来自动回收角色,以便可以在使角色脱机之前执行关闭序列。

TheDude's response is very close to the correct answer! TheDude的回答非常接近正确答案! It turns out he's right that the run method needs to stay alive instead of returning immediately. 事实证明,他说的对,run方法需要保持活动状态而不是立即返回。 With Azure Service Bus's message pump mechanism though, you can't place the _client.onMessage(...) inside a while loop, as this results in an error (The message pump has already been initialized). 但是,借助Azure Service Bus的消息泵机制,您无法将_client.onMessage(...)放入while循环内,因为这会导致错误(消息泵已被初始化)。

What actually needs to happen is aa manual reset event needs to be created before the worker role begins executing, and then waited after the message pump code is executed. 实际需要发生的是,需要在辅助角色开始执行之前创建一个手动重置事件,然后在执行消息泵代码之后等待。 For documentation on ManualResetEvent, see https://msdn.microsoft.com/en-us/library/system.threading.manualresetevent(v=vs.110).aspx . 有关ManualResetEvent的文档,请参见https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent(v=vs.110).aspx Additionally, the process is described here: http://www.acousticguitar.pro/questions/607359/using-queueclient-onmessage-in-an-azure-worker-role 此外,此处介绍了该过程: http : //www.acousticguitar.pro/questions/607359/using-queueclient-onmessage-in-an-azure-worker-role

My final worker role class looks like this: 我的最终工作者角色类如下所示:

public class WorkerRole : RoleEntryPoint
{
    private static StandardKernel _kernel;
    private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);
    private BaseRepository<CallLog> _callLogRepository;

    private SubscriptionClient _client;
    private MessagingFactory _mFact;
    private NamespaceManager _nManager;
    private OnMessageOptions _options;

    public override void Run()
    {
        ManualResetEvent CompletedEvent = new ManualResetEvent(false);
        try
        {
            CallInformation callInfo;
            // Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
            _client.OnMessage(message =>
            {
                // Process message from subscription.
                Trace.TraceInformation("Call Received. Ready to process message " + message.MessageId);
                callInfo = message.GetBody<CallInformation>();
                WriteCallData(callInfo);

                Trace.TraceInformation("Call Processed. Clearing from topic.");
            }, _options);
        }
        catch (Exception e)
        {
            Trace.TraceInformation("Error: " + e.Message + "---" + e.StackTrace);
        }
        CompletedEvent.WaitOne();
    }

private void writeCallData(List<CallInformation> callList)
{
    try
    {
        Trace.TraceInformation("Calls received: " + callList.Count);
        foreach (var callInfo in callList)
        {
            Trace.TraceInformation("Unwrapping call...");
            var call = callInfo.CallLog.Unwrap();
            Trace.TraceInformation("Begin Processing: Local Call " + call.ID + " with " + callInfo.DataPoints.Length + " datapoints");
            Trace.TraceInformation("Inserting Call...");
            _callLogRepository.ExecuteSqlCommand(/*SNIP: Insert call*/);
                Trace.TraceInformation("Call entry written. Now building datapoint list...");
                var datapoints = callInfo.DataPoints.Select(datapoint => datapoint.Unwrap()).ToList();
                Trace.TraceInformation("datapoint list constructed. Processing datapoints...");
                foreach (var data in datapoints)
                {
                    /*SNIP: Long running code. Insert our datapoints one at a time. Sometimes our messages die in the middle of this foreach. */
                }
                Trace.TraceInformation("All datapoints written for call with dependable ID " + call.Call_ID);
            Trace.TraceInformation("Call Processed successfully.");
        }
    }
    catch (Exception e)
    {
        Trace.TraceInformation("Call Processing Failed. " + e.Message);
    }
}

public override bool OnStart()
{
    try
    {
        var connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
        _nManager = NamespaceManager.CreateFromConnectionString(connectionString);
        _nManager.Settings.OperationTimeout = new TimeSpan(0,0,10,0);
        var topic = new TopicDescription("MyTopic")
        {
            DuplicateDetectionHistoryTimeWindow = new TimeSpan(0, 0, 10, 0),
            DefaultMessageTimeToLive = new TimeSpan(0, 0, 10, 0),
            RequiresDuplicateDetection = true,
        };
        if (!_nManager.TopicExists("MyTopic"))
        {
            _nManager.CreateTopic(topic);
        }
        if (!_nManager.SubscriptionExists("MyTopic", "AllMessages"))
        {
            _nManager.CreateSubscription("MyTopic", "AllMessages");
        }
        _client = SubscriptionClient.CreateFromConnectionString(connectionString, "MyTopic", "AllMessages",
            ReceiveMode.ReceiveAndDelete);
        _options = new OnMessageOptions
        {
                AutoRenewTimeout = TimeSpan.FromMinutes(5),

        };
        _options.ExceptionReceived += LogErrors;
        CreateKernel();

        _callLogRepository.ExecuteSqlCommand(/*SNIP: Background processing*/);
    }
    catch (Exception e)
    {
        Trace.TraceInformation("Error on roleStart:" + e.Message + "---" + e.StackTrace);
    }
    return base.OnStart();
}

public override void OnStop()
{
    // Close the connection to Service Bus Queue
    _client.Close();
    _completedEvent.Set();
}

void LogErrors(object sender, ExceptionReceivedEventArgs e)
{
    if (e.Exception != null)
    {
        Trace.TraceInformation("Error: " + e.Exception.Message + "---" + e.Exception.StackTrace);
        _client.Close();
    }
}

public IKernel CreateKernel()
{
    _kernel = new StandardKernel();
    /*SNIP: Bind NInjectable repositories */
    return _kernel;
}

} }

You'll notice the presence of the ManualResetEvent and the invocation of WaitOne() at the end of my Run method. 您会在Run方法的结尾注意到ManualResetEvent的存在和WaitOne()的调用。 I hope someone finds this helpful! 我希望有人觉得这有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM