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