RabbitMQ学习(二):Java使用RabbitMQ要点知识

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/LeiXiaoTao_Java/article/details/78924863

1、maven依赖

<dependency>
    	<groupId>commons-lang</groupId>
    	<artifactId>commons-lang</artifactId>
    	<version>2.3</version>
    </dependency>
    
    <dependency>
    	<groupId>com.rabbitmq</groupId>
    	<artifactId>amqp-client</artifactId>
    	<version>3.4.1</version>
    </dependency>


2、RabbitMQ重要方法介绍(基本常用的)

2.1、创建连接

// 创建连接工厂
ConnectionFactory cf = new ConnectionFactory();
// 设置rabbitmq服务器IP地址
cf.setHost("*.*.*.*");
// 设置rabbitmq服务器用户名
cf.setUsername("***");
// 设置rabbitmq服务器密码
cf.setPassword("***");
// 指定端口,默认5672
cf.setPort(AMQP.PROTOCOL.PORT);
// 获取一个新的连接
connection = cf.newConnection();
// 创建一个通道
channel = connection.createChannel();
 
//关闭管道和连接
channel.close();
connection.close();

2.2、声明队列

/**
* 申明一个队列,如果这个队列不存在,将会被创建
* @param queue 队列名称
* @param durable 持久性:true队列会再重启过后存在,但是其中的消息不会存在。
* @param exclusive 是否只能由创建者使用,其他连接不能使用。
* @param autoDelete 是否自动删除(没有连接自动删除)
* @param arguments 队列的其他属性(构造参数)
* @return Queue.DeclareOk:宣告队列的声明确认方法已成功声明。
* @throws java.io.IOException if an error is encountered
*/
channel.queueDeclare("testQueue", true, false, false, null);


此方法一般由Producer调用创建消息队列。如果由Consumer创建队列,有可能Producer发布消息的时候Queue还没有被创建好,会造成消息丢失的情况。

 

2.3、声明Exchange

/**
* 声明一个 exchange.
* @param exchange 名称
* @param type  exchange type:direct、fanout、topic、headers
* @param durable 持久化
* @param autoDelete 是否自动删除(没有连接自动删除)
* @param arguments 队列的其他属性(构造参数)
* @return 成功地声明了一个声明确认方法来指示交换。
* @throws java.io.IOException if an error is encountered
*/
channel.exchangeDeclare("leitao","topic", true,false,null);

2.4、将queue和Exchange进行绑定(Binding)

/**
* 将队列绑定到Exchange,不需要额外的参数。
* @param queue 队列名称
* @param exchange 交换机名称
* @param routingKey 路由关键字
* @return Queue.BindOk:如果成功创建绑定,则返回绑定确认方法。
* @throws java.io.IOException if an error is encountered
*/
channel.queueBind("testQueue", "leitao", "testRoutingKey");

2.5、发布消息

/**
* 发布一条不用持久化的消息,且设置两个监听。
* @param exchange 消息交换机名称,空字符串将使用直接交换器模式,发送到默认的Exchange=amq.direct。此状态下,RoutingKey默认和Queue名称相同
* @param routingKey 路由关键字
* @param mandatory 监听是否有符合的队列
* @param immediate 监听符合的队列上是有至少一个Consumer
* @param BasicProperties  设置消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN是持久化;MessageProperties.TEXT_PLAIN是非持久化。
* @param body 消息对象转换的byte[]
* @throws java.io.IOException if an error is encountered
*/
channel.basicPublish("",queueName,true,false,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));

exchange的值为空字符串或者是amq.direct时,此时的交换器类型默认是direct类型可以不用单独声明Exchange,也不用单独进行Binding,系统默认将queue名称作为RoutingKey进行了绑定。

 

两个传入参数的含义

mandatory

当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返回给生产者(Basic.Return + Content-Header + Content-Body);当mandatory设置为false时,出现上述情形broker会直接将消息扔掉。

immediate

