随笔 - 41  文章 - 7  trackbacks - 0
<2016年8月>
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用链接

留言簿

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

3.1.10 配置broker

介绍

AMQP 规范描述了协议是如何用于broker中队列,交换器以及绑定上的.这些操作是从0.8规范中移植的,更高的存在于org.springframework.amqp.core包中的AmqpAdmin 接口中.
那个接口的RabbitMQ 实现是RabbitAdmin,它位于org.springframework.amqp.rabbit.core 包.

AmqpAdmin接口是基于Spring AMQP 域抽象,展示如下:

public interface AmqpAdmin {      
// Exchange Operations
void declareExchange(Exchange exchange);      
void deleteExchange(String exchangeName);
// Queue Operations
Queue declareQueue();
String declareQueue(Queue queue);
void deleteQueue(String queueName);
void deleteQueue(String queueName, boolean unused, boolean empty);
void purgeQueue(String queueName, boolean noWait);
// Binding Operations
void declareBinding(Binding binding);      
void removeBinding(Binding binding);
Properties getQueueProperties(String queueName);
}

getQueueProperties() 方法会返回关于队列的的一些有限信息(消息个数和消费者数目). 属性返回的keys像RabbitTemplate (QUEUE_NAMEQUEUE_MESSAGE_COUNTQUEUE_CONSUMER_COUNT)中的常量一样是可用的. 
RabbitMQ REST API 提供了更多关于 QueueInfo 对象的信息.

无参 declareQueue() 方法在broker上定义了一个队列,其名称是自动生成的. 自动生成队列的其它属性是exclusive=trueautoDelete=true, and durable=false.

declareQueue(Queue queue) 方法接受一个 Queue 对象,并且返回声明队列的名称.如果提供的队列名称是空字符串,broker 使用生成的名称来声明队列再将名称返回给调用者. Queue 对象本身是不会变化的. 

这种功能只能用于编程下直接调用RabbitAdmin. 它不支持在应用程序上下文中由admin来定义队列的自动声明.

与此形成鲜明对比的是,AnonymousQueue,框架会为其生成唯一名称(UUID),durable为false,exclusiveautoDelete 为true的匿名队列<rabbit:queue/> 带空的或缺失的name 属性总会创建 一个AnonymousQueue.

参考the section called “AnonymousQueue” 来理解为什么 AnonymousQueue 会优先选择broker生成队列名称,以及如何来控制名称格式. 声明队列必须有固定的名称,因为它们可能会上下文的其它地方引用,例如,在监听器中

<rabbit:listener-container>
<rabbit:listener ref="listener" queue-names="#{someQueue.name}" />
</rabbit:listener-container>

参考 the section called “Automatic Declaration of Exchanges, Queues and Bindings”.

此接口的RabbitMQ实现是RabbitAdmin,当用Spring XML配置时,看起来像下面这样:

<rabbit:connection-factory id="connectionFactory"/>
<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory"/>

CachingConnectionFactory 缓存模式是CHANNEL 时(默认的),  RabbitAdmin 实现会在同一个ApplicationContext中自动延迟声明 Queues,Exchanges 和 Bindings.
只要Connection打开了与Broker的连接,这些组件就会被声明.有一些命名空间特性可以使这些变得便利,如,在Stocks 样例程序中有:

<rabbit:queue id="tradeQueue"/>
<rabbit:queue id="marketDataQueue"/>
<fanout-exchange name="broadcast.responses" xmlns="http://www.springframework.org/schema/rabbit">
<bindings>
<binding queue="tradeQueue"/>
</bindings>
</fanout-exchange>
<topic-exchange name="app.stock.marketdata" xmlns="http://www.springframework.org/schema/rabbit">
<bindings>
<binding queue="marketDataQueue"pattern="${stocks.quote.pattern}"/>
</bindings>
</topic-exchange>

在上面的例子中,我们使用匿名队列(实际上由框架内部生成,而非由broker生成的队列),并用ID进行了指定.我们也可以使用明确的名称来声明队列,也作为上下文中bean定义的标识符.如.

<rabbit:queue name="stocks.trade.queue"/>
重要
你可以提供id 和 name 属性.这允许你独立于队列名称通过id来指定队列.它允许使用标准的Spring 属性,如属性占位符和队列名称的SpEL 表达式; 当使用名称来作为标识符,这些特性是不可用的.

