如何使用 ServiceStack RabbitMQ RPC 从异常中恢复

How to recover from an exception with ServiceStack RabbitMQ RPC

在 ServiceStack Web 服务项目中给出以下代码:

public object Post(LeadInformation request)
{
    if (request == null) throw new ArgumentNullException("request");

    try
    {
        var msgId = Guid.NewGuid();
        var profiler = Profiler.Current;
        using (profiler.Step("Direct Api LeadInformation POST {0}".Fmt(msgId)))
        {

            var domainRequest = Mapper.Map<Leads.ServiceModel.Api.DirectApi.LeadInformationInfo>(request);

            LeadInformationResponse response;
            using (var client = base.MessageFactory.CreateMessageQueueClient())
            {
                var replyToMq = client.GetTempQueueName();
                using (profiler.Step("request message {0}".Fmt(msgId)))
                {
                    var requestMsg = new Message<Leads.ServiceModel.Api.DirectApi.LeadInformationInfo>(domainRequest)
                    {
                        ReplyTo = replyToMq,
                        Id = msgId,
                    };
                    client.Publish<Leads.ServiceModel.Api.DirectApi.LeadInformationInfo>(requestMsg);    
                }

                using (profiler.Step("response message {0}".Fmt(msgId)))
                {
                    var timeOut = TimeSpan.FromMilliseconds(2000);
                    // IMessageQueueClient.Get sleeps the thread for 100ms, lets wait for a total of x seconds

                    var responseMsg = client.Get<Leads.ServiceModel.Api.DirectApi.LeadInformationInfoResponse>(replyToMq, timeOut);

                    var domainResponse = responseMsg.GetBody();

                    if (domainResponse.ResponseStatus != null)
                    {
                        client.Nak(responseMsg, false);
                    }
                    client.Ack(responseMsg);
                    response = Mapper.Map<LeadInformationResponse>(domainResponse);
                }
            }
            return response;
        }

    }
    catch (Exception exception)
    {
        _log.Error(exception);
        throw;
    }         
}

还有一个 windows 服务托管 ServiceStack(2 个版本,相同的结果):

版本 1

在另一个进程中调用 Web 服务可能会死掉并且return null 或异常

mqServer.RegisterHandler<LeadInformationInfo>(m =>
{
    try
    {
        repository.SaveMessage(m as Message);
        LeadInformationInfo response;
        using (var client = new JsonServiceClient(settingsFactory.GetMasterSetting("ProcessorApi:baseUri")))
        {
            response = client.Post<LeadInformationInfo>(m.GetBody());
        }
        return response; // will cause RabbitMQ to hang if null
    }
    catch (Exception exception)
    {
        _log.Error("RegisterHandler<LeadInformationInfo>", exception);
        throw;
    }
}, 1);

版本 2

调用 in-process

的服务
mqServer.RegisterHandler<LeadInformationInfo>(m =>
{
    var db = container.Resolve<IRepository>();
    db.SaveMessage(m as Message);
    var response = ServiceController.ExecuteMessage(m);
    return response; // will cause RabbitMQ to hang if null
}, 4);

您会注意到,如果您 return null 在处理过程中的某处模拟 NullReferenceException,则 RabbitMQ 临时队列只会保持 'running' 状态,而 'idle' ,如图所示,它保持挂起状态。

队列将无限期地保持这种状态,唯一的补救方法是回收 RabbitMQ windows 服务,或者托管进程,这两种方式在生产环境中都无法正常工作。我试过设置超时,但在这种情况下似乎没有按预期工作。

如何从这个问题中可靠地恢复?

谢谢, 斯蒂芬

响应为 null 时的行为,由于服务的典型响应类型未知,Request DTO is published to the .out MQ

此行为还包括客户端 ReplyTo 请求,但后来更改了它 in this commit 以将请求 DTO 发布回客户端 ReplyTo MQ。

虽然此更改现在应清除客户端创建的独占临时回复 MQ,但这意味着仅返回 Request DTO 而不是 客户端通常期望的响应 DTO。此行为在这些 null Response MQ Tests 中可见,如果您的处理程序返回空响应 DTO,则可以避免此行为。虽然正常行为是针对异常气泡并在请求 DLQ 中发布。

null 响应发布回 ReplyMQ 的更改可从 v4.0.37+ 获得,现在是 available on MyGet