我的服务堆栈服务中的异常未将邮件移至死信队列

时间:2018-08-09 10:26:51

标签: c# rabbitmq servicestack

我有一个带有标准服务堆栈RabbitMQ抽象的服务堆栈服务。 将为我的类型MyRequest自动创建消息队列,并且我已经设置了一种服务方法来处理来自MyRequest的请求。

我期望,如果我在此方法中引发异常,则消息将被放置在死信队列中。但是,它们只是从“入队”队列中删除,而不是进入死信队列

public class MyOtherService : AsmServiceBase
{
    public void Any(MyRequest request)
    {
        try
        {
            throw new InvalidOperationException("this is an invalid operation");
        }
        catch (Exception ex)
        {
            Console.Write("exceptions");
            throw;
        }
    }
}

[Route("/api/myrequest", "POST")]
public class MyRequest : HeliosRequestBase<MyResponse>
{
    public string Content { get; set; }
}

public class MyResponse : HeliosResponseBase
{

}

这是AppHost中的代码,它将MyRequest消息路由到我的服务方法:

RabbitMqServer mqServer = RabbitMqServerFactory
                         .GetRabbitMqServer(m_ServiceDiscovery).Result;
mqServer.RegisterHandler<MyRequest>(ExecuteMessage);
mqServer.Start();

有人可以解释我在做什么错吗?

更新 之前的队列图片 before starting the service, with 3 messages in q

及之后(请注意,将调用service方法,并为每个方法引发异常) after running my service

如果我将RegisterHandler行更改为这样

 mqServer.RegisterHandler<MyRequest>(
            x =>
            {
                try
                {
                    object resp =  ExecuteMessage(x);
                    //if I throw an exception here it will go to the DLQ
                    return resp;
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            );

我可以看到该异常包含在ExecuteMessage返回的对象中。我想也许我需要扔掉它?还是我在这里做错了什么?

更新2-问题的发布方式 在mythz示例的帮助下,我已经能够确定问题是由如何将消息发布到队列引起的。

我们这样称呼publish方法:

 public void PublishOneWay<TRequest>(TRequest request, string queueName, int timeoutInMilliseconds)
    {
        if (m_Disposed)
            throw new ObjectDisposedException("The service client has been disposed and cannot be used.");
        Message message = MakeMessage(request);


        //Updated to just send one-way messages - why bother enquing if we are never going to capture the callback/block on this thread?
        //MessagesToProcess.Add(new Tuple<string, Message, MessageDirection>(queueName, message, MessageDirection.OneWay));
        MqClient.SendOneWay(queueName, message); //will work if I send request instead of message

    }

问题似乎出在这段代码中,我们在其中构造了一个要发送的新Message对象。

 private Message MakeMessage<TRequest>(TRequest request)
    {
        Guid messageId = Guid.NewGuid();
        Message newMessage = new Message<TRequest>(request) { ReplyTo = ResponseQueueName, Id = messageId };

        newMessage.Meta = new Dictionary<string, string>();

        newMessage.Meta.Add(ServiceContextConstants.TrackingIdentifier, AsmServiceContext.TrackingIdentifierData?.Value);
        newMessage.Meta.Add(ServiceContextConstants.SessionContext, AsmServiceContext.SessionContextData?.Value);

        return newMessage;
    }

如果我发送原始请求对象,则异常将按您期望的那样发送到DLQ。如果我没有设置消息对象的ReplyTo属性,它也可以工作。

我想知道,是否需要在Message对象上设置任何属性来创建此行为?我可能无法设置ReplyTo属性,但不确定结果会更改多少代码。

1 个答案:

答案 0 :(得分:1)

编辑

如果您使用的是明确的ReplyTo地址,则任何错误都会发送到该ReplyTo地址而不是DLQ。

如果您的响应DTO具有ResponseStatus属性,则将在响应DTO的ResponseStatus中填充异常,否则,您可以使用通用的ErrorResponse DTO读取异常信息,例如:

var requestMsg = new Message<ThrowVoid>(request)
{
    ReplyTo = $"mq:{request.GetType().Name}.replyto"
};
mqProducer.Publish(requestMsg);

var msg = mqClient.Get<ErrorResponse>(requestMsg.ReplyTo, null);
mqClient.Ack(msg);

msg.GetBody().ResponseStatus.ErrorCode //= InvalidOperationException

我无法仅使用正常的ServiceStack类来解决此问题,如this commit所示,该类在所有MQ服务器中都能正常工作。

我在下面提取了使用ServiceStack's RabbitMQ Server的代码:

public class ThrowVoid
{
    public string Content { get; set; }
}

public class TestMqService : Service
{
    public void Any(ThrowVoid request)
    {
        throw new InvalidOperationException("this is an invalid operation");
    }
}

public class AppHost : AppSelfHostBase
{
     public AppHost(Func<IMessageService> createMqServerFn)
        : base(nameof(TestMqService), typeof(TestMqService).Assembly) {}

    public override void Configure(Container container)
    {
        var mqServer = new RabbitMqServer { RetryCount = 1 };
        container.Register<IMessageService>(c => mqServer);
        mqServer.RegisterHandler<ThrowVoid>(ExecuteMessage);

        AfterInitCallbacks.Add(appHost => mqServer.Start());
    }
}

消息发送的地方:

using (var mqFactory = appHost.TryResolve<IMessageFactory>())
{
    var request = new ThrowVoid { Content = "Test" };

    using (var mqProducer = mqFactory.CreateMessageProducer())
    using (var mqClient = mqFactory.CreateMessageQueueClient())
    {
        mqProducer.Publish(request);

        var msg = mqClient.Get<ThrowVoid>(QueueNames<ThrowVoid>.Dlq, null);
        mqClient.Ack(msg);

        Assert.That(msg.Error.ErrorCode, Is.EqualTo("InvalidOperationException"));
    }
}

鉴于存在的许多实现隐藏在您自己的自定义类后面,因此无法判断出您的问题是什么,但是我建议从上面的一个小独立示例开始,该示例确实起作用并缓慢添加您的自定义代码找出是什么原因造成的。

尽管我会recommend against using inheritance to hide properties in your DTOs,但DTO是用于定义服务合同的声明性模型,将其属性隐藏在继承后会使阅读它的人更难确切地知道每个服务接受和返回的内容。继承对于定义可重用的功能很有用,但是DTO是声明性的,应该没有实现,因此您基本上是在添加不必要的耦合来隐藏显式的Service Contract,这使得通过查看Service Contract来推断每个Service所做的工作变得更加困难