队列也可以使用其它的参数进行配置,例如x-message-ttl 或 x-ha-policy.通过命名空间支持,它们可以通过<rabbit:queue-arguments>元素以参数名/参数值的MAP形式来提供 .

<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>

默认情况下,参数假设为字符串.对于其它类型的参数,需要提供类型.

<rabbit:queue name="withArguments">
<rabbit:queue-arguments value-type="java.lang.Long">
<entry key="x-message-ttl" value="100"/>
</rabbit:queue-arguments>
</rabbit:queue>

当提供混合类型的参数时,可为每个entry元素提供type:

<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-message-ttl">
<value type="java.lang.Long">100</value>
</entry>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>

在Spring Framework 3.2或以后,声明起来更加简洁:

<rabbit:queue name="withArguments">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="100" value-type="java.lang.Long"/>
<entry key="x-ha-policy" value="all"/>
</rabbit:queue-arguments>
</rabbit:queue>
重要
RabbitMQ broker 不允许使用不匹配的参数来声明队列. 例如,如果一个无time to live参数的队列已经存在,然后你试图使用 key="x-message-ttl" value="100"进行声明,那么会抛出一个异常.

默认情况下,当出现异常时, RabbitAdmin 会立即停止所有声明的处理过程;这可能会导致下游问题- 如监听器容器会初始化失败,因另外的队列没有声明.

这种行为可以通过在RabbitAdmin上设置 ignore-declaration-exceptions 为true来修改. 此选项会指示RabbitAdmin 记录异常,并继续声明其它元素.当使用Java来配置RabbitAdmin 时, 此属性为ignoreDeclarationExceptions.
这是一个全局设置,它将应用到所有元素上,如应用到queues, exchanges 和bindings这些具有相似属性的元素上.

在1.6版本之前, 此属性只会在channel上发生IOExcepton时才会起作用- 如当目前和期望属性发生错配时. 现在, 这个属性可在任何异常上起作用,包括TimeoutException 等等.

此外,任何声明异常都会导致发布DeclarationExceptionEvent, 这是一个ApplicationEvent ,在上下文中可通过任何ApplicationListener 消费. 此事件包含了admin的引用, 正在声明的元素以及Throwable.

从1.3版本开始, HeadersExchange 可配置匹配多个headers; 你也可以指定是否需要必须匹配任何一个或全部headers:

<rabbit:headers-exchange name="headers-test">
<rabbit:bindings>
<rabbit:binding queue="bucket">
<rabbit:binding-arguments>
<entrykey="foo"value="bar"/>
<entrykey="baz"value="qux"/>
<entrykey="x-match"value="all"/>
</rabbit:binding-arguments>
</rabbit:binding>
</rabbit:bindings>
</rabbit:headers-exchange>

从1.6版本开始,Exchanges 可使用internal 标志来配置(默认为false) ,当然,这样的Exchange 也可以通过 RabbitAdmin 来配置(如果在应用程序上下文中存在).
如果对于交换器来说,internal 标志为true , RabbitMQ 会允许客户端来使用交换器.这对于死信交换器来说或交换器到交换器绑定来说,是很用的,因为在这些地方你不想让发布者直接使用交换器.

要看如何使用Java来配置AMQP基础设施,可查看Stock样例程序,在那里有一个带@Configuration 注解的抽象AbstractStockRabbitConfiguration 类,它依次有RabbitClientConfiguration 和 RabbitServerConfiguration 子类. AbstractStockRabbitConfiguration 的代码展示如下:

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

    @Bean
 public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
 public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

    @Bean
 public MessageConverter jsonMessageConverter() {
        returnnew JsonMessageConverter();
    }

    @Bean
 public TopicExchange marketDataExchange() {
        returnnew TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}

在Stock 程序中,服务器使用下面的@Configuration注解来配置:

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

    @Bean
 public Queue stockRequestQueue() {
        returnnew Queue("app.stock.request");
    }
}

这是整个@Configuration 类继承链结束的地方. 最终结果是TopicExchange 和队列会在应用程序启动时被声明.在服务器配置中,没有TopicExchange与队列的绑定,因为这是在客户端程序完成的.
然后stock 请求队列是自动绑定到AMQP 默认交换器上的 - 这种行为是由规范来定义的.

客户端 @Configuration 类令人关注的地方展示如下.

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
 private String marketDataRoutingKey;

    @Bean
 public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */@Bean
  public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}