当immediate标志位设置为true时,如果exchange在将消息路由到queue(s)时发现对于的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或者多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。

概括来说,mandatory标志告诉服务器至少将该消息route到一个队列中,否则将消息返还给生产者;immediate标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。

注意:在RabbitMQ3.0以后的版本里,去掉了immediate参数的支持,发送带immediate=true标记的publish会返回如下错误:

com.rabbitmq.client.AlreadyClosedException: connection is already closed due to connection error;protocol method: #method<connection.close>(reply-code=540, reply-text=NOT_IMPLEMENTED - immediate=true, class-id=60, method-id=40)。

为什么取消支持:immediate标记会影响镜像队列性能,增加代码复杂性,并建议采用“TTL”和“DLX”等方式替代。

 

2.6、接收消息

/**
* 设置消费批量投递数目,一次性投递10条消息。当消费者未确认消息累计达到10条时,rabbitMQ将不会向此Channel上的消费者投递消息,直到未确认数小于10条再投递
* @param prefetchCount 投递数目
* @param global 是否针对整个Channel。true表示此投递数是给Channel设置的,false是给Channel上的Consumer设置的。
* @throws java.io.IOException if an error is encountered
*/
channel.basicQos(10,false);
//整个传输管道最多15条,具体分到每个消费者身上又不能大于10条
channel.basicQos(15,true);
 
/**
* 开始一个非局部、非排他性消费, with a server-generated consumerTag.
* 执行这个方法会回调handleConsumeOk方法
* @param queue 队列名称
* @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
* @param callback 回调方法类,一般为自己的Consumer类
* @return 由服务器生成的consumertag
* @throws java.io.IOException if an error is encountered
*/
channel.basicConsume(queueName, false, Consumer);

2.7、Consumer处理消息

/**
* 消费者收到消息的回调函数
* @param consumerTag 消费者标签
* @param envelope 消息的包装数据
* @param properties 消息的内容头数据
* @param body 消息对象的byte[]
* @throws IOException
*/
    void handleDelivery(String consumerTag,
                        Envelope envelope,
                        AMQP.BasicProperties properties,
                        byte[] body)
        throws IOException;

3、Producer消息确认机制

3.1、什么是生产者消息确认机制?

没有消息确认模式时,生产者不知道消息是不是已经到达了Broker服务器,这对于一些业务严谨的系统来说将是灾难性的。消息确认模式可以采用AMQP协议层面提供的事务机制实现(此文没有这种实现方式),但是会降低RabbitMQ的吞吐量。RabbitMQ自身提供了一种更加高效的实现方式:confirm模式。

消息生产者通过调用Channel.confirmSelect()方法将Channel信道设置成confirm模式。一旦信道被设置成confirm模式,该信道上的所有消息都会被指派一个唯一的ID(从1开始),一旦消息被对应的Exchange接收,Broker就会发送一个确认给生产者(其中deliveryTag就是此唯一的ID),这样消息生产者就知道消息已经成功到达Broker。

confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。

在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。

3.2、开启confirm模式

如上所说生产者通过调用Channel.confirmSelect()方法将Channel信道设置成confirm模式。

注意:已经在transaction事务模式的channel是不能再设置成confirm模式的,即这两种模式是不能共存的

3.3、普通confirm模式

普通confirm模式是串行的,即每次发送了一次消息,生产者都要等待Broker的确认消息,然后根据确认标记权衡消息重发还是继续发下一条。由于是串行的,在效率上是比较低下的。

 

(1)重点方法

/**
* 等待Broker返回消息确认标记
* 注意,在非确定的通道,waitforconfirms抛出IllegalStateException。
* @return 是否发送成功
* @throws java.lang.IllegalStateException
*/
boolean waitForConfirms() throws InterruptedException;

(2部分使用代码如下:

//注意:返回的时候Return在前,Confirm在后
channel.confirmSelect();
int i=1;
while (i<=50) {
//发布消息
	channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));
