提交期间的Spring AMQP Rabbit MQ Transaction异常

时间:2015-10-29 10:29:08

标签: java rabbitmq amqp spring-amqp spring-rabbitmq

我尝试使用Spring将事务性消息写入HA RabbitMQ集群,并在单个事务中将实体写入H2数据库。

我的代码适用于标准情况,我遇到的问题是在测试错误情况时引起的。如果我断开程序连接到的兔节点的网络电缆,在提交阶段会抛出异常,我希望回滚,但是消息以某种方式被写入兔队列并且我的实体被持久化到数据库?

以下是我收到的例外情况:

org.springframework.amqp.AmqpException: failed to commit RabbitMQ transaction
    at org.springframework.amqp.rabbit.connection.RabbitResourceHolder.commitAll(RabbitResourceHolder.java:154)
    at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$RabbitResourceSynchronization.processResourceAfterCommit(ConnectionFactoryUtils.java:256)
    at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$RabbitResourceSynchronization.processResourceAfterCommit(ConnectionFactoryUtils.java:236)
    at org.springframework.transaction.support.ResourceHolderSynchronization.afterCommit(ResourceHolderSynchronization.java:85)
    at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationUtils.java:133)
    at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerAfterCommit(TransactionSynchronizationUtils.java:121)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerAfterCommit(AbstractPlatformTransactionManager.java:954)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:799)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:515)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy22.sendMessage(Unknown Source)
    at com.test.transactional.RabbitSender.run(RabbitSender.java:32)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)
Caused by: java.io.IOException
    at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:106)
    at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:102)
    at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:124)
    at com.rabbitmq.client.impl.ChannelN.txCommit(ChannelN.java:1163)
    at com.rabbitmq.client.impl.ChannelN.txCommit(ChannelN.java:61)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$CachedChannelInvocationHandler.invoke(CachingConnectionFactory.java:704)
    at com.sun.proxy.$Proxy24.txCommit(Unknown Source)
    at org.springframework.amqp.rabbit.connection.RabbitResourceHolder.commitAll(RabbitResourceHolder.java:151)
    ... 18 more
Caused by: com.rabbitmq.client.ShutdownSignalException: connection error
    at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:67)
    at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:33)
    at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:361)
    at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:226)
    at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:118)
    ... 26 more
Caused by: java.io.EOFException
    at java.io.DataInputStream.readUnsignedByte(DataInputStream.java:290)
    at com.rabbitmq.client.impl.Frame.readFrom(Frame.java:95)
    at com.rabbitmq.client.impl.SocketFrameHandler.readFrame(SocketFrameHandler.java:139)
    at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:536)

