1.先分析下rabbitmq的一些关键概念吧
(1)rabbitmq的ConnectionFactory类连接到主机上
两种连接方式:
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();或者Connection连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection conn = factory.newConnection();
(2)Connection上创建信道
由于一个连接上可以创建多个信道,由于TCP连接断开重连的代价太大,所以通过创建信道来消息通信。
Channel channel = conn.createChannel();
最后关闭可用
channel.close();
conn.close();
(3)使用exchange和queue(交换器和消息队列)
交换器的类型:fanout、direct、topic、header四种,分别代表无差别分发、对应直连、按套接字分发、custom headers参数声明分发模式(server应该实现headers交换器类型, 且server必须在每个虚拟主机中预先声明至少一个headers交换器,且名称为amq.match.)。
消息队列:传输消息的消息队列,消息的持久化durable=true和高可用的话需要可以使用mirror-queue必须使用集群配置中。
channel.exchangeDeclare(exchangeName, "direct", true); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, exchangeName, routingKey);
创建一个标准连接方式的可以使用
channel.exchangeDeclare(exchangeName, "direct", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
Queue DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) throws IOException;
根据上面的queueDeclare方法参数的命名可以看出含义:队列名称--是否持久化--是否独有--是否自动删除--其他属性(结构参数)
(4)发布消息
多种消息发布和订阅方式看官网:http://www.rabbitmq.com/api-guide.html
BasicProperties properties = new BasicProperties.Builder() .deliveryMode(2).build();
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.contentType("text/plain")
.deliveryMode(2)
.priority(1)
.userId("bob")
.build()),
messageBodyBytes);
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
deliveryMode(2) // non-persistent(1) or persistent(2)
消息发布成功。
(5)订阅消息
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
// (process the message components here ...)
channel.basicAck(deliveryTag, false);
}
});
String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException;
OK订阅成功。至此消息发布和订阅的基本功能已实现。附上源码:
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by
*/
public class Main {
static final String exchangeName = "testblock";
static final String routingKey = "testblock";
static final String queueName = "testblock";
private static int producterConnection_size = 5; // 消息生产者连接数
private static int consumerConnection_size = 0; // 消费者连接数
private static final int consumer_size = 1;// 每个消费者连接里面开启的consumer数量
private static int qos = 1; // Qos设置
private static long sleep_time = 0; // 模拟每条消息的处理时间
private static boolean autoAck = true; // 是否默认Ack
private static Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) throws Exception {
final AtomicLong count = new AtomicLong(10000000000L);
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("hebao");
factory.setPassword("hebao");
factory.setVirtualHost("/");
factory.setHost("192.168.2.158");
factory.setPort(5672);
// 启动监控程序
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long c = count.get();
while (c != 0) {
try {
Thread.sleep(1000);
long c1 = count.get();
System.out.println("每秒消费为:{}Qps" + (c - c1));
c = c1;
} catch (Exception e) {
}
}
}
});
t.start();
// 启动
for (int i = 0; i < producterConnection_size; i++) {
Connection conn1 = factory.newConnection();
Thread t1 = producter(conn1, count.get());
t1.start();
}
// 启动consumer
for (int i = 0; i < consumerConnection_size; i++) {
Connection conn1 = factory.newConnection();
Thread t2 = consumer(conn1, count);
t2.start();
}
}
public static Thread consumer(final Connection conn, final AtomicLong count)
throws Exception {
return new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start consumer");
try {
final CountDownLatch cdl = new CountDownLatch(1000);
for (int i = 0; i < consumer_size; i++) {
final Channel channel = conn.createChannel();
channel.basicQos(0, qos, false);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
if (count.decrementAndGet() == 0) {
channel.basicCancel(consumerTag);
cdl.countDown();
channel.close();
}
try {
Thread.sleep(sleep_time);
} catch (InterruptedException e) {
}
if (!autoAck) {
getChannel().basicAck(
envelope.getDeliveryTag(), true);
}
}
};
String consumerTag = channel.basicConsume(queueName,
autoAck, "testConsumer" + i, consumer);
System.out.println("consumerTag is {}" + consumerTag);
}
cdl.await();
} catch (Exception e) {
}
}
});
}
public static Thread producter(final Connection conn, final long count)
throws Exception {
return new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start send Message");
try {
Channel channel = conn.createChannel();
channel.exchangeDeclare(exchangeName, "direct", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
BasicProperties properties = new BasicProperties.Builder()
.deliveryMode(2).build();
for (long i = 0; i < count; i++) {
byte[] messageBodyBytes = ("{\"merchantsId\":13}")
.getBytes();
channel.basicPublish(exchangeName, routingKey,
properties, messageBodyBytes);
// System.out.println("add message {}",i);
}
channel.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
搭建好服务端之后可以自定义生成和消费连接数来查看rabbitmq烦人读写、是否持久化、多线程的异步处理、消息确认各种变化之后效率的变化情况
pom.xml
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.7</version>
</dependency>
根据以上自己可以maven项目来添加对应的jar包中主要的有:
amqp-client-2.8.2.jar
spring-amqp-1.1.1RELEASE.jar
spring-rabbit-1.1.1RELEASE.jar
2.源码解析
(1)现在一次对以上的jar包中的主要类进行说明(基本是源码,意会容易言传难)
在分析amqp-client-2.8.2.jar之前先给一个AMQP协议的一个文档,该文档基本涵盖了源码要表达的内容,而且rabbitmq也是基于此协议构建的,语言由erlang编写语言特性不多说,就好呗:AMQP-0-9-1中文规范。多看多看多看。。。重要的内容重复说。
此jar包就分析两个路口类吧,别的自己去看了(个人感觉下面的两段代码废话多,同感请忽略,还是那个代码不能伸缩比较无语)
RpcClient.java类
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License
// at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
// the License for the specific language governing rights and
// limitations under the License.
//
// The Original Code is RabbitMQ.
//
// The Initial Developer of the Original Code is VMware, Inc.
// Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
//
package com.rabbitmq.client;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.impl.MethodArgumentReader;
import com.rabbitmq.client.impl.MethodArgumentWriter;
import com.rabbitmq.client.impl.ValueReader;
import com.rabbitmq.client.impl.ValueWriter;
import com.rabbitmq.utility.BlockingCell;
/**
* Convenience class which manages a temporary reply queue for simple RPC-style communication.
* The class is agnostic about the format of RPC arguments / return values.
* It simply provides a mechanism for sending a message to an exchange with a given routing key,
* and waiting for a response on a reply queue.
*/
public class RpcClient {
/** Channel we are communicating on */
private final Channel _channel;
/** Exchange to send requests to */
private final String _exchange;
/** Routing key to use for requests */
private final String _routingKey;
/** timeout to use on call responses */
private final int _timeout;
/** NO_TIMEOUT value must match convention on {@link BlockingCell#uninterruptibleGet(int)} */
protected final static int NO_TIMEOUT = -1;
/** Map from request correlation ID to continuation BlockingCell */
private final Map<String, BlockingCell<Object>> _continuationMap = new HashMap<String, BlockingCell<Object>>();
/** Contains the most recently-used request correlation ID */
private int _correlationId;
/** The name of our private reply queue */
private String _replyQueue;
/** Consumer attached to our reply queue */
private DefaultConsumer _consumer;
/**
* Construct a new RpcClient that will communicate on the given channel, sending
* requests to the given exchange with the given routing key.
* <p/>
* Causes the creation of a temporary private autodelete queue.
* @param channel the channel to use for communication
* @param exchange the exchange to connect to
* @param routingKey the routing key
* @param timeout milliseconds before timing out on wait for response
* @throws IOException if an error is encountered
* @see #setupReplyQueue
*/
public RpcClient(Channel channel, String exchange, String routingKey, int timeout) throws IOException {
_channel = channel;
_exchange = exchange;
_routingKey = routingKey;
if (timeout < NO_TIMEOUT) throw new IllegalArgumentException("Timeout arguument must be NO_TIMEOUT(-1) or non-negative.");
_timeout = timeout;
_correlationId = 0;
_replyQueue = setupReplyQueue();
_consumer = setupConsumer();
}
/**
* Construct a new RpcClient that will communicate on the given channel, sending
* requests to the given exchange with the given routing key.
* <p/>
* Causes the creation of a temporary private autodelete queue.
* <p/>
* Waits forever for responses (that is, no timeout).
* @param channel the channel to use for communication
* @param exchange the exchange to connect to
* @param routingKey the routing key
* @throws IOException if an error is encountered
* @see #setupReplyQueue
*/
public RpcClient(Channel channel, String exchange, String routingKey) throws IOException {
this(channel, exchange, routingKey, NO_TIMEOUT);
}
/**
* Private API - ensures the RpcClient is correctly open.
* @throws IOException if an error is encountered
*/
public void checkConsumer() throws IOException {
if (_consumer == null) {
throw new EOFException("RpcClient is closed");
}
}
/**
* Public API - cancels the consumer, thus deleting the temporary queue, and marks the RpcClient as closed.
* @throws IOException if an error is encountered
*/
public void close() throws IOException {
if (_consumer != null) {
_channel.basicCancel(_consumer.getConsumerTag());
_consumer = null;
}
}
/**
* Creates a server-named exclusive autodelete queue to use for
* receiving replies to RPC requests.
* @throws IOException if an error is encountered
* @return the name of the reply queue
*/
protected String setupReplyQueue() throws IOException {
return _channel.queueDeclare("", false, true, true, null).getQueue();
}
/**
* Registers a consumer on the reply queue.
* @throws IOException if an error is encountered
* @return the newly created and registered consumer
*/
protected DefaultConsumer setupConsumer() throws IOException {
DefaultConsumer consumer = new DefaultConsumer(_channel) {
@Override
public void handleShutdownSignal(String consumerTag,
ShutdownSignalException signal) {
synchronized (_continuationMap) {
for (Entry<String, BlockingCell<Object>> entry : _continuationMap.entrySet()) {
entry.getValue().set(signal);
}
_consumer = null;
}
}
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
synchronized (_continuationMap) {
String replyId = properties.getCorrelationId();
BlockingCell<Object> blocker = _continuationMap.get(replyId);
_continuationMap.remove(replyId);
blocker.set(body);
}
}
};
_channel.basicConsume(_replyQueue, true, consumer);
return consumer;
}
public void publish(AMQP.BasicProperties props, byte[] message)
throws IOException
{
_channel.basicPublish(_exchange, _routingKey, props, message);
}
public byte[] primitiveCall(AMQP.BasicProperties props, byte[] message)
throws IOException, ShutdownSignalException, TimeoutException
{
checkConsumer();
BlockingCell<Object> k = new BlockingCell<Object>();
synchronized (_continuationMap) {
_correlationId++;
String replyId = "" + _correlationId;
props = ((props==null) ? new AMQP.BasicProperties.Builder() : props.builder())
.correlationId(replyId).replyTo(_replyQueue).build();
_continuationMap.put(replyId, k);
}
publish(props, message);
Object reply = k.uninterruptibleGet(_timeout);
if (reply instanceof ShutdownSignalException) {
ShutdownSignalException sig = (ShutdownSignalException) reply;
ShutdownSignalException wrapper =
new ShutdownSignalException(sig.isHardError(),
sig.isInitiatedByApplication(),
sig.getReason(),
sig.getReference());
wrapper.initCause(sig);
throw wrapper;
} else {
return (byte[]) reply;
}
}
/**
* Perform a simple byte-array-based RPC roundtrip.
* @param message the byte array request message to send
* @return the byte array response received
* @throws ShutdownSignalException if the connection dies during our wait
* @throws IOException if an error is encountered
* @throws TimeoutException if a response is not received within the configured timeout
*/
public byte[] primitiveCall(byte[] message)
throws IOException, ShutdownSignalException, TimeoutException {
return primitiveCall(null, message);
}
/**
* Perform a simple string-based RPC roundtrip.
* @param message the string request message to send
* @return the string response received
* @throws ShutdownSignalException if the connection dies during our wait
* @throws IOException if an error is encountered
* @throws TimeoutException if a timeout occurs before the response is received
*/
public String stringCall(String message)
throws IOException, ShutdownSignalException, TimeoutException
{
byte[] request;
try {
request = message.getBytes(StringRpcServer.STRING_ENCODING);
} catch (IOException _) {
request = message.getBytes();
}
byte[] reply = primitiveCall(request);
try {
return new String(reply, StringRpcServer.STRING_ENCODING);
} catch (IOException _) {
return new String(reply);
}
}
/**
* Perform an AMQP wire-protocol-table based RPC roundtrip <br><br>
*
* There are some restrictions on the values appearing in the table: <br>
* they must be of type {@link String}, {@link LongString}, {@link Integer}, {@link java.math.BigDecimal}, {@link Date},
* or (recursively) a {@link Map} of the enclosing type.
*
* @param message the table to send
* @return the table received
* @throws ShutdownSignalException if the connection dies during our wait
* @throws IOException if an error is encountered
* @throws TimeoutException if a timeout occurs before a response is received
*/
public Map<String, Object> mapCall(Map<String, Object> message)
throws IOException, ShutdownSignalException, TimeoutException
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
MethodArgumentWriter writer = new MethodArgumentWriter(new ValueWriter(new DataOutputStream(buffer)));
writer.writeTable(message);
writer.flush();
byte[] reply = primitiveCall(buffer.toByteArray());
MethodArgumentReader reader =
new MethodArgumentReader(new ValueReader(new DataInputStream(new ByteArrayInputStream(reply))));
return reader.readTable();
}
/**
* Perform an AMQP wire-protocol-table based RPC roundtrip, first
* constructing the table from an array of alternating keys (in
* even-numbered elements, starting at zero) and values (in
* odd-numbered elements, starting at one) <br>
* Restrictions on value arguments apply as in {@link RpcClient#mapCall(Map)}.
*
* @param keyValuePairs alternating {key, value, key, value, ...} data to send
* @return the table received
* @throws ShutdownSignalException if the connection dies during our wait
* @throws IOException if an error is encountered
* @throws TimeoutException if a timeout occurs before a response is received
*/
public Map<String, Object> mapCall(Object[] keyValuePairs)
throws IOException, ShutdownSignalException, TimeoutException
{
Map<String, Object> message = new HashMap<String, Object>();
for (int i = 0; i < keyValuePairs.length; i += 2) {
message.put((String) keyValuePairs[i], keyValuePairs[i + 1]);
}
return mapCall(message);
}
/**
* Retrieve the channel.
* @return the channel to which this client is connected
*/
public Channel getChannel() {
return _channel;
}
/**
* Retrieve the exchange.
* @return the exchange to which this client is connected
*/
public String getExchange() {
return _exchange;
}
/**
* Retrieve the routing key.
* @return the routing key for messages to this client
*/
public String getRoutingKey() {
return _routingKey;
}
/**
* Retrieve the continuation map.
* @return the map of objects to blocking cells for this client
*/
public Map<String, BlockingCell<Object>> getContinuationMap() {
return _continuationMap;
}
/**
* Retrieve the correlation id.
* @return the most recently used correlation id
*/
public int getCorrelationId() {
return _correlationId;
}
/**
* Retrieve the reply queue.
* @return the name of the client's reply queue
*/
public String getReplyQueue() {
return _replyQueue;
}
/**
* Retrieve the consumer.
* @return an interface to the client's consumer object
*/
public Consumer getConsumer() {
return _consumer;
}
}
RpcServer.java类
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License
// at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
// the License for the specific language governing rights and
// limitations under the License.
//
// The Original Code is RabbitMQ.
//
// The Initial Developer of the Original Code is VMware, Inc.
// Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
//
package com.rabbitmq.client;
import java.io.IOException;
/**
* Class which manages a request queue for a simple RPC-style service.
* The class is agnostic about the format of RPC arguments / return values.
*/
public class RpcServer {
/** Channel we are communicating on */
private final Channel _channel;
/** Queue to receive requests from */
private final String _queueName;
/** Boolean controlling the exit from the mainloop. */
private boolean _mainloopRunning = true;
/** Consumer attached to our request queue */
private QueueingConsumer _consumer;
/**
* Creates an RpcServer listening on a temporary exclusive
* autodelete queue.
*/
public RpcServer(Channel channel)
throws IOException
{
this(channel, null);
}
/**
* If the passed-in queue name is null, creates a server-named
* temporary exclusive autodelete queue to use; otherwise expects
* the queue to have already been declared.
*/
public RpcServer(Channel channel, String queueName)
throws IOException
{
_channel = channel;
if (queueName == null || queueName.equals("")) {
_queueName = _channel.queueDeclare().getQueue();
} else {
_queueName = queueName;
}
_consumer = setupConsumer();
}
/**
* Public API - cancels the consumer, thus deleting the queue, if
* it was a temporary queue, and marks the RpcServer as closed.
* @throws IOException if an error is encountered
*/
public void close()
throws IOException
{
if (_consumer != null) {
_channel.basicCancel(_consumer.getConsumerTag());
_consumer = null;
}
terminateMainloop();
}
/**
* Registers a consumer on the reply queue.
* @throws IOException if an error is encountered
* @return the newly created and registered consumer
*/
protected QueueingConsumer setupConsumer()
throws IOException
{
QueueingConsumer consumer = new QueueingConsumer(_channel);
_channel.basicConsume(_queueName, consumer);
return consumer;
}
/**
* Public API - main server loop. Call this to begin processing
* requests. Request processing will continue until the Channel
* (or its underlying Connection) is shut down, or until
* terminateMainloop() is called.
*
* Note that if the mainloop is blocked waiting for a request, the
* termination flag is not checked until a request is received, so
* a good time to call terminateMainloop() is during a request
* handler.
*
* @return the exception that signalled the Channel shutdown, or null for orderly shutdown
*/
public ShutdownSignalException mainloop()
throws IOException
{
try {
while (_mainloopRunning) {
QueueingConsumer.Delivery request;
try {
request = _consumer.nextDelivery();
} catch (InterruptedException ie) {
continue;
}
processRequest(request);
_channel.basicAck(request.getEnvelope().getDeliveryTag(), false);
}
return null;
} catch (ShutdownSignalException sse) {
return sse;
}
}
/**
* Call this method to terminate the mainloop.
*
* Note that if the mainloop is blocked waiting for a request, the
* termination flag is not checked until a request is received, so
* a good time to call terminateMainloop() is during a request
* handler.
*/
public void terminateMainloop() {
_mainloopRunning = false;
}
/**
* Private API - Process a single request. Called from mainloop().
*/
public void processRequest(QueueingConsumer.Delivery request)
throws IOException
{
AMQP.BasicProperties requestProperties = request.getProperties();
String correlationId = requestProperties.getCorrelationId();
String replyTo = requestProperties.getReplyTo();
if (correlationId != null && replyTo != null)
{
AMQP.BasicProperties replyProperties
= new AMQP.BasicProperties.Builder().correlationId(correlationId).build();
byte[] replyBody = handleCall(request, replyProperties);
_channel.basicPublish("", replyTo, replyProperties, replyBody);
} else {
handleCast(request);
}
}
/**
* Lowest-level response method. Calls
* handleCall(AMQP.BasicProperties,byte[],AMQP.BasicProperties).
*/
public byte[] handleCall(QueueingConsumer.Delivery request,
AMQP.BasicProperties replyProperties)
{
return handleCall(request.getProperties(),
request.getBody(),
replyProperties);
}
/**
* Mid-level response method. Calls
* handleCall(byte[],AMQP.BasicProperties).
*/
public byte[] handleCall(AMQP.BasicProperties requestProperties,
byte[] requestBody,
AMQP.BasicProperties replyProperties)
{
return handleCall(requestBody, replyProperties);
}
/**
* High-level response method. Returns an empty response by
* default - override this (or other handleCall and handleCast
* methods) in subclasses.
*/
public byte[] handleCall(byte[] requestBody,
AMQP.BasicProperties replyProperties)
{
return new byte[0];
}
/**
* Lowest-level handler method. Calls
* handleCast(AMQP.BasicProperties,byte[]).
*/
public void handleCast(QueueingConsumer.Delivery request)
{
handleCast(request.getProperties(), request.getBody());
}
/**
* Mid-level handler method. Calls
* handleCast(byte[]).
*/
public void handleCast(AMQP.BasicProperties requestProperties, byte[] requestBody)
{
handleCast(requestBody);
}
/**
* High-level handler method. Does nothing by default - override
* this (or other handleCast and handleCast methods) in
* subclasses.
*/
public void handleCast(byte[] requestBody)
{
// Does nothing.
}
/**
* Retrieve the channel.
* @return the channel to which this server is connected
*/
public Channel getChannel() {
return _channel;
}
/**
* Retrieve the queue name.
* @return the queue which this server is consuming from
*/
public String getQueueName() {
return _queueName;
}
}上述是rabbitmq的主要出入口,描述了其中的基础操作,其中涉及到reply的消息确认机制(publish confirm)的问题,因为需要等待异步消息的确认,所以上述源码中使用异步锁来锁定每个消息的确认分发到每个节点或者磁盘上。rabbitmq中的事务处理相比于消息确认严重影响性能,而官网给的说法是需要回滚的事务都是人为的错误导致,因此可忽略回滚带来的负面问题。
事务的特性:事务能覆盖发布内容和应答,但不能覆盖投递(deliveries). 因此回滚不能导致消息重新入队或者重新投递, 客户端有权在事务中确认这些消息。
消息的确认和应答:应答是从客户端程序发出的正式信号,用以表示消息队列中的消息已经得到成功处理. 有两种应答模型:
1. 自动地(Automatic),在这种情况下,只要消息投递到了应用程序,服务器就会立即从消息队列中删除消息(通过 Deliver 或 Get-Ok 方法)。
2. 明确地(Explicit),在这种情况下,客户端程序必须对每个消息发布一个Ack方法以表示消息被处理了.客户端层可以不同方式来实现明确应答,如.只要收到了消息或当应用程序表示消息已经处理了。
这些区别不会影响AMQP或互操作性。
(2)spring-amqp-1.1.1RELEASE.jar的分析
该jar包主要定义了交换器、banding、message、queue、异常处理、消息处理等接口,具体实现较少,看到上面的接口定义可以了解到该jar的作用基本是针对消息的处理而设计的,由于都是接口就不多说明,顺便再次吐槽下代码不能伸缩看去比较心累,直接上一张源码图吧
上图的很多定义在AMQP-0-9-1的中文规范中很多都有说明,所以有空就看看该文档吧。
(3)spring-rabbit-1.1.1RELEASE.jar的分析
该包主要是结合spring对rabbitmq进行封装成接口,也提供了注解,所以在此也提供下rabbitmq在spring中的配置,至于spring的配置就不写了,spring都还不懂的还是基础打捞了先吧!
发布的spring配置:
<rabbit:connection-factory id="connectionFactory"
addresses="${mq.url}" username="${mq.user}" password="${mq.password}" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:template id="rabbitTemplate"
connection-factory="connectionFactory" queue="${mq.quename}" exchange="${mq.exchange}"
routing-key="${mq.routekey}">
</rabbit:template>
<bean id="mqSendService" class="com.bixiaapi.service.impl.MqSendServiceImpl">
<property name="rabbitTemplate">
<ref bean="rabbitTemplate" />
</property>
</bean>
<rabbit:direct-exchange name="${mq.exchange}">
<rabbit:bindings>
<rabbit:binding queue="${mq.quename}" key="${mq.routekey}">
</rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:queue name="${mq.quename}">
</rabbit:queue>
上述使用的是rabbitmq的模板方法直接使用convertAndSend(String message)方法发送消息。
订阅的spring配置:
<rabbit:connection-factory id="connectionFactory"
addresses="${mq.url}" username="${mq.user}" password="${mq.password}" />
<rabbit:admin connection-factory="connectionFactory" />
<bean id="mqReciveService" class="com.hebaobackapi.service.impl.MqReciveServiceImpl">
</bean>
<rabbit:direct-exchange name="${mq.exchange}">
<rabbit:bindings>
<rabbit:binding queue="${mq.quename}" key="${mq.routekey}">
</rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:queue name="${mq.quename}"/>
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="mqListenerAdapter" queue-names="${mq.quename}"/>
</rabbit:listener-container>
<bean id="mqListenerAdapter"
class="org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter">
<constructor-arg ref="mqReciveService" />
<property name="defaultListenerMethod" value="recive(类中消费消息的方法recive(Object obj)名称)"></property>
<property name="messageConverter" ref="messageConverter"></property>
</bean>多了个消息监听器,用于消息到达后的一个适配器,从而转化消息格式并返回Response,不懂的自己研究就酱。
下面提供主要的两个类(这可以仔细看看)
其实方法也不多,还是看下源码吧
RabbitAdmin类
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.amqp.rabbit.core;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionListener;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.util.Assert;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
/**
* RabbitMQ implementation of portable AMQP administrative operations for AMQP >= 0.9.1
*
* @author Mark Pollack
* @author Mark Fisher
* @author Dave Syer
* @author Ed Scriven
*/
public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, InitializingBean {
protected static final String DEFAULT_EXCHANGE_NAME = "";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private final RabbitTemplate rabbitTemplate;
private volatile boolean running = false;
private volatile boolean autoStartup = true;
private volatile ApplicationContext applicationContext;
private final Object lifecycleMonitor = new Object();
private final ConnectionFactory connectionFactory;
public RabbitAdmin(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
this.rabbitTemplate = new RabbitTemplate(connectionFactory);
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public RabbitTemplate getRabbitTemplate() {
return this.rabbitTemplate;
}
// Exchange operations
public void declareExchange(final Exchange exchange) {
this.rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
declareExchanges(channel, exchange);
return null;
}
});
}
@ManagedOperation
public boolean deleteExchange(final String exchangeName) {
return this.rabbitTemplate.execute(new ChannelCallback<Boolean>() {
public Boolean doInRabbit(Channel channel) throws Exception {
if (isDeletingDefaultExchange(exchangeName)) {
return true;
}
try {
channel.exchangeDelete(exchangeName);
} catch (IOException e) {
return false;
}
return true;
}
});
}
// Queue operations
@ManagedOperation
public void declareQueue(final Queue queue) {
this.rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
declareQueues(channel, queue);
return null;
}
});
}
/**
* Declares a server-named exclusive, autodelete, non-durable queue.
*/
@ManagedOperation
public Queue declareQueue() {
DeclareOk declareOk = this.rabbitTemplate.execute(new ChannelCallback<DeclareOk>() {
public DeclareOk doInRabbit(Channel channel) throws Exception {
return channel.queueDeclare();
}
});
Queue queue = new Queue(declareOk.getQueue(), false, true, true);
return queue;
}
@ManagedOperation
public boolean deleteQueue(final String queueName) {
return this.rabbitTemplate.execute(new ChannelCallback<Boolean>() {
public Boolean doInRabbit(Channel channel) throws Exception {
try {
channel.queueDelete(queueName);
} catch (IOException e) {
return false;
}
return true;
}
});
}
@ManagedOperation
public void deleteQueue(final String queueName, final boolean unused, final boolean empty) {
this.rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
channel.queueDelete(queueName, unused, empty);
return null;
}
});
}
@ManagedOperation
public void purgeQueue(final String queueName, final boolean noWait) {
this.rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
channel.queuePurge(queueName);
return null;
}
});
}
// Binding
@ManagedOperation
public void declareBinding(final Binding binding) {
this.rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
declareBindings(channel, binding);
return null;
}
});
}
@ManagedOperation
public void removeBinding(final Binding binding) {
rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
if (binding.isDestinationQueue()) {
if (isRemovingImplicitQueueBinding(binding)) {
return null;
}
channel.queueUnbind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
binding.getArguments());
} else {
channel.exchangeUnbind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
binding.getArguments());
}
return null;
}
});
}
// Lifecycle implementation
public boolean isAutoStartup() {
return this.autoStartup;
}
/**
* If {@link #setAutoStartup(boolean) autoStartup} is set to true, registers a callback on the
* {@link ConnectionFactory} to declare all exchanges and queues in the enclosing application context. If the
* callback fails then it may cause other clients of the connection factory to fail, but since only exchanges,
* queues and bindings are declared failure is not expected.
*
* @see InitializingBean#afterPropertiesSet()
* @see #initialize()
*/
public void afterPropertiesSet() {
synchronized (this.lifecycleMonitor) {
if (this.running || !this.autoStartup) {
return;
}
connectionFactory.addConnectionListener(new ConnectionListener() {
// Prevent stack overflow...
private AtomicBoolean initializing = new AtomicBoolean(false);
public void onCreate(Connection connection) {
if (!initializing.compareAndSet(false, true)) {
// If we are already initializing, we don't need to do it again...
return;
}
try {
/*
* ...but it is possible for this to happen twice in the same ConnectionFactory (if more than
* one concurrent Connection is allowed). It's idempotent, so no big deal (a bit of network
* chatter). In fact it might even be a good thing: exclusive queues only make sense if they are
* declared for every connection. If anyone has a problem with it: use auto-startup="false".
*/
initialize();
} finally {
initializing.compareAndSet(true, false);
}
}
public void onClose(Connection connection) {
}
});
this.running = true;
}
}
/**
* Declares all the exchanges, queues and bindings in the enclosing application context, if any. It should be safe
* (but unnecessary) to call this method more than once.
*/
public void initialize() {
if (this.applicationContext == null) {
if (this.logger.isDebugEnabled()) {
this.logger
.debug("no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
}
return;
}
logger.debug("Initializing declarations");
final Collection<Exchange> exchanges = applicationContext.getBeansOfType(Exchange.class).values();
final Collection<Queue> queues = applicationContext.getBeansOfType(Queue.class).values();
final Collection<Binding> bindings = applicationContext.getBeansOfType(Binding.class).values();
for (Exchange exchange : exchanges) {
if (!exchange.isDurable()) {
logger.warn("Auto-declaring a non-durable Exchange ("
+ exchange.getName()
+ "). It will be deleted by the broker if it shuts down, and can be redeclared by closing and reopening the connection.");
}
if (exchange.isAutoDelete()) {
logger.warn("Auto-declaring an auto-delete Exchange ("
+ exchange.getName()
+ "). It will be deleted by the broker if not in use (if all bindings are deleted), but will only be redeclared if the connection is closed and reopened.");
}
}
for (Queue queue : queues) {
if (!queue.isDurable()) {
logger.warn("Auto-declaring a non-durable Queue ("
+ queue.getName()
+ "). It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.");
}
if (queue.isAutoDelete()) {
logger.warn("Auto-declaring an auto-delete Queue ("
+ queue.getName()
+ "). It will be deleted by the broker if not in use, and all messages will be lost. Redeclared when the connection is closed and reopened.");
}
if (queue.isExclusive()) {
logger.warn("Auto-declaring an exclusive Queue ("
+ queue.getName()
+ "). It cannot be accessed by consumers on another connection, and will be redeclared if the connection is reopened.");
}
}
rabbitTemplate.execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
declareExchanges(channel, exchanges.toArray(new Exchange[exchanges.size()]));
declareQueues(channel, queues.toArray(new Queue[queues.size()]));
declareBindings(channel, bindings.toArray(new Binding[bindings.size()]));
return null;
}
});
logger.debug("Declarations finished");
}
// private methods for declaring Exchanges, Queues, and Bindings on a Channel
private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException {
for (final Exchange exchange : exchanges) {
if (logger.isDebugEnabled()) {
logger.debug("declaring Exchange '" + exchange.getName() + "'");
}
if (!isDeclaringDefaultExchange(exchange)) {
channel.exchangeDeclare(exchange.getName(), exchange.getType(), exchange.isDurable(),
exchange.isAutoDelete(), exchange.getArguments());
}
}
}
private void declareQueues(final Channel channel, final Queue... queues) throws IOException {
for (Queue queue : queues) {
if (!queue.getName().startsWith("amq.")) {
if (logger.isDebugEnabled()) {
logger.debug("declaring Queue '" + queue.getName() + "'");
}
channel.queueDeclare(queue.getName(), queue.isDurable(), queue.isExclusive(), queue.isAutoDelete(),
queue.getArguments());
} else if (logger.isDebugEnabled()) {
logger.debug("Queue with name that starts with 'amq.' cannot be declared.");
}
}
}
private void declareBindings(final Channel channel, final Binding... bindings) throws IOException {
for (Binding binding : bindings) {
if (logger.isDebugEnabled()) {
logger.debug("Binding destination [" + binding.getDestination() + " (" + binding.getDestinationType()
+ ")] to exchange [" + binding.getExchange() + "] with routing key [" + binding.getRoutingKey()
+ "]");
}
if (binding.isDestinationQueue()) {
if (!isDeclaringImplicitQueueBinding(binding)) {
channel.queueBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
binding.getArguments());
}
} else {
channel.exchangeBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
binding.getArguments());
}
}
}
private boolean isDeclaringDefaultExchange(Exchange exchange) {
if (isDefaultExchange(exchange.getName())) {
if (logger.isDebugEnabled()) {
logger.debug("Default exchange is pre-declared by server.");
}
return true;
}
return false;
}
private boolean isDeletingDefaultExchange(String exchangeName) {
if (isDefaultExchange(exchangeName)) {
if (logger.isDebugEnabled()) {
logger.debug("Default exchange cannot be deleted.");
}
return true;
}
return false;
}
private boolean isDefaultExchange(String exchangeName) {
return DEFAULT_EXCHANGE_NAME.equals(exchangeName);
}
private boolean isDeclaringImplicitQueueBinding(Binding binding) {
if (isImplicitQueueBinding(binding)) {
if (logger.isDebugEnabled()) {
logger.debug("The default exchange is implicitly bound to every queue, with a routing key equal to the queue name.");
}
return true;
}
return false;
}
private boolean isRemovingImplicitQueueBinding(Binding binding) {
if (isImplicitQueueBinding(binding)) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot remove implicit default exchange binding to queue.");
}
return true;
}
return false;
}
private boolean isImplicitQueueBinding(Binding binding) {
return isDefaultExchange(binding.getExchange()) && binding.getDestination().equals(binding.getRoutingKey());
}
}
实现便捷式AMQP>=0-9-1版本的管理操作,基本都是概念方法的定义,具体实现可以了解下。
RabbitTemplate类
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.amqp.rabbit.core;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.AmqpIllegalStateException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import org.springframework.amqp.rabbit.connection.RabbitAccessor;
import org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import org.springframework.amqp.rabbit.connection.RabbitUtils;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.PendingConfirm;
import org.springframework.amqp.rabbit.support.PublisherCallbackChannel;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.GetResponse;
/**
* <p>
* Helper class that simplifies synchronous RabbitMQ access (sending and receiving messages).
* </p>
*
* <p>
* The default settings are for non-transactional messaging, which reduces the amount of data exchanged with the broker.
* To use a new transaction for every send or receive set the {@link #setChannelTransacted(boolean) channelTransacted}
* flag. To extend the transaction over multiple invocations (more efficient), you can use a Spring transaction to
* bracket the calls (with <code>channelTransacted=true</code> as well).
* </p>
*
* <p>
* The only mandatory property is the {@link #setConnectionFactory(ConnectionFactory) ConnectionFactory}. There are
* strategies available for converting messages to and from Java objects (
* {@link #setMessageConverter(MessageConverter) MessageConverter}) and for converting message headers (known as message
* properties in AMQP, see {@link #setMessagePropertiesConverter(MessagePropertiesConverter) MessagePropertiesConverter}
* ). The defaults probably do something sensible for typical use cases, as long as the message content-type is set
* appropriately.
* </p>
*
* <p>
* The "send" methods all have overloaded versions that allow you to explicitly target an exchange and a routing key, or
* you can set default values to be used in all send operations. The plain "receive" methods allow you to explicitly
* target a queue to receive from, or you can set a default value for the template that applies to all explicit
* receives. The convenience methods for send <b>and</b> receive use the sender defaults if no exchange or routing key
* is specified, but they always use a temporary queue for the receive leg, so the default queue is ignored.
* </p>
*
* @author Mark Pollack
* @author Mark Fisher
* @author Dave Syer
* @author Gary Russell
* @since 1.0
*/
public class RabbitTemplate extends RabbitAccessor implements RabbitOperations, MessageListener,
PublisherCallbackChannel.Listener {
private static final String DEFAULT_EXCHANGE = ""; // alias for amq.direct default exchange
private static final String DEFAULT_ROUTING_KEY = "";
private static final long DEFAULT_REPLY_TIMEOUT = 5000;
private static final String DEFAULT_ENCODING = "UTF-8";
private volatile String exchange = DEFAULT_EXCHANGE;
private volatile String routingKey = DEFAULT_ROUTING_KEY;
// The default queue name that will be used for synchronous receives.
private volatile String queue;
private volatile long replyTimeout = DEFAULT_REPLY_TIMEOUT;
private volatile MessageConverter messageConverter = new SimpleMessageConverter();
private volatile MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter();
private volatile String encoding = DEFAULT_ENCODING;
private volatile Queue replyQueue;
private final Map<String, LinkedBlockingQueue<Message>> replyHolder = new ConcurrentHashMap<String, LinkedBlockingQueue<Message>>();
private volatile ConfirmCallback confirmCallback;
private volatile ReturnCallback returnCallback;
private final Map<Object, SortedMap<Long, PendingConfirm>> pendingConfirms = new ConcurrentHashMap<Object, SortedMap<Long, PendingConfirm>>();
private volatile boolean mandatory;
private volatile boolean immediate;
private final String uuid = UUID.randomUUID().toString();
public static final String STACKED_CORRELATION_HEADER = "spring_reply_correlation";
public static final String STACKED_REPLY_TO_HEADER = "spring_reply_to";
/**
* Convenient constructor for use with setter injection. Don't forget to set the connection factory.
*/
public RabbitTemplate() {
initDefaultStrategies();
}
/**
* Create a rabbit template with default strategies and settings.
*
* @param connectionFactory the connection factory to use
*/
public RabbitTemplate(ConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
/**
* Set up the default strategies. Subclasses can override if necessary.
*/
protected void initDefaultStrategies() {
setMessageConverter(new SimpleMessageConverter());
}
/**
* The name of the default exchange to use for send operations when none is specified. Defaults to <code>""</code>
* which is the default exchange in the broker (per the AMQP specification).
*
* @param exchange the exchange name to use for send operations
*/
public void setExchange(String exchange) {
this.exchange = (exchange != null) ? exchange : DEFAULT_EXCHANGE;
}
/**
* The value of a default routing key to use for send operations when none is specified. Default is empty which is
* not helpful when using the default (or any direct) exchange, but fine if the exchange is a headers exchange for
* instance.
*
* @param routingKey the default routing key to use for send operations
*/
public void setRoutingKey(String routingKey) {
this.routingKey = routingKey;
}
/**
* The name of the default queue to receive messages from when none is specified explicitly.
*
* @param queue the default queue name to use for receive
*/
public void setQueue(String queue) {
this.queue = queue;
}
/**
* The encoding to use when inter-converting between byte arrays and Strings in message properties.
*
* @param encoding the encoding to set
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* A queue for replies; if not provided, a temporary exclusive, auto-delete queue will
* be used for each reply.
*
* @param replyQueue the replyQueue to set
*/
public void setReplyQueue(Queue replyQueue) {
this.replyQueue = replyQueue;
}
/**
* Specify the timeout in milliseconds to be used when waiting for a reply Message when using one of the
* sendAndReceive methods. The default value is defined as {@link #DEFAULT_REPLY_TIMEOUT}. A negative value
* indicates an indefinite timeout. Not used in the plain receive methods because there is no blocking receive
* operation defined in the protocol.
*
* @param replyTimeout the reply timeout in milliseconds
*
* @see #sendAndReceive(String, String, Message)
* @see #convertSendAndReceive(String, String, Object)
*/
public void setReplyTimeout(long replyTimeout) {
this.replyTimeout = replyTimeout;
}
/**
* Set the message converter for this template. Used to resolve Object parameters to convertAndSend methods and
* Object results from receiveAndConvert methods.
* <p>
* The default converter is a SimpleMessageConverter, which is able to handle byte arrays, Strings, and Serializable
* Objects depending on the message content type header.
*
* @see #convertAndSend
* @see #receiveAndConvert
* @see org.springframework.amqp.support.converter.SimpleMessageConverter
*/
public void setMessageConverter(MessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
/**
* Set the {@link MessagePropertiesConverter} for this template. This converter is used to convert between raw byte
* content in the message headers and plain Java objects. In particular there are limitations when dealing with very
* long string headers, which hopefully are rare in practice, but if you need to use long headers you might need to
* inject a special converter here.
*/
public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) {
Assert.notNull(messagePropertiesConverter, "messagePropertiesConverter must not be null");
this.messagePropertiesConverter = messagePropertiesConverter;
}
/**
* Return the message converter for this template. Useful for clients that want to take advantage of the converter
* in {@link ChannelCallback} implementations.
*/
public MessageConverter getMessageConverter() {
return this.messageConverter;
}
public void setConfirmCallback(ConfirmCallback confirmCallback) {
Assert.state(this.confirmCallback == null || this.confirmCallback == confirmCallback,
"Only one ConfirmCallback is supported by each RabbitTemplate");
this.confirmCallback = confirmCallback;
}
public void setReturnCallback(ReturnCallback returnCallback) {
Assert.state(this.returnCallback == null || this.returnCallback == returnCallback,
"Only one ReturnCallback is supported by each RabbitTemplate");
this.returnCallback = returnCallback;
}
public void setMandatory(boolean mandatory) {
this.mandatory = mandatory;
}
public void setImmediate(boolean immediate) {
this.immediate = immediate;
}
/**
* Gets unconfirmed correlatiom data older than age and removes them.
* @param age in millseconds
* @return the collection of correlation data for which confirms have
* not been received.
*/
public Collection<CorrelationData> getUnconfirmed(long age) {
Set<CorrelationData> unconfirmed = new HashSet<CorrelationData>();
synchronized (this.pendingConfirms) {
long threshold = System.currentTimeMillis() - age;
for (Entry<Object, SortedMap<Long, PendingConfirm>> channelPendingConfirmEntry : this.pendingConfirms.entrySet()) {
SortedMap<Long, PendingConfirm> channelPendingConfirms = channelPendingConfirmEntry.getValue();
Iterator<Entry<Long, PendingConfirm>> iterator = channelPendingConfirms.entrySet().iterator();
PendingConfirm pendingConfirm;
while (iterator.hasNext()) {
pendingConfirm = iterator.next().getValue();
if (pendingConfirm.getTimestamp() < threshold) {
unconfirmed.add(pendingConfirm.getCorrelationData());
iterator.remove();
}
else {
break;
}
}
}
}
return unconfirmed.size() > 0 ? unconfirmed : null;
}
public void send(Message message) throws AmqpException {
send(this.exchange, this.routingKey, message);
}
public void send(String routingKey, Message message) throws AmqpException {
send(this.exchange, routingKey, message);
}
public void send(final String exchange, final String routingKey, final Message message) throws AmqpException {
this.send(exchange, routingKey, message, null);
}
public void send(final String exchange, final String routingKey,
final Message message, final CorrelationData correlationData)
throws AmqpException {
execute(new ChannelCallback<Object>() {
public Object doInRabbit(Channel channel) throws Exception {
doSend(channel, exchange, routingKey, message, correlationData);
return null;
}
});
}
public void convertAndSend(Object object) throws AmqpException {
convertAndSend(this.exchange, this.routingKey, object, (CorrelationData) null);
}
public void correlationconvertAndSend(Object object, CorrelationData correlationData) throws AmqpException {
convertAndSend(this.exchange, this.routingKey, object, correlationData);
}
public void convertAndSend(String routingKey, final Object object) throws AmqpException {
convertAndSend(this.exchange, routingKey, object, (CorrelationData) null);
}
public void convertAndSend(String routingKey, final Object object, CorrelationData correlationData) throws AmqpException {
convertAndSend(this.exchange, routingKey, object, correlationData);
}
public void convertAndSend(String exchange, String routingKey, final Object object) throws AmqpException {
convertAndSend(exchange, routingKey, object, (CorrelationData) null);
}
public void convertAndSend(String exchange, String routingKey, final Object object, CorrelationData corrationData) throws AmqpException {
send(exchange, routingKey, convertMessageIfNecessary(object), corrationData);
}
public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException {
convertAndSend(this.exchange, this.routingKey, message, messagePostProcessor);
}
public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor)
throws AmqpException {
convertAndSend(this.exchange, routingKey, message, messagePostProcessor, null);
}
public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor,
CorrelationData correlationData)
throws AmqpException {
convertAndSend(this.exchange, routingKey, message, messagePostProcessor, correlationData);
}
public void convertAndSend(String exchange, String routingKey, final Object message,
final MessagePostProcessor messagePostProcessor) throws AmqpException {
convertAndSend(exchange, routingKey, message, messagePostProcessor, null);
}
public void convertAndSend(String exchange, String routingKey, final Object message,
final MessagePostProcessor messagePostProcessor, CorrelationData correlationData) throws AmqpException {
Message messageToSend = convertMessageIfNecessary(message);
messageToSend = messagePostProcessor.postProcessMessage(messageToSend);
send(exchange, routingKey, messageToSend, correlationData);
}
public Message receive() throws AmqpException {
String queue = this.getRequiredQueue();
return this.receive(queue);
}
public Message receive(final String queueName) {
return execute(new ChannelCallback<Message>() {
public Message doInRabbit(Channel channel) throws IOException {
GetResponse response = channel.basicGet(queueName, !isChannelTransacted());
// Response can be null is the case that there is no message on the queue.
if (response != null) {
long deliveryTag = response.getEnvelope().getDeliveryTag();
if (isChannelLocallyTransacted(channel)) {
channel.basicAck(deliveryTag, false);
channel.txCommit();
} else if (isChannelTransacted()) {
// Not locally transacted but it is transacted so it
// could be synchronized with an external transaction
ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel, deliveryTag);
}
MessageProperties messageProps = messagePropertiesConverter.toMessageProperties(
response.getProps(), response.getEnvelope(), encoding);
messageProps.setMessageCount(response.getMessageCount());
return new Message(response.getBody(), messageProps);
}
return null;
}
});
}
public Object receiveAndConvert() throws AmqpException {
return receiveAndConvert(this.getRequiredQueue());
}
public Object receiveAndConvert(String queueName) throws AmqpException {
Message response = receive(queueName);
if (response != null) {
return getRequiredMessageConverter().fromMessage(response);
}
return null;
}
public Message sendAndReceive(final Message message) throws AmqpException {
return this.doSendAndReceive(this.exchange, this.routingKey, message);
}
public Message sendAndReceive(final String routingKey, final Message message) throws AmqpException {
return this.doSendAndReceive(this.exchange, routingKey, message);
}
public Message sendAndReceive(final String exchange, final String routingKey, final Message message)
throws AmqpException {
return this.doSendAndReceive(exchange, routingKey, message);
}
public Object convertSendAndReceive(final Object message) throws AmqpException {
return this.convertSendAndReceive(this.exchange, this.routingKey, message, null);
}
public Object convertSendAndReceive(final String routingKey, final Object message) throws AmqpException {
return this.convertSendAndReceive(this.exchange, routingKey, message, null);
}
public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message)
throws AmqpException {
return this.convertSendAndReceive(exchange, routingKey, message, null);
}
public Object convertSendAndReceive(final Object message, final MessagePostProcessor messagePostProcessor) throws AmqpException {
return this.convertSendAndReceive(this.exchange, this.routingKey, message, messagePostProcessor);
}
public Object convertSendAndReceive(final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor)
throws AmqpException {
return this.convertSendAndReceive(this.exchange, routingKey, message, messagePostProcessor);
}
public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message,
final MessagePostProcessor messagePostProcessor) throws AmqpException {
Message requestMessage = convertMessageIfNecessary(message);
if (messagePostProcessor != null) {
requestMessage = messagePostProcessor.postProcessMessage(requestMessage);
}
Message replyMessage = this.doSendAndReceive(exchange, routingKey, requestMessage);
if (replyMessage == null) {
return null;
}
return this.getRequiredMessageConverter().fromMessage(replyMessage);
}
protected Message convertMessageIfNecessary(final Object object) {
if (object instanceof Message) {
return (Message) object;
}
return getRequiredMessageConverter().toMessage(object, new MessageProperties());
}
/**
* Send a message and wait for a reply.
*
* @param exchange the exchange name
* @param routingKey the routing key
* @param message the message to send
* @return the message that is received in reply
*/
protected Message doSendAndReceive(final String exchange, final String routingKey, final Message message) {
if (this.replyQueue == null) {
return doSendAndReceiveWithTemporary(exchange, routingKey, message);
}
else {
return doSendAndReceiveWithFixed(exchange, routingKey, message);
}
}
protected Message doSendAndReceiveWithTemporary(final String exchange, final String routingKey, final Message message) {
Message replyMessage = this.execute(new ChannelCallback<Message>() {
public Message doInRabbit(Channel channel) throws Exception {
final SynchronousQueue<Message> replyHandoff = new SynchronousQueue<Message>();
Assert.isNull(message.getMessageProperties().getReplyTo(),
"Send-and-receive methods can only be used if the Message does not already have a replyTo property.");
DeclareOk queueDeclaration = channel.queueDeclare();
String replyTo = queueDeclaration.getQueue();
message.getMessageProperties().setReplyTo(replyTo);
boolean noAck = false;
String consumerTag = UUID.randomUUID().toString();
boolean noLocal = true;
boolean exclusive = true;
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(
properties, envelope, encoding);
Message reply = new Message(body, messageProperties);
if (logger.isTraceEnabled()) {
logger.trace("Message received " + reply);
}
try {
replyHandoff.put(reply);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
channel.basicConsume(replyTo, noAck, consumerTag, noLocal, exclusive, null, consumer);
doSend(channel, exchange, routingKey, message, null);
Message reply = (replyTimeout < 0) ? replyHandoff.take() : replyHandoff.poll(replyTimeout,
TimeUnit.MILLISECONDS);
channel.basicCancel(consumerTag);
return reply;
}
});
return replyMessage;
}
protected Message doSendAndReceiveWithFixed(final String exchange, final String routingKey, final Message message) {
Message replyMessage = this.execute(new ChannelCallback<Message>() {
public Message doInRabbit(Channel channel) throws Exception {
final LinkedBlockingQueue<Message> replyHandoff = new LinkedBlockingQueue<Message>();
String messageTag = UUID.randomUUID().toString();
RabbitTemplate.this.replyHolder.put(messageTag, replyHandoff);
String replyTo = message.getMessageProperties().getReplyTo();
if (StringUtils.hasLength(replyTo) && logger.isDebugEnabled()) {
logger.debug("Dropping replyTo header:" + replyTo
+ " in favor of template's configured reply-queue:"
+ RabbitTemplate.this.replyQueue.getName());
}
String springReplyTo = (String) message.getMessageProperties()
.getHeaders().get(STACKED_REPLY_TO_HEADER);
message.getMessageProperties().setHeader(
STACKED_REPLY_TO_HEADER,
pushHeaderValue(replyTo,
springReplyTo));
message.getMessageProperties().setReplyTo(RabbitTemplate.this.replyQueue.getName());
String correlation = (String) message.getMessageProperties()
.getHeaders().get(STACKED_CORRELATION_HEADER);
if (StringUtils.hasLength(correlation)) {
message.getMessageProperties().setHeader(
STACKED_CORRELATION_HEADER,
pushHeaderValue(messageTag, correlation));
} else {
message.getMessageProperties().setHeader(
"spring_reply_correlation", messageTag);
}
if (logger.isDebugEnabled()) {
logger.debug("Sending message with tag " + messageTag);
}
doSend(channel, exchange, routingKey, message, null);
Message reply = (replyTimeout < 0) ? replyHandoff.take() : replyHandoff.poll(replyTimeout,
TimeUnit.MILLISECONDS);
RabbitTemplate.this.replyHolder.remove(messageTag);
return reply;
}
});
return replyMessage;
}
public <T> T execute(ChannelCallback<T> action) {
Assert.notNull(action, "Callback object must not be null");
RabbitResourceHolder resourceHolder = getTransactionalResourceHolder();
Channel channel = resourceHolder.getChannel();
if (this.confirmCallback != null || this.returnCallback != null) {
addListener(channel);
}
try {
if (logger.isDebugEnabled()) {
logger.debug("Executing callback on RabbitMQ Channel: " + channel);
}
return action.doInRabbit(channel);
} catch (Exception ex) {
if (isChannelLocallyTransacted(channel)) {
resourceHolder.rollbackAll();
}
throw convertRabbitAccessException(ex);
} finally {
ConnectionFactoryUtils.releaseResources(resourceHolder);
}
}
/**
* Send the given message to the specified exchange.
*
* @param channel the RabbitMQ Channel to operate within
* @param exchange the name of the RabbitMQ exchange to send to
* @param routingKey the routing key
* @param message the Message to send
* @throws IOException if thrown by RabbitMQ API methods
*/
protected void doSend(Channel channel, String exchange, String routingKey, Message message,
CorrelationData correlationData) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Publishing message on exchange [" + exchange + "], routingKey = [" + routingKey + "]");
}
if (exchange == null) {
// try to send to configured exchange
exchange = this.exchange;
}
if (routingKey == null) {
// try to send to configured routing key
routingKey = this.routingKey;
}
if (this.confirmCallback != null && channel instanceof PublisherCallbackChannel) {
PublisherCallbackChannel publisherCallbackChannel = (PublisherCallbackChannel) channel;
publisherCallbackChannel.addPendingConfirm(this, channel.getNextPublishSeqNo(),
new PendingConfirm(correlationData, System.currentTimeMillis()));
}
boolean mandatory = this.returnCallback == null ? false : this.mandatory;
boolean immediate = this.returnCallback == null ? false : this.immediate;
MessageProperties messageProperties = message.getMessageProperties();
if (mandatory || immediate) {
messageProperties.getHeaders().put(PublisherCallbackChannel.RETURN_CORRELATION, this.uuid);
}
BasicProperties convertedMessageProperties = this.messagePropertiesConverter
.fromMessageProperties(messageProperties, encoding);
channel.basicPublish(exchange, routingKey, mandatory, immediate,
convertedMessageProperties, message.getBody());
// Check if commit needed
if (isChannelLocallyTransacted(channel)) {
// Transacted channel created by this template -> commit.
RabbitUtils.commitIfNecessary(channel);
}
}
/**
* Check whether the given Channel is locally transacted, that is, whether its transaction is managed by this
* template's Channel handling and not by an external transaction coordinator.
*
* @param channel the Channel to check
* @return whether the given Channel is locally transacted
* @see ConnectionFactoryUtils#isChannelTransactional
* @see #isChannelTransacted
*/
protected boolean isChannelLocallyTransacted(Channel channel) {
return isChannelTransacted() && !ConnectionFactoryUtils.isChannelTransactional(channel, getConnectionFactory());
}
private MessageConverter getRequiredMessageConverter() throws IllegalStateException {
MessageConverter converter = this.getMessageConverter();
if (converter == null) {
throw new AmqpIllegalStateException(
"No 'messageConverter' specified. Check configuration of RabbitTemplate.");
}
return converter;
}
private String getRequiredQueue() throws IllegalStateException {
String name = this.queue;
if (name == null) {
throw new AmqpIllegalStateException("No 'queue' specified. Check configuration of RabbitTemplate.");
}
return name;
}
private void addListener(Channel channel) {
if (channel instanceof PublisherCallbackChannel) {
PublisherCallbackChannel publisherCallbackChannel = (PublisherCallbackChannel) channel;
SortedMap<Long, PendingConfirm> pendingConfirms = publisherCallbackChannel.addListener(this);
if (!this.pendingConfirms.containsKey(channel)) {
this.pendingConfirms.put(channel, pendingConfirms);
if (logger.isDebugEnabled()) {
logger.debug("Added pending confirms for " + channel + " to map, size now " + this.pendingConfirms.size());
}
}
}
else {
throw new IllegalStateException(
"Channel does not support confirms or returns; " +
"is the connection factory configured for confirms or returns?");
}
}
public void handleConfirm(PendingConfirm pendingConfirm, boolean ack) {
if (this.confirmCallback != null) {
this.confirmCallback.confirm(pendingConfirm.getCorrelationData(), ack);
}
else {
if (logger.isDebugEnabled()) {
logger.warn("Confirm received but no callback available");
}
}
}
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
BasicProperties properties,
byte[] body)
throws IOException
{
if (this.returnCallback == null) {
if (logger.isWarnEnabled()) {
logger.warn("Returned message but no callback available");
}
}
else {
properties.getHeaders().remove(PublisherCallbackChannel.RETURN_CORRELATION);
MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(
properties, null, this.encoding);
Message returnedMessage = new Message(body, messageProperties);
this.returnCallback.returnedMessage(returnedMessage,
replyCode, replyText, exchange, routingKey);
}
}
public boolean isConfirmListener() {
return this.confirmCallback != null;
}
public boolean isReturnListener() {
return this.returnCallback != null;
}
public void removePendingConfirmsReference(Channel channel,
SortedMap<Long, PendingConfirm> unconfirmed) {
this.pendingConfirms.remove(channel);
if (logger.isDebugEnabled()) {
logger.debug("Removed pending confirms for " + channel + " from map, size now " + this.pendingConfirms.size());
}
}
public String getUUID() {
return this.uuid;
}
public void onMessage(Message message) {
String messageTag = (String) message.getMessageProperties()
.getHeaders().get(STACKED_CORRELATION_HEADER);
if (messageTag == null) {
logger.error("No correlation header in reply");
return;
}
PoppedHeader poppedHeaderValue = popHeaderValue(messageTag);
messageTag = poppedHeaderValue.getPoppedValue();
message.getMessageProperties().setHeader(STACKED_CORRELATION_HEADER,
poppedHeaderValue.getNewValue());
String springReplyTo = (String) message.getMessageProperties()
.getHeaders().get(STACKED_REPLY_TO_HEADER);
if (springReplyTo != null) {
poppedHeaderValue = popHeaderValue(springReplyTo);
springReplyTo = poppedHeaderValue.getNewValue();
message.getMessageProperties().setHeader(STACKED_REPLY_TO_HEADER, springReplyTo);
message.getMessageProperties().setReplyTo(null);
}
LinkedBlockingQueue<Message> queue = this.replyHolder.get(messageTag);
if (queue == null) {
if (logger.isWarnEnabled()) {
logger.warn("Reply received after timeout for " + messageTag);
}
return;
}
queue.add(message);
if (logger.isDebugEnabled()) {
logger.debug("Reply received for " + messageTag);
}
}
private String pushHeaderValue(String newValue, String oldValue) {
if (oldValue == null) {
return newValue;
}
else {
return newValue + ":" + oldValue;
}
}
private PoppedHeader popHeaderValue(String value) {
int index = value.indexOf(":");
if (index < 0) {
return new PoppedHeader(value, null);
}
else {
return new PoppedHeader(value.substring(0, index), value.substring(index+1));
}
}
private static class PoppedHeader {
private final String poppedValue;
private final String newValue;
public PoppedHeader(String poppedValue, String newValue) {
this.poppedValue = poppedValue;
if (StringUtils.hasLength(newValue)) {
this.newValue = newValue;
}
else {
this.newValue = null;
}
}
public String getPoppedValue() {
return poppedValue;
}
public String getNewValue() {
return newValue;
}
}
public static interface ConfirmCallback {
void confirm(CorrelationData correlationData, boolean ack);
}
public static interface ReturnCallback {
void returnedMessage(Message message, int replyCode, String replyText,
String exchange, String routingKey);
}
}
根据RabbitTemplate方法的说明中可以总结出:
1.该帮助类是一个简化的同步rabbitmq访问类(sending and receiving messages)。
2.默认不适用事务性的消息,而是由spring来提供一个事务来管理发送和接受消息。
3.消息类型的转换(MessageConverter)和设置(mandatory property),提供java Object类型
4.消息的接受和发送可以不提供具体的方法名使用默认的,也可以直接指定交换器、队列、路由键的名称。
至此就结束吧,下篇将根据rabbitmq实战这本书和官网来具体总结下服务端的配置和配件的细节。
本文深入解析了 RabbitMQ 的关键概念和技术细节,包括连接管理、消息通道使用、交换器与队列操作,以及消息发布与订阅流程。同时介绍了 RabbitMQ 的 Java 客户端库 amqp-client 和 Spring AMQP 的核心类 RpcClient 和 RabbitAdmin 的工作原理。
2万+

被折叠的 条评论
为什么被折叠?