//等待Broker的确认回调
	if(channel.waitForConfirms())
		System.out.println("send success!");
	else
		System.out.println("send error!");
	i++;
}

3.4、批量confirm模式

批量confirm模式是异步的方式,效率要比普通confirm模式高许多,但是此种方式也会造成线程阻塞,想要进行失败重发就必须要捕获异常。网络上还有采用waitForConfirms()实现批量confirm模式的,但是只要一条失败了,就必须把这批次的消息统统再重发一次,非常的消耗性能,因此此文不予考虑。

 

(1)重点代码

/**
* 等待直到所有消息被确认或者某个消息发送失败。如果消息发送确认失败了,
* waitForConfirmsOrDie 会抛出IOException异常。当在非确认通道上调用时
* ,会抛出IllegalStateException异常。
* @throws java.lang.IllegalStateException
*/
void waitForConfirmsOrDie() throws IOException, InterruptedException;

(2)部分代码如下:

//注意:返回的时候Return在前,Confirm在后
channel.confirmSelect();
int i=1;
while (i<=50) {
//发布消息
	channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.serialize(object));
	i++;
}
channel.waitForConfirmsOrDie();

3.5、ConfirmListener监听器模式

RabbitMQ提供了一个ConfirmListener接口专门用来进行确认监听,我们可以实现ConfirmListener接口来创建自己的消息确认监听。ConfirmListener接口中包含两个回调方法:

/**
* 生产者发送消息到exchange成功的回调方法
*/
void handleAck(long deliveryTag, boolean multiple) throws IOException;
/**
* 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
* 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。
*/
void handleNack(long deliveryTag, boolean multiple) throws IOException;

其中deliveryTagBroker给每条消息指定的唯一ID(从1开始);multiple表示是否接收所有的应答消息,比如multiple=true时,发送100条消息成功过后,我们并不会收到100次handleAck方法调用。

 

(1)重要方法

//注册消息确认监听器
channel.addConfirmListener(new MyConfirmListener());

(2)部分使用代码如下:

//注意:返回的时候Return在前,Confirm在后
channel.confirmSelect();
//注册消息确认监听器
channel.addConfirmListener(new MyConfirmListener());
//注册消息结果返回监听器
channel.addReturnListener(new MyReturnListener());
int i=1;
while (i<=50) {
    //发布消息
    channel.basicPublish("",queueName,true,MessageProperties.TEXT_PLAIN,SerializationUtils.
    serialize(object));
    i++;
}

 

//自定义的消息确认监听器
public class MyConfirmListener implements ConfirmListener{
	/**
	 * 生产者发送消息到exchange成功的回调方法
	 * 消息被Exchange接受以后,如果没有匹配的Queue,则会被丢弃。但是可以设置ReturnListener监听来监听有没有匹配的队列。
	 * 因此handleAck执行了,并不能完全表示消息已经进入了对应的队列,只能表示对应的exchange成功的接收了消息。
	 * 消息被exchange接收过后,还需要通过一定的匹配规则分发到对应的队列queue中。
	 */
	public void handleAck(long deliveryTag, boolean multiple) throws IOException {
		//注意:deliveryTag是broker给消息指定的唯一id(从1开始)
		System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)成功!multiple="+multiple);
	}
	/**
	 * 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
	 * 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。(不明白,既然丢失了,为啥还能发送)
	 */
	public void handleNack(long deliveryTag, boolean multiple) throws IOException {
		System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)失败!服务器broker丢失了消息");
	}
}


//自定义的结果返回监听器
/**
 * 实现此接口以通知交付basicpublish失败时,“mandatory”或“immediate”的标志监听(源代码注释翻译)。
 * 在发布消息时设置mandatory等于true,监听消息是否有相匹配的队列,
 * 没有时ReturnListener将执行handleReturn方法,消息将返给发送者 
 */