客户端使用AmqpAdmin的declareQueue()方法声明了另一个队列,并将其绑定到了market data 交换器上(路由键模式是通常外部properties文件来定义的).

Queues 和Exchanges的Builder API

当使用Java配置时,1.6版本引入了一个便利的API来配置Queue 和Exchange 对象:

@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}

@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}

查看 org.springframework.amqp.core.QueueBuilder 

和 org.springframework.amqp.core.ExchangeBuilder 的JavaDoc来了解更多信息.

Declaring Collections of Exchanges, Queues, Bindings

从1.5版本开始,可以在一个@Bean声明多个条目来返回集合.

只有集合中的第一个元素可认为是Declarablea的,并且只有集合中的Declarable 元素会被处理.(

Only collections where the first element is a Declarable are considered, and only Declarable elements from such collections are processed.)

@Configuration
public static class Config {

    @Bean
public ConnectionFactory cf() {
        returnnew CachingConnectionFactory("localhost");
    }

    @Bean
public RabbitAdmin admin(ConnectionFactory cf) {
        returnnew RabbitAdmin(cf);
    }

    @Bean
public DirectExchange e1() {
    	returnnew DirectExchange("e1", false, true);
    }

    @Bean
public Queue q1() {
    	returnnew Queue("q1", false, false, true);
    }

    @Bean
public Binding b1() {
    	return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
public List<Exchange> es() {
    	return Arrays.<Exchange>asList(
    			new DirectExchange("e2", false, true),
    			new DirectExchange("e3", false, true)
    	);
    }

    @Bean
public List<Queue> qs() {
    	return Arrays.asList(
    			new Queue("q2", false, false, true),
    			new Queue("q3", false, false, true)
    	);
    }

    @Bean
public List<Binding> bs() {
    	return Arrays.asList(
    			new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
    			new Binding("q3", DestinationType.QUEUE, "e3", "k3", null)
    	);
    }

    @Bean
public List<Declarable> ds() {
    	return Arrays.<Declarable>asList(
    			new DirectExchange("e4", false, true),
    			new Queue("q4", false, false, true),
    			new Binding("q4", DestinationType.QUEUE, "e4", "k4", null)
    	);
    }

}

条件声明

默认情况下,所有queues, exchanges,和bindings 都可通过应用程序上下文中所有RabbitAdmin 实例来声明(设置了auto-startup="true").

重要

从1.2版本开始,可以有条件地声明元素.当程序连接了多个brokers,并需要在哪些brokers上声明特定元素时,特别有用.

代表这些元素要实现Declarable 接口,此接口有两个方法: shouldDeclare() 和 getDeclaringAdmins()RabbitAdmin 使用这些方法来确定某个特定实例是否应该在其Connection上处理声明.

这些属性作为命名空间的属性是可用的,如下面的例子所示.

<rabbit:admin id="admin1" connection-factory="CF1" />
<rabbit:admin id="admin2" connection-factory="CF2" />
<rabbit:queue id="declaredByBothAdminsImplicitly" />
<rabbit:queue id="declaredByBothAdmins" declared-by="admin1, admin2" />
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />
<rabbit:queue id="notDeclaredByAny" auto-declare="false" />
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
<rabbit:bindings>
<rabbit:bindingkey="foo" queue="bar"/>
</rabbit:bindings>
</rabbit:direct-exchange>
重要
默认情况下,如果没有提供declared-by(或是空的) auto-declare 属性则为 true,那么所有RabbitAdmin将声明对象(只要admin的auto-startup 属性为true,默认值).

现样的,你可以使用基于Java的@Configuration 注解来达到同样的效果.在这个例子中,组件会由admin1来声明,而不是admin2:

@Bean
public RabbitAdmin admin() {
	RabbitAdmin rabbitAdmin = new RabbitAdmin(cf1());
	rabbitAdmin.afterPropertiesSet();
	return rabbitAdmin;
}

@Bean
public RabbitAdmin admin2() {
	RabbitAdmin rabbitAdmin = new RabbitAdmin(cf2());
	rabbitAdmin.afterPropertiesSet();
	return rabbitAdmin;
}

@Bean
public Queue queue() {
	Queue queue = new Queue("foo");
	queue.setAdminsThatShouldDeclare(admin());
	return queue;
}

@Bean
public Exchange exchange() {
	DirectExchange exchange = new DirectExchange("bar");
	exchange.setAdminsThatShouldDeclare(admin());
	return exchange;
}

@Bean
public Binding binding() {
	Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
	binding.setAdminsThatShouldDeclare(admin());
	return binding;
}

AnonymousQueue

一般来说,当需要一个独特命名,专用的,自动删除队列时,建议使用AnonymousQueue 来代替中间件定义的队列名称(使用 "" 作为队列名称会导致中间件生成队列名称).

这是因为:

