SpringFramework .NET - NMS消息传递的回滚/提交(ActiveMQ)事务

时间:2016-04-19 23:03:55

标签: .net spring transactions activemq nms

我正致力于将现有的Java / SpringBoot / ActiveMQ控制台应用程序(使用来自指定的ActiveMQ消息队列的消息并处理消息)转换为类似的C#.NET控制台应用程序。我已经为成功案例正确运行了,但是我在复制Java应用程序的行为时遇到问题,因为消息处理程序无法成功处理消息。

我试图复制的失败案例行为是rollback&当消息处理失败时,重新排队消息(返回到接收它的命名队列)。

在Java / SpringBoot中,我通过将事务配置添加到发生消息处理的适当类/方法来实现。对于失败的情况,我抛出一个RuntimeException(未经检查)并允许在Spring事务框架中处理这些抛出的异常(使用所有SpringBoot默认值进行此类异常处理),并且基于框架的事务回滚处理,消息是排队。我没有在我的Java应用程序代码逻辑中明确地进行任何回滚处理,而是允许框架默认流程处理所有这些。

我不熟悉Spring .NET模拟,以便在我的C#应用​​程序中实现这种自动回滚/重新排队行为。而且,到目前为止,我还没有能够复制这个。根据Spring .NET文档的第31.5.5节:

  

只需要在事务中调用消息侦听器   重新配置监听器容器。本地消息事务   可以通过设置SessionAcknowledgeMode属性来激活它   对于NMS属于枚举类型AcknowledgementMode,to   AcknowledgementMode.Transactional。每个消息监听器调用   然后将在活动的消息传递事务中使用消息进行操作   在侦听器执行失败的情况下,接收回滚

我在C#控制台应用的主要方法中遵循了上述处方:

static void Main(string[] args)
{
    var ctx = ContextRegistry.GetContext();
    var msgListenerContainer = ctx.GetObject<SimpleMessageListenerContainer>("MessageListenerContainer");
    msgListenerContainer.SessionAcknowledgeMode = AcknowledgementMode.Transactional;

    Console.ReadLine();
}

但是,我不清楚如何在我的C#控制台应用程序中触发侦听器执行失败。我尝试过抛出各种类型的异常(ApplicationException,Exception,NMSException),我看不到消息的重新排队。

任何启蒙都会受到高度赞赏。

仅供参考,这是其他密码逻辑和配置:

消息处理方法:

public void HandleMessage(Hashtable message)
{
    Logger.Debug("Entered HandleMessage");

    var ticketUuid = message["ti"] as string;
    if (ticketUuid == null) throw new ArgumentNullException("ticketUuid");
    var gameTitle = message["gt"] as string;
    if (gameTitle == null) throw new ArgumentNullException("gameTitle");
    var recallData = message["grd"] as string;
    if (recallData == null) throw new ArgumentNullException("recallData");
    Logger.Debug(string.Format("HandleMessage - ticketUuid={0} gameTitle={1} recallData={2}", ticketUuid,
        gameTitle, recallData));
    VideoRecordingService.RecordVideo(ticketUuid, gameTitle, recallData);
}

这是RecordVideo方法的一个版本,为了清晰起见,省略了许多不相关的代码逻辑:

    [Transaction]
    public async Task RecordVideo(string ticketId, string gameTitle, string gameRecallData)
    {
            <elided code>

            // start up the video recording app
            Recorder.PowerOn();

            // start up the Air Game Exe as a separate process
            RunAirGameProc(gameTitle);

            // for testing failed message processing
            throw new ApplicationException("forced exception");

            var videoBytes = File.ReadAllBytes(gameShareVideoFilename);
            MsgProducer.UpdateJobStatus(ticketId, TicketStatusEnum.Recorded, videoBytes);
            MsgProducer.UploadVideo(ticketId, videoBytes);
        }
        catch (Exception ex)
        {
            Logger.WarnFormat("Exception caught: {0}" + Environment.NewLine + "{1}", ex.Message, ex.StackTrace);
            throw new NMSException(ex.Message);
        }
        finally
        {
            // clean up files
            <elided>
        }
    }

并且,以下是控制台应用程序的相关Spring .NET配置:

<spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <description>Game Share Video Recording Service Spring IoC Configuration</description>
      <object name="MsgProducer"
              type="GameShare.VideoRecorder.MessageProducer, GameShare.VideoRecorder">
        <property name="NmsTemplate" ref="NmsTemplate" />
        <property name="JobInfoDestination">
          <object type="Apache.NMS.ActiveMQ.Commands.ActiveMQQueue, Apache.NMS.ActiveMQ">
            <constructor-arg value="gameShareJobInfo" />
          </object>
        </property>
        <property name="VideoUploadDestination">
          <object type="Apache.NMS.ActiveMQ.Commands.ActiveMQQueue, Apache.NMS.ActiveMQ">
            <constructor-arg value="gameShareVideoUpload" />
          </object>
        </property>
      </object>
      <object name="ConnectionFactory"
              type="Spring.Messaging.Nms.Connections.CachingConnectionFactory, Spring.Messaging.Nms">
        <property name="SessionCacheSize" value="10" />
        <property name="TargetConnectionFactory">
          <object type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ">
            <constructor-arg index="0" value="tcp://localhost:61616" />
          </object>
        </property>
      </object>
      <object name="MessageHandler"
              type="GameShare.VideoRecorder.MessageHandler, GameShare.VideoRecorder"
              autowire="autodetect" />
      <object name="MessageListenerAdapter"
              type="Spring.Messaging.Nms.Listener.Adapter.MessageListenerAdapter, Spring.Messaging.Nms">
        <property name="HandlerObject" ref="MessageHandler" />
      </object>
      <object name="MessageListenerContainer"
              type="Spring.Messaging.Nms.Listener.SimpleMessageListenerContainer, Spring.Messaging.Nms">
        <property name="ConnectionFactory" ref="ConnectionFactory" />
        <property name="DestinationName" value="gameShareVideoRecording" />
        <property name="MessageListener" ref="MessageListenerAdapter" />
      </object>
      <object name="NmsTemplate" type="Spring.Messaging.Nms.Core.NmsTemplate, Spring.Messaging.Nms">
        <property name="ConnectionFactory" ref="ConnectionFactory" />
      </object>
    </objects>
  </spring>

更新:在重新阅读Spring .Net文档的第31章(并稍微修改我的控制台应用程序)之后,我已经找到了解决方案/答案。默认的侦听器容器(Spring.Messaging.Nms.Listener.SimpleMessageListenerContainer)不提供我试图从我的Java / Spring / ActiveMQ模型模拟的行为(BTW, 是默认行为用于使用Java / SpringBoot框架的事务上下文中的消息处理)。具体而言,如第31.5.2节“异步接收”中所述:

  

可以将消息处理期间抛出的异常传递给   IExceptionHandler的一个实现,并注册了   容器通过属性ExceptionListener。已注册   如果异常属于该类型,则将调用IExceptionHandler   NMSException(或其他的等效根异常类型)   供应商)。 SimpleMessageListenerContainer将记录异常   错误级别 并且不会将异常传播到提供程序 。所有   处理确认和/或交易是由   监听器容器。您可以覆盖该方法   HandleListenerException可以更改此行为。

因此,为了获得所需的行为,我必须提供自己的IExceptionHandler和IErrorHandler实现,它们将异常传播给提供者。提供者(在我的情况下是ActiveMQ)将看到异常并重新排队消息。

这些接口的简单实现如下:

public class VideoRecorderApp
{
    static void Main(string[] args)
    {
        var ctx = ContextRegistry.GetContext();
        var msgListenerContainer = ctx.GetObject<SimpleMessageListenerContainer>("MessageListenerContainer");
        msgListenerContainer.SessionAcknowledgeMode = AcknowledgementMode.Transactional;
        msgListenerContainer.ErrorHandler = new MyErrorHandler();
        msgListenerContainer.ExceptionListener = new MyExceptionListener();

        Console.ReadLine();
    }
}

internal class MyErrorHandler : IErrorHandler
{
    /// <summary>
    ///     The logger
    /// </summary>
    private static readonly ILog Logger = LogManager.GetLogger<MyErrorHandler>();

    /// <summary>
    /// Handles the error.
    /// </summary>
    /// <param name="exception">The exception.</param>
    public void HandleError(Exception exception)
    {
        Logger.WarnFormat("HandleError: {0}", exception.Message);
        throw exception;
    }
}

internal class MyExceptionListener : IExceptionListener
{
    /// <summary>
    ///     The logger
    /// </summary>
    private static readonly ILog Logger = LogManager.GetLogger<MyExceptionListener>();

    /// <summary>
    /// Called when there is an exception in message processing.
    /// </summary>
    /// <param name="exception">The exception.</param>
    public void OnException(Exception exception)
    {
        Logger.WarnFormat("OnException: {0}", exception.Message);
        throw exception;
    }
}

并且,如果想要/需要区分特定的异常类型(例如,其中一些应该触发重新排队以及其他我们希望记录/吞下的那些),那么当然可以将代码逻辑添加到处理程序中这样做。

0 个答案:

没有答案