public class MyReturnListener implements ReturnListener {
	public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
			BasicProperties properties, byte[] body) throws IOException {
		System.out.println("消息发送到队列失败:回复失败编码:"+replyCode+";回复失败文本:"+replyText+";失败消息对象:"+SerializationUtils.deserialize(body));
	}
}


4、Consumer消息确认机制

为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message acknowledgment)。消费者在注册消费者时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(或磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。

当noAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息(web管理界面上的Ready状态);一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息(web管理界面上的Unacked状态)。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。

 

(1)重要方法

/**
*1. 开始一个非局部、非排他性消费, with a server-generated consumerTag.
* 注意:执行这个方法会回调handleConsumeOk方法,在此方法中处理消息。
* @param queue 队列名称
* @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
* @param callback 回调方法类
* @return 由服务器生成的consumertag
* @throws java.io.IOException if an error is encountered
*/
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
 
 
/**
*2
consumer处理成功后,通知broker删除队列中的消息,如果设置multiple=true,表示支持批量确认机制以减少网络流量。
   例如:有值为5,6,7,8 deliveryTag的投递
   如果此时channel.basicAck(8, true);则表示前面未确认的5,6,7投递也一起确认处理完毕。
   如果此时channel.basicAck(8, false);则仅表示deliveryTag=8的消息已经成功处理。
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
 
/**3
consumer处理失败后,例如:有值为5,6,7,8 deliveryTag的投递。
   如果channel.basicNack(8, true, true);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息重新放回队列中。
   如果channel.basicNack(8, true, false);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息直接丢弃。
   如果channel.basicNack(8, false, true);表示deliveryTag=8的消息处理失败且将该消息重新放回队列。
   如果channel.basicNack(8, false, false);表示deliveryTag=8的消息处理失败且将该消息直接丢弃。
*/
void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
 
/**4
相比channel.basicNack,除了没有multiple批量确认机制之外,其他语义完全一样。
   如果channel.basicReject(8, true);表示deliveryTag=8的消息处理失败且将该消息重新放回队列。
   如果channel.basicReject(8, false);表示deliveryTag=8的消息处理失败且将该消息直接丢弃。
*/
void basicReject(long deliveryTag, boolean requeue) throws IOException;


(2)部分使用代码如下:

//this表示自己的Consumer
channel.basicConsume(queueName, false, this);
...
@Override
public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
		if (body == null)
			return;
		Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
/**
 		* 专门处理奇数消息的消费者
 */
		int tagId = (Integer) map.get("tagId");
		if (tagId % 2 != 0) {
			//处理消息
			System.out.println("接收并处理消息:"+tagId);
			//通知服务器此消息已经被处理了
			channel.basicAck(envelope.getDeliveryTag(), false);
		}else{
			//通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
			channel.basicReject(envelope.getDeliveryTag(), true);
		}
	}


5、Demo项目整体代码

此demo就是向RabbitMQ服务器上面发送20个消息,消息体是map,里面装的是tagId=数字。然后注册了两个消费者,分别处理奇数和偶数。

5.1、连接工具类

/**
 * 连接工具类
 */
public class ConnectionUtil {

	Channel channel;
	Connection connection;
	String queueName;

	public ConnectionUtil(String queueName) throws IOException {
		this.queueName = queueName;
		// 创建连接工厂
		ConnectionFactory cf = new ConnectionFactory();
		// 设置rabbitmq服务器IP地址
		cf.setHost("*.16.0.*");
		// 设置rabbitmq服务器用户名
		cf.setUsername("*");
		// 设置rabbitmq服务器密码
		cf.setPassword("*");
		cf.setPort(AMQP.PROTOCOL.PORT);
		// 获取一个新的连接
		connection = cf.newConnection();
		// 创建一个通道
		channel = connection.createChannel();
		/**
	     *申明一个队列,如果这个队列不存在,将会被创建
	     * @param queue 队列名称
	     * @param durable 持久性:true队列会再重启过后存在,但是其中的消息不会存在。
	     * @param exclusive 是否只能由创建者使用
	     * @param autoDelete 是否自动删除(没有连接自动删除)
	     * @param arguments 队列的其他属性(构造参数) 
	     * @return 宣告队列的声明确认方法已成功声明。
	     * @throws java.io.IOException if an error is encountered
	     */
		channel.queueDeclare(queueName, true, false, false, null);
	}
	
