1、总体介绍
1.1 RabbitMQ是一个由Erlang语言编写的AMQP协议的开源实现.
1.2 RabbitMQ是信息传输的中间者.本质上,他从生产者(producers)接收消息,转发这些消息给消费者(consumers).换句话说,他能够按根据你指定的规则进行消息转发、缓冲、和持久化,解耦了生产者和消费者。
1.3 有着分布式,高可用,持久化,可靠,安全的特点
1.4 RabbitMQ是支持多种客户端的开发。
2、开发环境及配置
2.1下载安装Erlang
因为RabbitMQ是建立在Erlang OTP平台上的,因此安装RabbitMQ之前需要安装Erlang.配置环境变量”ERLANG_HOME”指向安装Erlang的目录,并把”%ERLANG_HOME%\bin”加到系统环境变量Path中.
2.2RabbitMQ
安装RabbitMQ Server成功,同样需要配置环境变量”RABBITMQ_BASE”指向安装目录,配置”%RABBITMQ_BASE%\sbin”到Path中。如果安装成功后双击【rabbitmq-server.bat】启动不了,提示【node with name rabbit already running on ***】的错误,就试着删除【C:\Users\Administrator\AppData\Roaming\rabbitmq】这个目录,如果还是没有效果,就点击开始菜单,在所有程序》RabbitMQ Service 》RabbitMQ Service stop,先关闭已经启动的RabbitMQ,然后再启动。
2.3rabbit-client.jar包
因为我们针对的是Android应用,所以我们需要下载一个RabbitMQ针对java语言的jar包,导入到项目中。
3、处理流程及实例
3.1RabbitMQ中基本概念理解
1)RabbitMQ Server
也叫broker server,是一种传输服务,维护一条从Producer到Consumer的路线
2)Producer
即上图中的ClientA&ClientB,是数据的发送方.作用是创造消息和发送消息到broker server.传输的Message分为两个部分payload和label。payload就是传输的数据,而label就是exchange的名字或者说是一个tag。RabbitMQ也是通过这个label来决定把这个Message发给哪个Consumer
3)Consumer
即是上图中的Client1,2,3,表示数据的接收方。Consumer依附于一个broker server 并且订阅一个queue。Consumer可以从queue中获取Message,并且做出相应的处理
4)Queues
队列,是消息的终点,可以理解成分装消息的容器。消息一直就在里面,直到有Consumer连接到这个队列并将消息取走(不过也可以设置成一旦消息进入这个队列,次消息就被删除)
5)Exchanges
交换机,可以理解成有路由表的路由程序。每一个消息都有一个routingKey的属性(就是一个简单的字符串),当中有系列的binding机制即路由规则。
6)Bindings
Binding即是一种路由规则,一个绑定就是一个基于RoutingKey将Exchange和Queue链接起来的路由规则。
7)Connection
就是一个TCP的连接。Producer和Consumer都是通过TCP连接到RabbitMQ Server的。以后我们可以看到,程序的起始处就是建立这个TCP连接。
API:
1
|
new
ConnectionFactory();
//Connection工厂类的无参构造
|
得到ConnectionFactory实例之后,其也有许多的set方法,比如设置主机地址和端口号,设置用户名和密码,设置虚拟地址,设置连接超时等等,不在做一一阐述。最后通过下列方法
1
|
factory.newConnection();
|
得到Connection对象的实例。
newConnection还有其他的重载方法,一下是参数较多的一个方法。
1
|
newConnection( ExecutorService executor,Address[] addrs,String clientProvidedName);
|
//executor:为消费者连接的线程池
//addrs:一个已知的代理地址列表,如果自动回复连接启用,遇到重连的情况会在addrs的地址列表中随机选取一个
//clientProvidedName:特定应用程序的连接名称,RabbitMQ如果支持它,其将会在UI管理中显示出来。这个值不一定是唯一的,所以不能使用作为一个连接标识符如HTTP API请求。
8)Channel
虚拟连接。它建立在上述的TCP连接中。数据流动都是在Channel中进行的。也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel。官方推荐是如果是多线程并发执行任务的时候,在一个Connection对象上创建多个Channel,如果需要公用一个Channel,至少要保证共用Channel的线程发送消息必须是串行的。
1
|
connetion.creatChannel()
//创建Channel实例
|
9)Virtual Host
虚拟的地址,其实是一个虚拟概念,类似于权限控制组,一个Virtual Host里面可以有若干个Exchange和Queue,但是权限控制的最小粒度是Virtual Host。
3.2消息的发布和接收
前文我们已经简单的介绍了Exchange,现在我们来深入了解一下。
RabbitMQ消息模型的核心理念是生产者永远不会直接发送任何消息给队列,一般的情况生产者甚至不知道消息应该发送到哪些队列。
相反的,生产者只能发送消息给转发器(Exchange)。转发器是非常简单的,一边接收从生产者发来的消息,另一边把消息推送到队列中。转发器必须清楚的知道消息如何处理它收到的每一条消息。是否应该追加到一个指定的队列?是否应该追加到多个队列?或者是否应该丢弃?这些规则通过转发器的类型进行定义,以下是声明转发器的方法和发送消息的方法。
1
|
channel.exchangeDeclare(newExchangeName,
"direct"
);
|
//声明Exchange参数一:转发器名称;参数二:转发器路由类型
//该方法还有一些重载,可以设置是否为持久的Exchange,是否可以自动删除等
1
|
channel.basicPublish(
"logs"
,
""
,
null
, message.getBytes());
|
第一个参数为转发器的名称,第二个参数是routingKey,第三个参数是MessageProperties,第四个参数就是传递的消息。Exchange会判断routingKey再决定把消息发送到哪个队列。
3.2.1临时队列
如果想要实现这么一个需求:我们想要接收到所有的消息,而且我们也只对当前正在传递的数据的感兴趣。这个时候RabbitMQ为我们提供了一个临时队列。
第一, 无论什么时间连接到Rabbit我们都需要一个新的空的队列。为了实现,我们可以使用随机数创建队列,或者更好的,让服务器给我们提供一个随机的名称。
第二, 一旦消费者与Rabbit断开,消费者所接收的那个队列应该被自动删除。
在java的RabbitMQ中我们可以使用queueDeclare()方法,不传递任何参数,来创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生,方法如下:
1
|
channel.queueDeclare();
|
3.2.2绑定
我们已经创建了一个转发器和队列,我们现在需要通过binding告诉转发器把消息发送给我们的队列。
1
|
channel.queueBind(queueName,“logs”, routingKey);
|
参数1:队列名称 ;参数2:转发器名称;参数3:绑定键名称。
这个方法还有一个四参的重载,第四个参数是一个Map集合用于其他的绑定参数的设置。
四种转发器类型:
1)Fanout
相当于广播,无论绑定键的名称是什么,消息都会被发送到于该Exchange绑定的Queue中。
2)Direct
消息会被推送至绑定键(binding key)和消息发布附带的选择键(routing key)完全匹配的队列。
当然是用一个绑定键也可以与多个Queue相绑定:
3)Topic
发往主题类型的转发器的消息不能随意的设置选择键(routing_key),必须是由点隔开的一系列的标识符组成。
绑定键和选择键的形式一样。主题类型的转发器背后的逻辑和直接类型的转发器很类似:一个附带特殊的选择键将会被转发到绑定键与之匹配的队列中。需要注意的是:关于绑定键有两种特殊的情况。
*可以匹配一个标识符。
#可以匹配0个或多个标识符。
比如说我们的绑定逻辑是这么写的:
1
|
channel.queueBind(queueName, EXCHANGE_NAME,
"*.orange.*"
);
|
我们的绑定键是"*.orange.*"
然后从服务端发来的Message中RoutingKey中含有"*.orange.*"特征的话,该Message就会被发往相应的队列。
3.3RabbitMQ的工作队列
3.3.1消息应答机制
如果执行一个任务需要几秒钟,可能一个Consumer在执行任务时发生中断。而RabbitMQ默认的会将一个一个的发送消息给下一个Consumer,而不考虑每个任务的时长,且是一次性分配的,并非一个一个分配的。而且RabbitMQ一旦交付了一个信息给消费者,会马上从内存中移除这个信息。这种情况下,我们会丢失已经转发给这个工作者且他还未执行的消息。(这种情况只会发生在一条Queue中有多个Consumer的情形)
而消息的应答机制是,在未关闭消息的ACK机制的情况下, 当消息从队列中提取后, 在未明确获取确认信息之前, 队列中的消息是不会被删除的. 这样, 流程上就变成, 当消息被提取之后, 队列中的这条消息处于"等待确认"的状态. 如果反馈"成功"给队列, 则消息可以安全地被删除了。如果反馈"拒绝"给队列, 则消息可能还需要再次被其它 Consumer提取。
我们可以通过消息的应答机制,来确保当Consumer被杀死的时候,RabbitMQ会把还未处理完的信息重新转发给别的消费者。
具体代码如下:
1)打开消息的应答机制
2)手动发送应答
以上就是最为基本的应答机制使用的是basicAck方法,除了这个方法外还有两个发送应答的方法,分别是basicNack和basicReject。
其区别为前者是积极的确认,即获取消息后成功的处理消息,后两者为消极的确认,为消息并不会处理。
而两个消极的响应唯一的区别就是basicReject方法只能处理一条消息,而basicNack方法可以处理多条消息。
API:
1
|
mChannel. basicAck(
long
deliveryTag,
boolean
multiple);
|
//deliveryTag:在AMQP.Basic.GetOk 或 AMQP.Basic.Deliver中获取的一个tag
//multiple:如果是true,表示确认所有的消息,直到提供的tag值,如果是false,表示仅仅确认tag值所为提供的消息
1
|
mChannel. basicNack(
long
deliveryTag,
boolean
multiple,
boolean
requeue);
|
//deliveryTag:在AMQP.Basic.GetOk 或 AMQP.Basic.Deliver中获取的一个tag
//multiple:如果是true,表示拒绝所有的消息,直到提供的tag值,如果是false,表示仅仅拒绝tag值所为提供的消息
//requeue:如果是true,表示所拒绝的消息会重新再queue中排队,如果是false,表示该消息会被丢弃
1
|
mChannel. basicReject(
long
deliveryTag,
boolean
requeue);
|
//deliveryTag:在AMQP.Basic.GetOk 或 AMQP.Basic.Deliver中获取的一个tag
//requeue:如果是true,表示所拒绝的消息会重新再queue中排队,如果是false,表示该消息会被丢弃
3.3.2消息持久化
通过消息的应答机制,我们可以做到即使Consumer即使被杀死了,消息也不会丢失,但是如果RabbitMQ服务器停止了消息依然会丢失,我们可以通过消息的持久化设置确保消息不会丢失:
1)交换机持久化
1
|
mChannel.exchangeDeclare(newExchangeName,
"direct"
,
true
);
|
2)队列的持久化
API:
1
|
channel.queueDeclare(String queue,
boolean
durable,
boolean
exclusive,
boolean
autoDelete,Map
|
//queue:队列名称
//durable:是否是一个持久化的队列
//exclusive:是否是一个独家的队列(仅限于此链接)
//autoDelete:是否是可以自动删除的
//arguments:一个MAP集合用于存储其他参数
3)标志信息的持久化
通过设置MessageProperties(implements BasicProperties)值为PERSISTENT_TEXT_PLAIN。
MessageProperties有6种预设的值,其中预设了内容的类型,是否为持久化消息,内容的优先级。上述例子就表示一个持久化的,优先级为0的,text/plain格式的消息。除了使用预设的Flag,你也可以通过Builder来构建,你需要的需求。例如:
1
2
3
4
5
6
7
8
9
|
channel.basicPublish(exchangeName, routingKey,
new
AMQP.BasicProperties
.Builder()
.contentType(
"text/plain"
)
.deliveryMode(
2
)
.priority(
1
)
.userId(
"bob"
)
.build()),
messageBodyBytes);
|
然而持久化消息需要把消息写入磁盘中,比较耗时,而不进行持久化消息处理,消息直接在内存中保存,和磁盘的效率比大概是3:1。
3.3.3消息的公平转发机制
当一个Queue有多个Consumer的时候,当前RabbitMQ的转发机制,仅仅是把Queue中的Message依次转发给Consumer,并不在乎有多少任务Consumer并未传递一个应答给RabbitMQ,这可能会造成一个消费者特别繁忙,一个消费者很快就执行完了任务。
我们可以使用basicQos方法,传递参数为prefetchCount=1,来告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。换句话说,只有在消费者空闲的时候会发送下一条信息。
4.Demo设计研究
因为RabbitMQ应用于实战的行情模块,其页面是一个TableLayout和Viewpager的联动.我认为Viewpager的每一个页面获取的数据都是不同的,应该采用”direct”的路由规则,进行精确的传输。
而且,因为获取的是实时的消息,所以我没有设置消息的应答机制和消息的持久化。
首先,我写了一个服务端的,该服务器创建了一个Connection,一个Channel,声明了一个的Exchange,且都是用了”direct”的路由规则。该Channel每隔一秒,向三个不同的Exchange中各发送一条内容不同的Message,且发送时的routingKey也不同,具体代码如下:
(其实一开始写Demo的时候我理解错了以为每条消息都需要一个Exchange,其实不然,后来我尝试了用使用相同的Exchange,只改变RoutingKey,就可以往不同Queue中传递消息)
其次,模仿了实战简单的写了一个Viewpager和Tablelayout的联动,以及获取Message并打印的逻辑。本次研究的目的是为了控制每一个客户端和服务器只有一个Connection和一个Channel,同时不对Connection和Channel进行过多的创建和销毁,基于这个目的,我写了如下的代码:
4.1ConnectionFactory进行基本设置:
4.2其次保证Connection和Channel的唯一性:
1
2
3
4
5
6
7
8
9
10
11
12
|
//使用之前的设置,建立连接
if
(mConnection ==
null
|| !mConnection.isOpen()) {
mConnection = factory.newConnection();
m++;
Log.d(
"test"
, m +
"----------------"
);
}
if
(mChannel ==
null
|| !mChannel.isOpen()) {
mChannel = mConnection.createChannel();
mChannel.exchangeDeclare(newExchangeName,
"direct"
);
n++;
Log.d(
"test"
, n +
"----------------"
);
}
|
//通过打印m和n的值记录Connection和Channel是否重复创建
4.3绑定和解绑操作
Viewpager的具体页签,解绑和绑定对应的Exchange和所对应的queue。其次我认为当Viewpager页面切换的时候,其他页面的queue接受Message是无意义的,因为我们需要的是最新的数据,所以我创建的Queue是临时的,可自动删除的队列。
1
2
3
4
5
6
7
8
9
|
if
(!TextUtils.isEmpty(lastQueueName)) {
mChannel.queueUnbind(lastQueueName, lastExchangeName, lastRoutingKey);
}
newQueueName = mChannel.queueDeclare().getQueue();
mChannel.queueBind(newQueueName, newExchangeName, newRoutingKey);
lastQueueName = newQueueName;
lastExchangeName = newExchangeName;
lastRoutingKey = newRoutingKey;
|
channel.queueUnbind()方法中需要传入三个参数,分别是队列名,交换器名和RoutingKey,同理channel.queueBind()方法也是传入这三个参数
4.4创建消费者,获取队列中的Message进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mConsumer =
new
QueueingConsumer(mChannel);
mChannel.basicConsume(newQueueName,
true
, mConsumer);
flag =
true
;
while
(flag) {
QueueingConsumer.Delivery delivery =mConsumer.nextDelivery();
String message =
new
String(delivery.getBody());
Message msg = handler.obtainMessage();
Bundle bundle =
new
Bundle();
bundle.putString(
"msg"
, message);
msg.setData(bundle);
If(flag){
handler.sendMessage(msg);
}
|
使用new QueueingConsumer()方法传入一个channel创建一个消费者,通过一个标记位,控制是否一直从该queue中获取Message并处理。
4.5.开启服务端,开启用户端,不断切换Viewpage的页面得到如下打印结果
发现每次切换页面时,该页面可以正确的得到当前传给它的Message,且Connection和Channel也只创建了一次。但是当我打开RabbitMQ的OverView查看时发现了Queue和Consumer被创建了多次。
看了一下代码,发现每次切换页面调用的方法中,都创建了一个Consumer和一个Queue,这应该是造成这个情况的原因。
4.6查询了一下官方文档,发现了其实临时队列自动删除的原理是队列没有消费者,然后我翻了一下API发现了一个cancel的方法
1
2
3
4
5
|
//取消消费者
if
(mConsumer !=
null
&& mConnection !=
null
&& mConnection.isOpen() && mChannel !=
null
&& mChannel.isOpen()) {
String consumerTag = mConsumer.getConsumerTag();
mChannel.basicCancel(consumerTag);
}
|
在每次创建Consumer之前,通过consumer.getConsumerTag()方法得到tag,通过channel.basicCancel(tag);方法取消当前的cancel;得到如下结果:
上图可以看出queue和Consumers都只有一个。
但是当我非常快速的不断来回滑动Viewpager时,经常会出现这么一个错误:
这个错误说明Channel.basicCancel(tag)方法中传入的tag是错误的,并不能关闭Comsumer。
后来想起来官方文档中有这么一句话:Channel instances must not be shared between threads. Applications should prefer using a Channel per thread instead of sharing the same Channel。说明多线程间共用一个Channel是不安全的。因为我原来的Demo中的代码逻辑是每次切换Viewpage都新创建了一个线程。我想可能是多线程的原因。
4.7修改代码使其在一个线程中
子线程中的代码:
切换Viewpager的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Override
public
void
onPageSelected(
int
position) {
switch
(position) {
case
0
:
flag =
false
;
newRoutingKey = ROUNTKEY1;
break
;
case
1
:
flag =
false
;
newRoutingKey = ROUNTKEY2;
break
;
case
2
:
flag =
false
;
newRoutingKey = ROUNTKEY3;
break
;
}
}
|
通过改变当前的ExchangeName和当前的RoutingKeyName以及控制是否从Queue中一直取Message的标记位,和记录上一次的ExchangeName,RoutingKeyName,从而改变线程的运行。(最后我所有的消息都共用了一个Exchange,仅仅只改变了RoutingKey和标记位的值)
运行代码之后,我发现上述的bug没有复现过。(其实这仅仅是打印数据,如果是更改UI的话,这个逻辑还需要修改)
然后来我把服务端每秒发送的数据量增加到20个,发现数据得获取和Channel、Connection、Queue、Consumer的数据个数都是正常的。但是这些都是仅仅只有一台客户端获取数据的情况下得到的结果,可能有所局限。
后来我看了,实战的代码,目前的实现是通过一个定时器不断的创建销毁线程和Channel实现的,我控制Channel不关闭模仿着写了一下,Queue和Consumer的数量会不断的增加,但是使用basicCancel方法,发现Coumsuer的关闭十分不好控制,可能因为每次滑动的时候原先的线程会被关闭,Consumer对象都是为空。虽然Queue在控制台上看到的状态都是闲置的,但是觉得可能对性能会有影响。后来我发现了下列的方法:
1
|
mChannel.queueDelete(lastQueueName);
|
传入队列名可以删除队列,同时Consumer的数量也减少了。
如果要控制Channel数量的不变,在这方面还需要更深入的研究。
5.RabbitMQ的其他特点
5.1自动重连
1
|
factory.setAutomaticRecoveryEnabled(
true
);
|
//RabbitMQ自动连接的方法,当网络断开了,该方法每隔一段时间就会尝试恢复连接。
但是我当我使用这个方法,断开连接进行测试的时候,我发现Connection确实重新连接上了,但是Channel数、Queue数和Consumer数量却都是为0。
后来在demo中就放弃使用这个方法转而使用一个很讨巧的方式,双重带标记位的死循环,当Connection断开,异常被catch之后,我让线程睡了五秒中,然后重新进入循环中,直到网络连接成功,或是离开界面。
5.2RabbitMQ—远程过程调用RPC
RPC系统简单的来说就是我们的客户端需要调用远程服务器上的函数方法,拿到结果的模式。使用RabbitMQ构建一个简单的RPC系统:
一般来说在RabbitMQ上做RPC是容易的。一个客户端发送一个请求消息,一个服务器返回响应消息。为了接受到响应,我们需要再请求中带上一个callback队列的地址。
1
2
3
4
5
6
7
8
|
callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props =
new
BasicProperties
.Builder()
.replyTo(callbackQueueName)
.build();
channel.basicPublish(
""
,
"rpc_queue"
, props, message.getBytes());
|
上文其实已经提到过BasicProperties,其种预设了14个消息的属性,较为常用的就是:
deliveryMode :标志着消息持久化(价值 2 ) 或瞬态(任何其他值)。
contentType :用于描述编码的mime类型。
replyTo :常用callback队列名称。
correlationId :有用的RPC响应与请求关联起来,对RPC加速响应请求是很有用的。
上图就是RabbitMQ的RPC的工作流程
当客户端启动,它会创建一个匿名的独占的回收队列。 对于一个RPC请求,客户端会发送一个消息中有两个属性:replyTo,要发送的的回收队列和correlationId,对于每一个请求都是唯一值。 这请求发送到rpc_queue队列中。 这RPC工作者(亦称:服务器)等候队列中的请求。当请求出现,它处理这工作并发送携带结果的信息到客户端,使用的队列是消息属性replTo中的那个。 客户端等待回收队列中的数据。当一个消息出现,它会检查correlationId属性。如果它符合请求中的值,它会返回这响应给应用程序。
具体的代码如下:
服务端核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//创建消费者
channel.basicQos(
1
);
QueueingConsumer consumer =
new
QueueingConsumer(channel);
channel.basicConsume(RPC_QUEUE_NAME,
false
, consumer);
//循环从去队列中获取消息
while
(
true
) {
QueueingConsumer.Delivery delivery =consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
//获取请求中的correlationId属性,构造BasicProperties于请求相对应。
BasicProperties replyProps =
new
BasicProperties
.Builder()
.correlationId(props.getCorrelationId())
.build();
String message =
new
String(delivery.getBody());
int
n = Integer.parseInt(message);
String response =
""
+ fib(n);
//处理请求,获得返回的数据
channel.basicPublish(
""
, props.getReplyTo(), replyProps, response.getBytes());
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),
false
);
}
|
客户端核心代码:
1
2
3
4
|
//声明回调队列和Consumer用于接受服务端返回的Message
replyQueueName = channel.queueDeclare().getQueue();
consumer =
new
QueueingConsumer(channel);
channel.basicConsume(replyQueueName,
true
, consumer);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//创建一个方法用来发送消息
public
String call(String message)
throws
Exception {
String response =
null
;
//得到唯一的UUID作为correlationId
String corrId = java.util.UUID.randomUUID().toString();
BasicProperties props =
new
BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
//回调的队列
.build();
//发送消息
channel.basicPublish(
""
, requestQueueName, props, message.getBytes());
//进入死循环,从replyQueue中获取消息
while
(
true
) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
//判断得到的Message中的correntelationId和发出的Id是否相同
if
(delivery.getProperties().getCorrelationId().equals(corrId)) {
//如果相同得到Message,返回
response =
new
String(delivery.getBody());
break
;
}
}
return
response;
}
|
5.3优先级
5.3.1消费者的优先级:
消费者的优先级可以确保在消费者是活跃的情况下,高优先级的消费者接收消息,而低优先级的消费者只会当高优先级的消费者阻塞的时候接收到消息。
1
2
3
4
5
|
Channel channel = ...;
Consumer consumer = ...;
Map
new
HashMap
args.put(
"x-priority"
,
10
);
channel.basicConsume(
"my-queue"
,
false
, args, consumer);
|
5.3.2队列的优先级
1
2
3
4
|
Channel ch = ...;
Map
new
HashMap
args.put(
"x-max-priority"
,
10
);
ch.queueDeclare(
"my-priority-queue"
,
true
,
false
,
false
, args);
|
5.4TTL(Time to Live)
TTL指定就是消息在队列中的最大的驻留时间,超过该时间此消息就会被销毁。
1
2
3
|
Map
new
HashMap
args.put(
"x-message-ttl"
,
60000
);
channel.queueDeclare(
"myqueue"
,
false
,
false
,
false
, args);
|
上文是直接在创建队列的时候设置,也可以为每一条发送的消息设置TTL。
1
2
3
4
|
byte
[] messageBodyBytes =
"Hello, world!"
.getBytes();
AMQP.BasicProperties properties =
new
AMQP.BasicProperties();
properties.setExpiration(
"60000"
);
channel.basicPublish(
"my-exchange"
,
"routing-key"
, properties, messageBodyBytes);
|
其实本文所提到的只是一些关于RabbitMQ的基本概念及其使用。在服务端还有关于RabbitMQ集群,分布式等概念,在客户端还有事务等概念,正真想十分熟练且到位的使用RabbitMQ需要学习的还需要很多。
参考文献:
http://www.rabbitmq.com/documentation.html
http://blog.csdn.net/lmj623565791/article/details/37657225
http://blog.csdn.net/liyantianmin/article/details/46696029
http://www.jianshu.com/p/4112d78a8753