  1. 队列实际上是在与broker的连接建立时声明的;这在bean创建和包装之后要很长时间;使用这个队列的beans需要知道其名称.而事实上,当app启动时,broker甚至还没有运行.
  2. 如果与broker的连接因某种原因丢失了,admin会使用相同的名称会重新声明AnonymousQueue.如果我们使用broker-声明队列,队列名称可能会改变.

从1.5.3版本开始,你可通过AnonymousQueue 来控制队列名称的格式.

默认情况下,队列名称是UUID的字符串表示; 例如: 07afcfe9-fe77-4983-8645-0061ec61a47a.

现在,你可以提供一个 AnonymousQueue.NamingStrategy 实现作为其构造器参数:

@Bean
public Queue anon1() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy());
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("foo-"));
}

第一个会生成队列名称前辍spring.gen- 其后为UUID base64 的表示,例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g. 第二个会生成队列名称前辍为foo- 其后为UUID的 base64 表示.

base64 编码使用RFC 4648的"URL and Filename Safe Alphabet" ; 删除了字符(=).

你可以提供你自己的命名策略, 可以包括队列名称中的其他信息(例如应用程序、客户端主机)。

从1.6版本开始,当使用XML配置时,可以指定命名策略; naming-strategy 属性出现在<rabbit:queue>元素的属性中,对于bean引用来说,它们实现了AnonymousQueue.NamingStrategy.

<rabbit:queue id="uuidAnon" />
<rabbit:queue id="springAnon" naming-strategy="springNamer" />
<rabbit:queue id="customAnon" naming-strategy="customNamer" />
<bean id="springNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy" />
<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
<constructor-arg value="custom.gen-" />
</bean>
第一个创建了UUID字符串表示的名称.第二个创建了类似spring.gen-MRBv9sqISkuCiPfOYfpo4g的名称. 第三个创建了类似custom.gen-MRBv9sqISkuCiPfOYfpo4g的名称.

当然,你可以提供你自己的命名策略bean.

3.1.11 延迟的消息交换器

1.6版本引入了 Delayed Message Exchange Plugin支持.

该插件目前被标记为实验性质,但可用已超过一年(在写作的时间)。如果插件的变化是必要的,我们将尽快添加支持这样的变化。因此,这种在Spring AMQP支持同样也应考虑为实验性质.这个功能在RabbitMQ 3.6.0版本和0.0.1插件版本中经过测试。

要使用RabbitAdmin 来声明一个延迟交换器,只需要在交换器上简单地设置delayed 属性为true. RabbitAdmin 会使用交换器类型(DirectFanout 等)来设置x-delayed-type 参数,并使用x-delayed-message来声明交换器.

当使用XML来配置交换器beans时,delayed 属性 (默认为false)是可用的.

<rabbit:topic-exchange name="topic" delayed="true" />

要发送延迟消息,只需要通过MessageProperties设置x-delay header:

MessageProperties properties = new MessageProperties();
properties.setXDelay(15000);
template.send(exchange, routingKey,
        MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());

rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() {

    @Override
public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setXDelay(15000);
        return message;
    }

});

要检查消息是否是延迟的,可调用MessagePropertiesgetReceivedDelay()它是一个独立的属性,以避免从一个输入消息意外的传播到一个输出消息。

3.1.12 RabbitMQ REST API

当启用了管理插件时,RabbitMQ 服务器公开了 REST API 来监控和配置broker. 

现在提供了 Java Binding for the API.一般来说,你可以直接使用API,但提供了便利的包装器来使用熟悉的Spring AMQP QueueExchange, 和 Binding 域对象.
当直接使用 com.rabbitmq.http.client.Client API  (分别使用QueueInfoExchangeInfo, 和BindingInfo),那些对象的更多信息将可用. 下面是RabbitManagementTemplate上的可用操作:

public interface AmqpManagementOperations {