	public void close() throws IOException{
		channel.close();
		connection.close();
	}
}

 

5.2、具体生产者

/**
 * 消息生产者
 */
public class MessageProducer {
	
	private ConnectionUtil connectionUtil;
	
	public MessageProducer(ConnectionUtil connectionUtil){
		this.connectionUtil=connectionUtil;
	}
	/**
	 * 发送消息到队列中
	 */
	public void sendMessage(Serializable object) throws IOException{
		/**
	     * Publish a message
	     * @param exchange 消息交换机名称,空字符串将使用直接交换器模式,发送到默认的Exchange=amq.direct
	     * @param routingKey 路由关键字
	     * @param mandatory 监听是否有符合的队列
	     * @param BasicProperties 设置消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN是持久化;MessageProperties.TEXT_PLAIN是非持久化
	     * @param body 消息对象
	     * @throws java.io.IOException if an error is encountered
	     */
		connectionUtil.channel.basicPublish("", connectionUtil.queueName, true, MessageProperties.TEXT_PLAIN, SerializationUtils.serialize(object));
		System.out.println("MessageProducer发送了一条消息:"+object);
	}
}

5.3、公共消费者父类

/**
 * 消息消费者基础类
 */
public class MessageConsumer implements Consumer {
	//消费者标签,注册成功时由rabbitmq服务器自动生成
	protected String consumerTag;
	
	protected ConnectionUtil connectionUtil;
	
	public MessageConsumer(ConnectionUtil connectionUtil){
		this.connectionUtil=connectionUtil;
	}
	
	public void basicConsume(){
		try {
			/**
		     * 设置消费投递数目,一次性投递10条消息。当消费者未确认消息达到10条时,rabbitMQ将不会向此消费者投递消息,直到未确认数小于10条再投递
		     * @param prefetchCount 投递数目
		     * @param global 是否针对整个Channel。true表示此投递数是给Channel设置的,false是给Channel上的Consumer设置的。
		     * @throws java.io.IOException if an error is encountered
		     */
			connectionUtil.channel.basicQos(10,false);//表示每个消费者最多10条
			connectionUtil.channel.basicQos(15,true);//整个传输管道最多15条,具体分到每个消费者身上又不能大于10条
			/**
		     * 开始一个非局部、非排他性消费, with a server-generated consumerTag.
		     * 执行这个方法会回调handleConsumeOk方法
		     * @param queue 队列名称
		     * @param autoAck 是否自动应答。false表示consumer在成功消费过后必须要手动回复一下服务器,如果不回复,服务器就将认为此条消息消费失败,继续分发给其他consumer。
		     * @param callback 回调方法类
		     * @return 由服务器生成的consumertag
		     * @throws java.io.IOException if an error is encountered
		     */
			connectionUtil.channel.basicConsume(connectionUtil.queueName, false, this);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 收到消息时的回调函数
	 */
	public void handleDelivery(String arg0, Envelope arg1, BasicProperties arg2, byte[] arg3) throws IOException {
		//子类重写覆盖具体操作
	}

	/**
	 * 消费者注册成功回调函数
	 */
	public void handleConsumeOk(String consumerTag) {
		this.consumerTag=consumerTag;
		System.out.println("消费者:"+consumerTag+",注册成功!");
	}

	/**
	 * 手动取消消费者注册成功回调函数
	 * 当调用Channel类的void basicCancel(String consumerTag) throws IOException;方法触发此回调函数
	 */
	public void handleCancelOk(String consumerTag) {
		System.out.println(consumerTag+" 手动取消消费者注册成功!");
	}

	/**
	 * 当消费者因为其他原因被动取消注册时调用,比如queue被删除了。
	 */
	public void handleCancel(String consumerTag) throws IOException {
		System.out.println("因为外部原因消费者:"+consumerTag+" 取消注册!");
	}

	/**
	 * 当通道或基础连接被关闭时调用
	 */
	public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
		System.out.println("通道或基础连接被关闭");
	}

	/**
     * Called when a <code><b>basic.recover-ok</b></code> is received
     * in reply to a <code><b>basic.recover</b></code>. All messages
     * received before this is invoked that haven't been <i>ack</i>'ed will be
     * re-delivered. All messages received afterwards won't be.
     * @param consumerTag the <i>consumer tag</i> associated with the consumer
     */
	public void handleRecoverOk(String consumerTag) {
		
	}
}

 

5.4、具体的消费者

/**
 * 专门处理偶数消息的消费者
 */
public class EvenConsumer extends MessageConsumer {