这是我发送消息和编写数据库实体的代码: (这在X消息循环中重复调用。如果收到异常,循环不会尝试重新发送消息

@Service
public class SenderServiceImpl implements SenderService {

   private static final Logger LOG = LoggerFactory.getLogger(SenderServiceImpl.class);   

   private DatabaseService databaseService;

   private ConnectionFactory connectionFactory;

   private RabbitTemplate template;

   private AtomicLong messageId;

   private final String startTime;   

   private final String exchange;

   private final String routingKey;

   @Autowired
   public SenderServiceImpl( DatabaseService databaseService,
                             ConnectionFactory connectionFactory, 
                             @Value("${rabbitmq.exchange}") String exchange, 
                             @Value("${rabbitmq.routingKey}") String routingKey) {
      this.databaseService = databaseService;
      this.connectionFactory = connectionFactory;
      this.exchange   = exchange;
      this.routingKey = routingKey;
      this.startTime  = "" + new Date().getTime();
      this.messageId  = new AtomicLong(0);
   }

   @PostConstruct
   public void init() {
      template = new RabbitTemplate(connectionFactory);
      template.setChannelTransacted( true );
   }

   @Transactional(rollbackFor=AmqpException.class) 
   public void sendMessage( String messageString ) throws AmqpException {   
      String messageCorrelationId = startTime + "-" + messageId.incrementAndGet();

      MessageProperties properties = new MessageProperties();      
      properties.setMessageId( messageCorrelationId );      

      Message message = new Message(messageString.getBytes(), properties );      

      template.send( exchange, routingKey, message, new CorrelationData( messageCorrelationId ) );
      databaseService.saveMessage( messageString, messageCorrelationId );
   }
}

这里提供完整的信息是我的DatabaseService实现

@Service
public class DatabaseServiceImpl implements DatabaseService {

   private TestDao testDao;

   @Autowired
   public DatabaseServiceImpl( TestDao testDao ) {
      this.testDao = testDao;      
   }

   @Override
   @Transactional
   public void saveMessage( String message, String messageId ) {
      testDao.saveMessage( message, messageId );      
   }
}

和TestDao类

@Repository
public class TestDao {

   @PersistenceContext
   private EntityManager entityManager;

   public void saveMessage( String message, String messageId ) {
      TestEntity entity = new TestEntity();
      entity.setMessage( message );
      entity.setMessageId( messageId );
      entityManager.persist( entity );    
   }    
}

我的applicationContext.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:rabbit="http://www.springframework.org/schema/rabbit" 
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd    
    http://www.springframework.org/schema/rabbit 
    http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx.xsd">

   <!-- Configuration for Component Scan -->
   <context:component-scan base-package="com.test.transactional" />

   <context:property-placeholder location="classpath*:rabbitmq.properties" />

   <!-- RabbitMQ Connection Factory -->
   <rabbit:connection-factory id="connectionFactory" 
                              addresses="${rabbitmq.addresses}" 
                              username="${rabbitmq.username}"
                              password="${rabbitmq.password}" />


   <!-- Transaction Manager-->   
   <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
   </bean>

   <!-- JPA Adapter -->
   <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
      <property name="databasePlatform" value="org.eclipse.persistence.platform.database.H2Platform" />
      <property name="showSql" value="true" />        
   </bean>  

   <!-- EntityManager -->
   <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml" />
      <property name="persistenceUnitName"    value="TestPU" />      
      <property name="loadTimeWeaver"> 
         <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
      </property>
      <property name="jpaPropertyMap">
         <map>
            <entry key="eclipselink.weaving" value="false"/>
         </map>
      </property>
   </bean>

   <!-- This instructs spring to check for the @Transactional annotation -->
   <tx:annotation-driven />

   <!-- bean post-processor for JPA annotations -->
   <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
</beans>

此测试应用程序通过HAProxy服务器连接到RabbitMQ。群集配置有3个兔节点。

回顾一下这个问题。如果我尝试发送30,000条消息,并从我连接的兔节点拉出网线。应用程序将在20秒内抛出几个AmqpExceptions,因此HAProxy会注意到服务器已关闭。

应用程序不会尝试重新发送任何失败的消息,但我的接收应用程序将收到完整的30,000条消息,而我的数据库将包含完整的30000个实体?

任何人都可以帮我找出原因吗?

更新

试图进一步澄清这里的测试功能是盲目地循环尝试发送消息,无论成功与否。

for(int i = 0; i < MESSAGE_LIMIT; i++) {                  
     try {
         senderService.sendMessage( "Message:" + i );
     } catch (Throwable th) {
         LOG.error("Unable to send message.", th);
     }   
} 

作为测试设置的一个示例,初始状态将发送应用程序通过HAProxy连接到Rabbit Node 1。

Initial State

然后我们从HAProxy切断到Rabbit Node 1的网络连接。 HAProxy大约需要20秒才意识到它无法连接到Rabbit Node。在此期间出现AmqpException - 我希望这些事务能够回滚。

State after cutting connection

在20秒之后,HAProxy将连接发送到剩余的2个兔子节点之一,并且该过程正常继续。

0 个答案:

没有答案