	void addExchange(Exchange exchange);

	void addExchange(String vhost, Exchange exchange);

	void purgeQueue(Queue queue);

	void purgeQueue(String vhost, Queue queue);

	void deleteQueue(Queue queue);

	void deleteQueue(String vhost, Queue queue);

	Queue getQueue(String name);

	Queue getQueue(String vhost, String name);

	List<Queue> getQueues();

	List<Queue> getQueues(String vhost);

	void addQueue(Queue queue);

	void addQueue(String vhost, Queue queue);

	void deleteExchange(Exchange exchange);

	void deleteExchange(String vhost, Exchange exchange);

	Exchange getExchange(String name);

	Exchange getExchange(String vhost, String name);

	List<Exchange> getExchanges();

	List<Exchange> getExchanges(String vhost);

	List<Binding> getBindings();

	List<Binding> getBindings(String vhost);

	List<Binding> getBindingsForExchange(String vhost, String exchange);

}

参考javadocs 来了解更多信息.

3.1.13 异常处理

RabbitMQ Java client的许多操作会抛出受查异常. 例如,有许多可能抛出IOExceptions的地方. RabbitTemplate, SimpleMessageListenerContainer, 和其它Spring AMQP 组件会捕获这些异常,并将它们转换为运行时层次的异常.
这些是定义在
org.springframework.amqp 包中的,且 AmqpException 是层次结构的基础.

当监听器抛出异常时,它会包装在一个 ListenerExecutionFailedException 中,正常情况下消息会被拒绝并由broker重新排队.将defaultRequeueRejected 设置为false 可导致消息丢弃(或路由到死信交换器中). 

正如 the section called “Message Listeners and the Asynchronous Case”讨论的,监听器可抛出 AmqpRejectAndDontRequeueException 来有条件地控制这种行为。

然而,有一种类型的错误,监听器无法控制其行为. 当遇到消息不能转换时(例如,无效的content_encoding 头),那么消息在到达用户代码前会抛出一些异常.当设置 defaultRequeueRejected 为 true (默认),这样的消息可能会一遍又一遍地重新投递.
在1.3.2版本之前,用户需要编写定制ErrorHandler, 正如Section 3.1.13, “Exception Handling” 描述的内容来避免这种情况.

从1.3.2版本开始,默认的ErrorHandler 是 ConditionalRejectingErrorHandler ,它将拒绝那些失败且不可恢复的消息 (不会重新排队):

  • o.s.amqp...MessageConversionException
  • o.s.messaging...MessageConversionException
  • o.s.messaging...MethodArgumentNotValidException
  • o.s.messaging...MethodArgumentTypeMismatchException

第一个是在使用MessageConverter转换传入消息负荷时抛出的.
第二个是当映射到@RabbitListener方法时,转换服务需要其它转换抛出的.
第三个是在监听器上使用了验证(如.@Valid),且验证失败时抛出的.
第四个是对于目标方法传入消息类型转换失败抛出的.例如,参数声明为Message<Foo> ,但收到的是Message<Bar>

错误处理器的实例可使用FatalExceptionStrategy 来配置,因为用户可以提供它们的规则来有条件的拒绝消息,如. 来自 Spring Retry (the section called “Message Listeners and the Asynchronous Case”)中的BinaryExceptionClassifier代理实现.
此外, ListenerExecutionFailedException 现在有一个可用于决策的failedMessage 属性.如果FatalExceptionStrategy.isFatal() 方法返回true,错误处理器会抛出AmqpRejectAndDontRequeueException.
默认FatalExceptionStrategy 会记录warning信息.

3.1.14 事务(Transactions)

介绍

Spring Rabbit 框架支持在同步和异步使用中使用不同语义(这一点对于现有Spring事务的用户是很熟悉的)来支持自动事务管理. 它做了很多,不是常见消息模式能轻易实现的.

有两种方法可用来向框架发出期望事务语义的信号.在RabbitTemplate 和 SimpleMessageListenerContainer 中,这里有一个channelTransacted 标记,如果它为true,就会告知框架使用事务通道,并根据结果使用提交或回滚来结束所有操作,出现异常时则发出回滚信号. 

另一个提供的信号是Spring的PlatformTransactionManager实现(作为正在进行的操作的上下文)外部事务 
当框架发送或接收消息时,如果过程中已经存在一个事
务,且channelTransacted 标记为true, 那么当前消息事务的提交或回滚操作会延迟直到在当前事务结束.如果channelTransacted 标记为false,那么消息操作是不会应用事务语义(它是自动应答的).