	public EvenConsumer(ConnectionUtil connectionUtil) {
		super(connectionUtil);
	}
	
	@Override
	public void handleConsumeOk(String consumerTag) {
		this.consumerTag=consumerTag;
		System.out.println("EvenConsumer消费者:"+consumerTag+",注册成功!");
	}

	@Override
	public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
		if (body == null)
			return;
		Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
		int tagId = (Integer) map.get("tagId");
		if (tagId % 2 == 0) {
			//处理消息
			System.out.println("EvenConsumer接收并处理消息:"+tagId);
			//通知服务器此消息已经被处理了
			connectionUtil.channel.basicAck(envelope.getDeliveryTag(), false);
		}else{
			//通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
			connectionUtil.channel.basicReject(envelope.getDeliveryTag(), true);
		}
	}
}

 

/**
 * 专门处理奇数消息的消费者
 */
public class OddConsumer extends MessageConsumer {

	public OddConsumer(ConnectionUtil connectionUtil) {
		super(connectionUtil);
	}

	@Override
	public void handleConsumeOk(String consumerTag) {
		this.consumerTag=consumerTag;
		System.out.println("OddConsumer消费者:"+consumerTag+",注册成功!");
	}
	
	@Override
	public void handleDelivery(String arg0, Envelope envelope, BasicProperties arg2, byte[] body) throws IOException {
		if (body == null)
			return;
		Map<String, Object> map = (Map<String, Object>) SerializationUtils.deserialize(body);
		int tagId = (Integer) map.get("tagId");
		if (tagId % 2 != 0) {
			//处理消息
			System.out.println("OddConsumer接收并处理消息:"+tagId);
			//通知服务器此消息已经被处理了
			connectionUtil.channel.basicAck(envelope.getDeliveryTag(), false);
		}else{
			//通知服务器消息处理失败,重新放回队列。false表示处理失败消息不放会队列,直接删除
			connectionUtil.channel.basicReject(envelope.getDeliveryTag(), true);
		}
	}
}


5.5、监听器

/**
 *producer发送确认事件。
 */
public class MyConfirmListener implements ConfirmListener{
	/**
	 * 生产者发送消息到exchange成功的回调方法
	 * 消息被Exchange接受以后,如果没有匹配的Queue,则会被丢弃。但是可以设置ReturnListener监听来监听有没有匹配的队列。
	 * 因此handleAck执行了,并不能完全表示消息已经进入了对应的队列,只能表示对应的exchange成功的接收了消息。
	 * 消息被exchange接收过后,还需要通过一定的匹配规则分发到对应的队列queue中。
	 */
	public void handleAck(long deliveryTag, boolean multiple) throws IOException {
		//注意:deliveryTag是broker给消息指定的唯一id(从1开始)
		System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)成功!multiple="+multiple);
	}
	/**
	 * 生产者发送消息到服务器broker失败的回调方法,服务器丢失了此消息。
	 * 注意,丢失的消息仍然可以传递给消费者,但broker不能保证这一点。
	 */
	public void handleNack(long deliveryTag, boolean multiple) throws IOException {
		System.out.println("Exchange接收消息:"+deliveryTag+"(deliveryTag)失败!服务器broker丢失了消息");
	}
}


