如何使CXF的JMS传输重新发布失败的QPid消息?或者,是否有一个我错过的更好的解决方案?

时间:2011-12-08 00:10:50

标签: jms cxf qpid

我正在为我的公司写一封电子邮件网络服务。其中一个主要要求是保证交付,因此我们使用持久的QPid队列在JMS传输上有一个瘦HTTP层。

我遇到的一个问题是处理过程中的错误处理。如果我在发生错误时简单地回滚事务,则消息将转到队列的头部。如果错误足够普遍,这可能会锁定整个队列,直到有人手动干预,我们希望通过回发到头来避免这种情况,以便在此期间处理消息。

然而,其中存在我的问题。首先,虽然AMQP有一种机制来“原子地拒绝和重新排列”消息而不是确认它,但JMS似乎没有任何模拟这个特性,因此访问它的唯一方法是通过强制转换,这使我与特定的JMS实现。此外,CXF的JMS传输似乎没有任何在传输级别覆盖或注入行为的方法,这意味着我要么编写字节码代理或更改代码并重新编译以获得我想要的行为。

要解决这个问题,我已经玩弄了在CXF中实现故障处理程序的想法,它只是从CXF消息中重构JMS消息,并重新排队。但是后来我不能使用事务处理会话,因为故障会导致我无法覆盖的回滚,然后我会在头上(从回滚)和尾部(最后)收到消息的副本(从重新排队)。我不能使用CLIENT_ACKNOWLEDGE,因为JMS传输在提交它进行处理之前确认消息,这意味着如果服务器在错误的时间出现故障,我可能会丢失消息。

基本上,只要我不接受JMS传输的默认行为,就不可能在不影响数据完整性的情况下获得我想要的行为(重新排列失败的消息)。

同事建议完全避开JMS传输,并直接调用队列。然后,服务实现将是一个仅用于将消息放入队列的框架类,另一个进程将实现消息监听器。对我来说,这个解决方案是次优的,因为我失去了不可知的Web服务的优雅,并且通过将我的实现与底层技术相结合而失去了一些可伸缩性。

我还考虑过使用RabbitMQ客户端库为AMQP编写CXF传输。这需要更长的时间,但这将是我们公司可以继续使用的东西,也许可以回馈给CXF项目。也就是说,由于编写,运行和测试代码所花费的时间,我对这个想法并不满意。

这是我的CXF的beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:jms="http://cxf.apache.org/transports/jms"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util      http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/context   http://www.springframework.org/schema/context/spring-context.xsd
    http://cxf.apache.org/jaxrs                     http://cxf.apache.org/schemas/jaxrs.xsd
    http://cxf.apache.org/transports/jms            http://cxf.apache.org/schemas/configuration/jms.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <context:component-scan base-package="com.edo" />

    <bean id="jmsConnectionFactory" class="org.apache.qpid.client.AMQConnectionFactory">
        <constructor-arg name="broker" value="tcp://localhost:5672"/>
        <constructor-arg name="username" value="guest"/>
        <constructor-arg name="password" value="guest"/>
        <constructor-arg name="clientName" value=""/>
        <constructor-arg name="virtualHost" value=""/>
    </bean>

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate" p:explicitQosEnabled="true" p:deliveryMode="1" p:timeToLive="5000" p:connectionFactory-ref="jmsConnectionFactory" p:sessionTransacted="false" p:sessionAcknowledgeModeName="CLIENT_ACKNOWLEDGE" />
    <bean id="jmsConfig" class="org.apache.cxf.transport.jms.JMSConfiguration" p:connectionFactory-ref="jmsConnectionFactory" p:wrapInSingleConnectionFactory="false" p:jmsTemplate-ref="jmsTemplate" p:timeToLive="500000" p:sessionTransacted="false" p:concurrentConsumers="1" p:maxSuspendedContinuations="0" p:maxConcurrentConsumers="1" />

    <jms:destination id="jms-destination-bean" name="{http://test.jms.jaxrs.edo.com/}HelloWorldImpl.jms-destination">
        <jms:address jndiConnectionFactoryName="ConnectionFactory" jmsDestinationName="TestQueue">
            <jms:JMSNamingProperty name="java.naming.factory.initial" value="org.apache.activemq.jndi.ActiveMQInitialContextFactory"/>
            <jms:JMSNamingProperty name="java.naming.provider.url" value="tcp://localhost:5672"/>
        </jms:address>
        <jms:jmsConfig-ref>jmsConfig</jms:jmsConfig-ref>
    </jms:destination>

    <jaxrs:server id="helloWorld" address="/HelloWorld" transportId="http://cxf.apache.org/transports/jms">
      <jaxrs:serviceBeans>
        <ref bean="helloWorldBean"/>
      </jaxrs:serviceBeans>
      <jaxrs:inInterceptors>
        <bean class="com.edo.jaxrs.jms.test.FlowControlInInterceptor" p:periodMs="1000" p:permitsPerPeriod="18" />
      </jaxrs:inInterceptors>
      <jaxrs:providers>
        <bean class="org.apache.cxf.jaxrs.provider.JSONProvider">
          <property name="produceMediaTypes" ref="jsonTypes"/>
          <property name="consumeMediaTypes" ref="jsonTypes"/>
        </bean>
      </jaxrs:providers>
    </jaxrs:server>

    <bean id="http-jms-config" class="com.edo.jaxrs.jms.test.HttpOverJmsConfig" 
        p:jmsFactory-ref="jmsConnectionFactory" 
        p:jmsDestinationName="TestQueue" />

    <util:list id="jsonTypes">
      <value>application/json</value>
      <value>application/jettison</value>
    </util:list>

</beans>

有什么简单的我不见了吗?还是有更好的方法来解决这个问题吗?

1 个答案:

答案 0 :(得分:0)

所以 - 我正在接受同事的建议而不是将JMS传输用于Web服务。相反,我们将在Spring Integration上创建一个瘦Web服务层。这应该允许我们所需的控制粒度,而不会不必要地暴露消息传递层。