channelTransacted 标记是一个配置时设置:它只在AMQP组件声明时执行一次,通常在应用程序启动时.原则上,外部事务更加动态化,因为需要在运行时根据当前线程状态来响应,当事务分层到应用程序上时,原则上来说它通常也是一个配置设置.

对于使用RabbitTemplate 的同步使用,外部事务是由调用者提供的, 要么是声明的,要么是强制的(日常Spring事务模式). 

下面是声明方法的一个例子(通常选择这个,因为它是非侵入的), 下面的例子中,模板已经配置了channelTransacted=true:

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}

收到字符负荷,转换,并以消息体发送到@Transactional标记的方法中,因此如果数据处理因异常失败了,传入消息将返回到broker,并且输出消息不会被发送.
在事务方法链中,这适用于
RabbitTemplate 中的所有操作(除非Channel 较早地直接控制了提交事务).

对于SimpleMessageListenerContainer 的异步使用情况,如果需要外部事务,当设置了监听器时,必须由容器来发出请求.
为了表示需要外部事务,当配置时,用户为容器提供了PlatformTransactionManager 实现.例如:

@Configuration
public class ExampleExternalTransactionAmqpConfiguration {

@Bean
public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setTransactionManager(transactionManager());
        container.setChannelTransacted(true);
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }

}

在上面的例子中,事务管理器是通过其它bean中注入添加的(未显示),并且channelTransacted 也设置为了true.其效果是如果监听器因异常失败了,那么事务将回滚,消息也会退回到broker中.
明显地,如果事务提交失败(如.数据库约束错误,或通过问题),那么AMQP 事务也要回滚,且消息也会回退到broker中.
有时候,这被称为最好努力1阶段提交(Best Efforts 1 Phase Commit),它是可靠消息非常强大的模式.
如果在上面的例子中将channelTransacted标志设为false(默认为false),那么外部事务仍会提供给监听器,但所有消息操作都是自动应答的, 因此其效果是即使发生了业务操作,也会提供消息操作.

关于接收消息的回滚说明

AMQP 事务只适用于发送应答给broker, 所以当有 Spring 事务回滚且又收到了消息时,Spring AMQP做的不仅要回滚事务,还要手动拒绝消息.
消息上的拒绝操作独立于事务,依赖于defaultRequeueRejected 属性(默认为true). 更多关于拒绝失败消息的详情,请参考the section called “Message Listeners and the Asynchronous Case”.

关于RabbitMQ 事务及其局限性的更多信息,参考RabbitMQ Broker Semantics.

重要

在 RabbitMQ 2.7.0前, 这样的消息(当通道关闭或中断时未应的消息)会回到队列中,从2.7.0, 拒绝消息会跑到队列前边,与JMS回滚消息方式类似.

使用RabbitTransactionManager

RabbitTransactionManager 是执行同步,外部事务Rabbit操作的另一种选择.这个事务管理器是PlatformTransactionManager 接口的实现类,应该在单个Rabbit ConnectionFactory中使用.

重要

此策略不能提供XA事务,比如,要在消息和数据库之间共享事务.

应用代码需要通过ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)来获取事务性Rabbit资源而不是使用Connection.createChannel() 调用.
当使用Spring AMQP的 RabbitTemplate时, 
它会自动检测线程绑定通道和自动参与事务。

在 Java 配置中,你可以使用下面的代码来设置一个新的RabbitTransactionManager:

@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    returnnew RabbitTransactionManager(connectionFactory);
}

如果你喜欢使用XML 配置,可以像下面进行声明:

<bean id="rabbitTxManager" class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
<propertyname="connectionFactory" ref="connectionFactory"/>
</bean>

3.1.15 消息监听器容器配置

有相当多的配置SimpleMessageListenerContainer 相关事务和服务质量的选项,它们之间可以互相交互.当使用命名空间来配置<rabbit:listener-container/>时,

下表显示了容器属性名称和它们等价的属性名称(在括号中).

未被命名空间暴露的属性,以`N/A`表示.

posted on 2016-08-13 16:07 胡小军 阅读(4895) 评论(0)  编辑  收藏 所属分类: RabbitMQ

只有注册用户登录后才能发表评论。


网站导航: