消息中间件系列三:使用RabbitMq原生Java客户端进行消息通信(消费者(接收方)自动确认模式、消费者(接收方)自行确认模式、生产者(发送方)确认模式)
准备工作:
1)安装RabbitMQ,参考文章:消息中间件系列二:RabbitMQ入门(基本概念、RabbitMQ的安装和运行)
2.)分别新建名为OriginalRabbitMQProducer和OriginalRabbitMQConsumer的maven工程
在pom.xml文件里面引入如下依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>
说明:5系列的版本最好使用JDK8及以上, 低于JDK8可以使用4.x(具体的版本号到Maven的中央仓库查)的版本
一、消费者(接收方)自动确认模式
前面有谈到消费者收到的每一条消息都必须进行确认,消息的确认机制分为自动确认和消费者自行确认。下面我们来看一下自动确认的示例:
示例1:交换器是direct
1. 在工程OriginalRabbitMQProducer新建一个一个direct的生产者
package study.demo.normal; import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是direct的生产者 路由键完全匹配时,消息才投放到对应队列
* @author leeSmall
* @date 2018年9月15日
*
*/
public class DirectProducer { //定义交换器的名字
private final static String EXCHANGE_NAME = "direct_logs"; public static void main(String[] args) throws IOException, TimeoutException { //1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory(); //设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //设置用户名 这里使用缺省的
//factory.setUsername(..); //设置连接断开 这里使用缺省的
//factory.setPort(); //设置虚拟主机 这里使用缺省的
//factory.setVirtualHost(); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //定义一组路由键
String[]routingKeys = {"error","info","warning"}; //发布消息的交换器上
for(int i=0;i<3;i++){
//路由键
String routingKey = routingKeys[i]; //要发送的消息
String message = "Hello world_"+(i+1); /**
* 发送消息到交换器上
* 参数1:交换器的名字
* 参数2:路由键
* 参数3:BasicProperties
* 参数4:要发送的消息
*/
channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
System.out.println("Sent "+routingKey+":"+message); } channel.close();
connection.close(); } }
2. 在工程OriginalRabbitMQConsumer新建一个direct的只消费error日志的消费者
package study.demo.normal; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是direct 只消费error日志的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ConsumerError { //定义交换器的名字
private static final String EXCHANGE_NAME = "direct_logs";
// private static final String EXCHANGE_NAME = "fanout_logs_1"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明随机队列
String queueName = channel.queueDeclare().getQueue(); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey); System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
}
};
//9.自动确认:autoAck参数为true
channel.basicConsume(queueName,true,consumerB);
} }
3. 启动消费者ConsumerError,再启动生产者DirectProducer,查看效果
启动消费者ConsumerError:
启动生产者DirectProducer:
查看消费者ConsumerError现在的状况:
可以看到消费者只消费了error级别的消息,这是因为在direct模式下,消费者只定义了路由键routingKey = "error";
4. 在工程OriginalRabbitMQConsumer新建一个direct的消费所有日志的消费者
package study.demo.normal; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是direct 消费所有日志的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ConsumerAll {
//定义交换器的名字
private static final String EXCHANGE_NAME = "direct_logs";
// private static final String EXCHANGE_NAME = "fanout_logs_1"; public static void main(String[] argv) throws IOException,
InterruptedException, TimeoutException { //1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明随机队列
String queueName = channel.queueDeclare().getQueue(); //6.定义一组路由键消费所有日志
String[]routingKeys = {"error","info","warning"}; //7.队列通过路由键绑定到交换器上
for(String routingKey:routingKeys){
//队列和交换器的绑定
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
}
System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
}
};
//9.自动确认:autoAck参数为true
channel.basicConsume(queueName,true,consumerA); } }
5. 启动消费者ConsumerAll,再启动生产者DirectProducer,查看效果
启动消费者ConsumerAll:
启动生产者DirectProducer:
查看消费者ConsumerAll的状态:
可以看到消费者ConsumerAll消费了生产者DirectProducer产生的所有消息,这是因为在direct模式下,消费者定义了和生产者一样个数的路由键String[]routingKeys = {"error","info","warning"};
示例2:交换器是fanout
1. 在工程OriginalRabbitMQProducer新建一个交换器是fanout的生产者
package study.demo.normal; import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是fanout的生产者 可以理解为广播,会把所有消息投放到绑定到这个交换器上的队列上
* @author leeSmall
* @date 2018年9月15日
*
*/
public class FanoutProducer { private final static String EXCHANGE_NAME = "fanout_logs_1"; public static void main(String[] args) throws IOException, TimeoutException { //1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory(); //设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //设置用户名 这里使用缺省的
//factory.setUsername(..); //设置连接断开 这里使用缺省的
//factory.setPort(); //设置虚拟主机 这里使用缺省的
//factory.setVirtualHost(); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); //定义一组路由键
String[]routingKeys = {"error","info","warning"}; //发布消息的交换器上
for(int i=0;i<3;i++){
//路由键
String routingKey = routingKeys[i]; //要发送的消息
String message = "Hello world_"+(i+1); /**
* 发送消息到交换器上
* 参数1:交换器的名字
* 参数2:路由键
* 参数3:BasicProperties
* 参数4:要发送的消息
*/
channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
System.out.println("Sent "+routingKey+":"+message); } channel.close();
connection.close(); } }
2. 在工程OriginalRabbitMQConsumer新建一个fanout的只消费error日志的消费者
package study.demo.normal; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是fanout,只消费error日志的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ConsumerError { //定义交换器的名字
//private static final String EXCHANGE_NAME = "direct_logs";
private static final String EXCHANGE_NAME = "fanout_logs_1"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); //5.声明随机队列
String queueName = channel.queueDeclare().getQueue(); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey); System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
}
};
//9.自动确认:autoAck参数为true
channel.basicConsume(queueName,true,consumerA); } }
3. 启动消费者ConsumerError,再启动生产者DirectProducer,查看效果
启动消费者ConsumerError:
启动生产者FanoutProducer:
查看消费者ConsumerError现在的状况:
可以看到消费者消费了所有级别的消息,这是因为在fanout模式下,虽然消费者只定义了路由键routingKey = "error",但是因为fanut是广播模式,会把所有消息投放到绑定到这个交换器上的队列上
4. 在工程OriginalRabbitMQConsumer新建一个fanout的消费所有日志的消费者
package study.demo.normal; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 交换器是fanout 消费所有日志的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ConsumerAll {
//定义交换器的名字
//private static final String EXCHANGE_NAME = "direct_logs";
private static final String EXCHANGE_NAME = "fanout_logs_1"; public static void main(String[] argv) throws IOException,
InterruptedException, TimeoutException { //1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); //5.声明随机队列
String queueName = channel.queueDeclare().getQueue(); //6.定义一组路由键消费所有日志
String[]routingKeys = {"error","info","warning"}; //7.队列通过路由键绑定到交换器上
for(String routingKey:routingKeys){
//队列和交换器的绑定
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
}
System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerA = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
}
};
//9.自动确认:autoAck参数为true
channel.basicConsume(queueName,true,consumerA); }
}
5. 启动消费者ConsumerAll,再启动生产者DirectProducer,查看效果
启动消费者ConsumerAll:
启动生产者FanoutProducer:
查看消费者ConsumerAll的状态:
可以看到消费者ConsumerAll消费了生产者DirectProducer产生的所有消息,这是因为在fanout模式下,消费者定义了和生产者一样个数的路由键String[]routingKeys = {"error","info","warning"};
三、消费者(接收方)自行确认模式
1. 在工程OriginalRabbitMQProducer新建一个消费者自行确认生产者
package study.demo.consumerconfirm; import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 消费者自行确认生产者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ConsumerConfirmProducer { //交换器
private final static String EXCHANGE_NAME = "direct_cc_confirm_1"; //路由键
private final static String ROUTE_KEY = "error"; public static void main(String[] args) throws IOException, TimeoutException,
InterruptedException { //1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory(); //设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //设置用户名 这里使用缺省的
//factory.setUsername(..); //设置连接断开 这里使用缺省的
//factory.setPort(); //设置虚拟主机 这里使用缺省的
//factory.setVirtualHost(); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //发布消息的交换器上
for(int i=0;i<10;i++){
//要发送的消息
String message = "Hello world_"+(i+1); /**
* 发送消息到交换器上
* 参数1:交换器的名字
* 参数2:路由键
* 参数3:BasicProperties
* 参数4:要发送的消息
*/
channel.basicPublish(EXCHANGE_NAME,ROUTE_KEY,null,message.getBytes());
System.out.println("Sent "+ROUTE_KEY+":"+message); } channel.close();
connection.close(); } }
2. 在工程OriginalRabbitMQConsumer新建一个消费者自行确认消费者
package study.demo.consumerconfirm; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 消费者自行确认消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ClientConsumerAck { private static final String EXCHANGE_NAME = "direct_cc_confirm_1"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明队列
String queueName = "consumer_confirm";
channel.queueDeclare(queueName,false,false,
false,null); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
//消费者自行确认
this.getChannel().basicAck(envelope.getDeliveryTag(),false);
}
}; //9.消费者自行确认:autoAck参数为false
channel.basicConsume(queueName,false,consumerB);
} }
3. 在工程OriginalRabbitMQConsumer新建一个消费者自行确认休眠不回复ack的消费者
package study.demo.consumerconfirm; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 消费者自行确认休眠不回复ack的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ClientConsumerSlowAck { private static final String EXCHANGE_NAME = "direct_cc_confirm_1"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明队列
String queueName = "consumer_confirm";
channel.queueDeclare(queueName,false,false,
false,null); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
try {
//消费者自行确认时不回复ack,一直休眠
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(body,"UTF-8");
System.out.println("Accept:"+envelope.getRoutingKey()+":"+message);
//this.getChannel().basicAck(envelope.getDeliveryTag(),false);
}
}; //9.消费者自行确认:autoAck参数为false
channel.basicConsume(queueName,false,consumerB);
} }
4. 分别启动消费者自行确认消费者ClientConsumerAck和消费者自行确认休眠不回复ack的消费者ClientConsumerSlowAck,再启动消费者自行确认生产者ConsumerConfirmProducer查看状态
启动消费者自行确认消费者ClientConsumerAck:
启动消费者自行确认休眠不回复ack的消费者ClientConsumerSlowAck
启动消费者自行确认生产者ConsumerConfirmProducer
查看消费者自行确认消费者ClientConsumerAck的状态:
查看消费者自行确认休眠不回复ack的消费者ClientConsumerSlowAck的状态:
查看RabbitMQ服务器上的队列情况:
可以看到队列consumer_confirm里面有5条消息未消费,这是因为消费者自行确认休眠不回复ack的消费者ClientConsumerSlowAck收到了这5条消息,但是没有向RabbitMQ服务器发送确认消息,RabbitMQMQ认为这5条消息还没有被消费就一直存在队列里面
下面停掉ClientConsumerSlowAck,查看ClientConsumerAck和RabbitMQ服务器里面队列consumer_confirm的状态
可以看到停掉ClientConsumerSlowAck以后,之前的5条消息被ClientConsumerAck消费了
5. 在工程OriginalRabbitMQConsumer 新建一个消费者自行确认拒绝消息的消费者
package study.demo.consumerconfirm; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 消费者自行确认拒绝消息的消费者
* @author leeSmall
* @date 2018年9月15日
*
*/
public class ClientConsumerReject { private static final String EXCHANGE_NAME = "direct_cc_confirm_1"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明队列
String queueName = "consumer_confirm";
channel.queueDeclare(queueName,false,false,
false,null); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
System.out.println("Waiting message......."); //8.设置一个监听器监听消费消息
Consumer consumerB = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
//消费者自行拒绝消息 参数requeue=true,让RabbitMQ服务器重新分发消息,requeue=false让RabbitMQ服务器移除消息
this.getChannel().basicReject(envelope.getDeliveryTag(),true);
System.out.println("Reject:"+envelope.getRoutingKey()
+":"+new String(body,"UTF-8"));
}
}; //9.消费者自行确认:autoAck参数为false
channel.basicConsume(queueName,false,consumerB);
} }
6. 分别启动消费者自行确认消费者ClientConsumerAck和消费者自行确认拒绝消息的消费者ClientConsumerReject,再启动消费者自行确认生产者ConsumerConfirmProducer查看状态
启动消费者自行确认消费者ClientConsumerAck:
启动消费者自行确认拒绝消息的消费者ClientConsumerReject:
启动消费者自行确认生产者ConsumerConfirmProducer
查看消费者自行确认消费者ClientConsumerAck的状态:
查看消费者自行确认拒绝消息的消费者ClientConsumerReject的状态:
可以看到消息都被ClientConsumerAck消费了,这是因为消费者ClientConsumerReject拒绝了所有消息,这里要注意
this.getChannel().basicReject(envelope.getDeliveryTag(),true);
这段代码的basicReject的第二个参数requeue,参数requeue=true,让RabbitMQ服务器重新分发消息,requeue=false让RabbitMQ服务器移除消息
requeue=false时,RabbitMQ服务器会删掉被ClientConsumerReject拒绝的消息,消费者ClientConsumerAck就不能消费所有消息了
四、生产者(发送方)确认模式
为什么要有个发送方确认模式?
生产者不知道消息是否真正到达RabbitMq,也就是说发布操作不返回任何消息给生产者。
AMQP协议层面为我们提供的事务机制解决了这个问题,但是事务机制本身也会带来问题:
1)严重的性能问题
2)使生产者应用程序产生同步
RabbitMQ团队为我们拿出了更好的方案,即采用发送方确认模式,该模式比事务更轻量,性能影响几乎可以忽略不计。
1. 在OriginalRabbitMQProducer工程新建一个生产者(发送方)确认同步模式的类
package study.demo.producerconfirm; import java.io.IOException;
import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; /**
*
* @Description: 生产者(发送方)确认同步模式
* @author leeSmall
* @date 2018年9月16日
*
*/
public class ProducerConfirm { private final static String EXCHANGE_NAME = "producer_confirm";
private final static String ROUTE_KEY = "error"; public static void main(String[] args) throws IOException, TimeoutException,
InterruptedException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory(); //设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //设置用户名 这里使用缺省的
//factory.setUsername(..); //设置连接断开 这里使用缺省的
//factory.setPort(); //设置虚拟主机 这里使用缺省的
//factory.setVirtualHost(); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel();
//将信道设置为发送方确认
channel.confirmSelect(); //发布消息的交换器上
for(int i=0;i<2;i++){
String msg = "Hello "+(i+1);
channel.basicPublish(EXCHANGE_NAME,ROUTE_KEY,null,msg.getBytes());
//等待RabbitMQ返回消息确认消息已送达RabbitMQ服务器
if (channel.waitForConfirms()){
System.out.println("发送方同步确认: "+ROUTE_KEY+":"+msg);
}
} // 关闭频道和连接
channel.close();
connection.close();
} }
2. 在OriginalRabbitMQProducer工程新建一个生产者(发送方)确认异步模式的类
package study.demo.producerconfirm; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 生产者(发送方)确认异步模式
* @author leeSmall
* @date 2018年9月16日
*
*/
public class ProducerConfirmAsync { private final static String EXCHANGE_NAME = "producer_confirm"; public static void main(String[] args) throws IOException, TimeoutException,
InterruptedException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory(); //设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //设置用户名 这里使用缺省的
//factory.setUsername(..); //设置连接断开 这里使用缺省的
//factory.setPort(); //设置虚拟主机 这里使用缺省的
//factory.setVirtualHost(); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //将信道设置为发送方确认
channel.confirmSelect(); //信道被关闭监听器 用于RabbitMQ服务器断线重连
//channel.addShutdownListener(); /**
* 生产者异步确认监听
* 参数deliveryTag代表了当前channel唯一的投递
* 参数multiple:false
*
*/
channel.addConfirmListener(new ConfirmListener() {
//RabbitMQ服务器确认收到消息
public void handleAck(long deliveryTag, boolean multiple)
throws IOException {
System.out.println("RabbitMQ服务器确认收到消息Ack deliveryTag="+deliveryTag
+"multiple:"+multiple);
} //RabbitMQ服务器由于自己内部出现故障没有收到消息
public void handleNack(long deliveryTag, boolean multiple)
throws IOException {
System.out.println("RabbitMQ服务没有收到消息Ack deliveryTag="+deliveryTag
+"multiple:"+multiple);
}
}); //生产者异步返回监听 这里和发布消息时的mandatory参数有关
//参数mandatory:mandatory=true,投递消息时无法找到一个合适的队列,把消息返回给生产者,mandatory=false 丢弃消息(缺省)
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body);
System.out.println("replyText:"+replyText);
System.out.println("exchange:"+exchange);
System.out.println("routingKey:"+routingKey);
System.out.println("msg:"+msg);
}
}); //声明一组路由键
String[] routingKeys={"error","info","warning"};
//发送消息到交换器上
for(int i=0;i<3;i++){
String routingKey = routingKeys[i%3];
// 发送的消息
String message = "Hello World_"+(i+1)+("_"+System.currentTimeMillis()); //通过路由键把消息发送到交换器上
//参数mandatory: mandatory=true,投递消息时无法找到一个合适的队列,把消息返回给生产者,
// mandatory=false 丢弃消息(缺省)
channel.basicPublish(EXCHANGE_NAME, routingKey, false,
null, message.getBytes());
System.out.println("----------------------------------------------------");
System.out.println(" Sent Message: [" + routingKey +"]:'"+ message + "'");
//sleep一下让程序不快速结束 可以看到RabbitMQ服务器的响应
Thread.sleep(1000);
} // 关闭信道和连接
channel.close();
connection.close();
} }
3. 在OriginalRabbitMQConsumer工程新建一个发送方确认消费者
package study.demo.producerconfirm; import com.rabbitmq.client.*; import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
*
* @Description: 发送方确认消费者
* @author leeSmall
* @date 2018年9月16日
*
*/
public class ProducerConfirmConsumer { private static final String EXCHANGE_NAME = "producer_confirm"; public static void main(String[] argv) throws IOException, TimeoutException {
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置要连接的RabbitMQ服务器的地址
factory.setHost("127.0.0.1"); //2.通过连接工厂创建一个连接
Connection connection = factory.newConnection(); //3.通过连接创建一个信道 信道是用来传送数据的
Channel channel = connection.createChannel(); //4.通过信道声明一个交换器 第一个参数时交换器的名字 第二个参数时交换器的种类
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //5.声明队列
String queueName = "producer_confirm";
channel.queueDeclare(queueName,false,false,
false,null); //6.声明一个只消费错误日志的路由键error
String routingKey = "error"; //7.队列通过路由键绑定到交换器上
channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
System.out.println("Waiting message......."); // 8.创建队列消费者 设置一个监听器监听消费消息
final Consumer consumerB = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println( "Received ["+ envelope.getRoutingKey() + "] "+message);
}
};
//9.消费者自动确认:autoAck参数为true
channel.basicConsume(queueName, true, consumerB);
} }
4. 启动发送方确认消费者ProducerConfirmConsumer,再分别启动生产者(发送方)确认同步模式的类ProducerConfirm和生产者(发送方)确认异步模式ProducerConfirmAsync
启动发送方确认消费者ProducerConfirmConsumer:
启动生产者(发送方)确认同步模式的类ProducerConfirm:
查看发送方确认消费者ProducerConfirmConsumer的状态:
启动生产者(发送方)确认异步模式ProducerConfirmAsync:
查看发送方确认消费者ProducerConfirmConsumer的状态:
消息中间件系列三:使用RabbitMq原生Java客户端进行消息通信(消费者(接收方)自动确认模式、消费者(接收方)自行确认模式、生产者(发送方)确认模式)的更多相关文章
- 原生 Java 客户端进行消息通信
原生 Java 客户端进行消息通信 Direct 交换器 DirectProducer:direct类型交换器的生产者 NormalConsumer:普通的消费者 MulitBindConsumer: ...
- RabbitMQ(3) Java客户端使用
RabbitMQ针对不同的开发语言(java,python,c/++,Go等等),提供了丰富对客户端,方便使用.就Java而言,可供使用的客户端有RabbitMQ Java client. Rabbi ...
- 消息中间件系列五:RabbitMQ的使用场景(异步处理、应用解耦)
一.异步处理 场景: 用户注册,写入数据库成功以后,发送邮件和短信. 准备工作: 1)安装RabbitMQ,参考前面的文章 2)新建一个名为RabbitMQAsyncProc的maven web工程, ...
- 消息中间件系列二:RabbitMQ入门(基本概念、RabbitMQ的安装和运行)
一.基本概念 1. AMQP AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议.支持不同语言和不同的产品 2. 生产者 ...
- 喵星之旅-狂奔的兔子-rabbitmq的java客户端使用入门
一.简介 RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件). 消息队列都涉及的生产者消费者模型,不做详解,本文只作为快速使用的参考文档. 消息队列主要有点 ...
- Hive学习(三)Hive的Java客户端操作
Hive的Java客户端操作分为JDBC和Thrifit Client,首先启动Hive远程服务: hive --service hiveserver 一.JDBC 在MyEclipse中首先创建连接 ...
- Redis系列(三)-Redis发布订阅及客户端编程
阅读目录 发布订阅模型 Redis中的发布订阅 客户端编程示例 0.3版本Hredis 发布订阅模型 在应用级其作用是为了减少依赖关系,通常也叫观察者模式.主要是把耦合点单独抽离出来作为第三方,隔离易 ...
- RabbitMQ学习笔记2-理解消息通信
消息包含两部分:1.有效载荷(payload) - 你想要传输的数据.2.标签(lable) - 描述有效载荷的相关信息,包含具体的交换器.消息的接受兴趣方等. rabbitmq的基础流程如下: Ra ...
- 消息中间件系列四:RabbitMQ与Spring集成
一.RabbitMQ与Spring集成 准备工作: 分别新建名为RabbitMQSpringProducer和RabbitMQSpringConsumer的maven web工程 在pom.xml文 ...
随机推荐
- js 函数与类的区别
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [CTSC2017]吉夫特
Description: 给定一个序列\(a_1,a_2,a_3...a_n\) 求有多少个不上升子序列: \(a_{b1},a_{b_2}...\) 满足 \(C_{a_{b1}}^{a_{b2}} ...
- 给电脑C盘系统盘瘦身的四种方法
进入后XP时代,又有很多朋友选择了Win 7系统,很多朋友在使用时可能会发现一个问题.电脑刚被买回来时,我们往往都把C盘(系统盘)预留几十个G左右的空间,而且可能装的东西并不是太多,但是磁盘却显示,可 ...
- C++程序设计方法3:虚函数
向上映射与向下映射 派生类对象转换成基类对象,称为向上映射. 而基类对象转换成派生类对象,称为向下映射: 向上映射可由编译器自动完成,是一种隐式的自动类型转化: 凡是接受基类对象的地方(如函数参数)都 ...
- 论文泛读:Click Fraud Detection: Adversarial Pattern Recognition over 5 Years at Microsoft
这篇论文非常适合工业界的人(比如我)去读,有很多的借鉴意义. 强烈建议自己去读. title:五年微软经验的点击欺诈检测 摘要:1.微软很厉害.2.本文描述了大规模数据挖掘所面临的独特挑战.解决这一问 ...
- Linux之通配符实验
作业五:通配符实验 反引号与()在此时都是表死获取结果 但是一般使用()的方式,因为反引号在多个反引号的时候无法正确指代 获取当前bash 的变量 echo $变量名 echo $? 表示上一次命令的 ...
- CSS单位和颜色表
- Spring(2)—IOC
一.Spring IOC是什么 1.简述 Spring是一个开源框架 Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能 Spring是 ...
- 【独家】硅谷创业公司在中国常跌的五个坑|禾赛科技CEO李一帆柏林亚太周主题演讲
[独家]硅谷创业公司在中国常跌的五个坑|禾赛科技CEO李一帆柏林亚太周主题演讲 李一帆 Xtecher特稿作者 关注 Xtecher推荐 演讲者:李一帆 翻译:晓娜 网址:www.xt ...
- JS数字指定长度不足前补零的实现
问题描述: 要求输出的数字长度是固定的,如长度为2,数字为1,则输出01,即不够位数就在之前补足0. 解决方法: 方法1 function fn1(num, length) { ret ...