/**
 * 实现此接口以通知交付basicpublish失败时,“mandatory”或“immediate”的标志监听(源代码注释翻译)。
 * 在发布消息时设置mandatory等于true,监听消息是否有相匹配的队列,
 * 没有时ReturnListener将执行handleReturn方法,消息将返给发送者 。
 * 由于3.0版本过后取消了支持immediate,此处不做过多的解释。
 */
public class MyReturnListener implements ReturnListener {

	public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
			BasicProperties properties, byte[] body) throws IOException {
		System.out.println("消息发送到队列失败:回复失败编码:"+replyCode+";回复失败文本:"+replyText+";失败消息对象:"+SerializationUtils.deserialize(body));
	}
}


5.6、客户端

public class Client {
	
	public static void main(String[] args) {
		new Client();
	}
	
	public Client(){
		try {
			//发消息
			publishMessage();
			//注册消费者
			addConsumer();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void publishMessage() throws IOException, InterruptedException{
		ConnectionUtil connectionUtil=new ConnectionUtil("testqueue");
		MessageProducer producer=new MessageProducer(connectionUtil);
		connectionUtil.channel.confirmSelect();
		//注意:返回的时候Return在前,Confirm在后
		connectionUtil.channel.addConfirmListener(new MyConfirmListener());
		connectionUtil.channel.addReturnListener(new MyReturnListener());
		int i=1;
		while (i<=10) {
			HashMap<String, Object> map=new HashMap<String, Object>();
			map.put("tagId", i);
			producer.sendMessage(map);
			i++;
		}
	}
	
	public void addConsumer() throws IOException{
		ConnectionUtil connectionUtil=new ConnectionUtil("testqueue");
		OddConsumer odd=new OddConsumer(connectionUtil);
		odd.basicConsume();
		EvenConsumer even=new EvenConsumer(connectionUtil);
		even.basicConsume();
	}

}

 

5.7、测试结果

MessageProducer发送了一条消息:{tagId=1}
MessageProducer发送了一条消息:{tagId=2}
MessageProducer发送了一条消息:{tagId=3}
Exchange接收消息:1(deliveryTag)成功!multiple=false
Exchange接收消息:2(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=4}
Exchange接收消息:3(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=5}
Exchange接收消息:4(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=6}
Exchange接收消息:5(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=7}
Exchange接收消息:6(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=8}
Exchange接收消息:7(deliveryTag)成功!multiple=false
Exchange接收消息:8(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=9}
Exchange接收消息:9(deliveryTag)成功!multiple=false
MessageProducer发送了一条消息:{tagId=10}
Exchange接收消息:10(deliveryTag)成功!multiple=false
OddConsumer消费者:amq.ctag-z8s8LaSgYvo02jktCZrCYA,注册成功!
OddConsumer接收并处理消息:1
OddConsumer接收并处理消息:3
OddConsumer接收并处理消息:5
OddConsumer接收并处理消息:7
OddConsumer接收并处理消息:9
EvenConsumer消费者:amq.ctag-LpN6Q5VvNY3wCof2lXqS4A,注册成功!
EvenConsumer接收并处理消息:4
EvenConsumer接收并处理消息:8
EvenConsumer接收并处理消息:2
EvenConsumer接收并处理消息:10
EvenConsumer接收并处理消息:6

6、Demo完整源码下载地址

Java使用RabbitMQ完整项目源码.rar

 

 

上一篇文章:《RabbitMQ学习(一):RabbitMQ要点简介

下一篇文章:《RabbitMQ学习(三):Spring整合RabbitMQ


【四川乐山程序员联盟,欢迎大家加群相互交流学习5 7 1 8 1 4 7 4 3】


没有更多推荐了,返回首页