初学rabbimt
下载安装
官网: www.rabbitmq.com
1 因为他是其他语言写的,所以先安装支持他的语言的环境
2 安装 进入该目录
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.3\sbin
运行入下:
rabbitmq-plugins enable rabbitmq_management
-- 找到该目录 删除下面的文件
C:\Users\admin\AppData\Roaming\RabbitMQ\db
-- 重新安装
访问 : http://localhost:15672/ 用户名和密码 guest guest
看代码
//简单队列名称
private static final String SIMPLE_QUEUE_NAME = "simple_mq";
/*
* 获取rabbitmq链接
* rabbitmq是AMPQ协议 有 服务地址 及 端口号 及 访问的数据库 及 用户名+密码
*/
public static Connection getConnection() throws Exception {
ConnectionFactory connection = new ConnectionFactory();
connection.setHost("192.168.0.50");
connection.setPort(15276);
connection.setVirtualHost("/simple_mq");//simple_mq 相当于数据库
connection.setUsername("user");
connection.setPassword("123");
return connection.newConnection();
}
/**
* 模式一:简单队列 simple P->C
* 1 生产者通过通道生成消息
* 2 创建队列(名称)
* 3 消费者通过队列(名称)获取消息
* <p>
* 简单队列的不足:
* 生产者与消费者一一对应(如果我想有多个消费者消费此消息的时候就明显的不租了)
* 当生产者队列名称变了的时候消费者也需要变更,此时出现模式二了
*/
@Test
public void SendSms() throws Exception {
Connection connection = null;
Channel channel = null;
try {
//获取链接
connection = getConnection();
//根据链接创建一个通道
channel = connection.createChannel();
//根据通道声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
//创建消息
String msg = "hell simpleQueue";
//发布消息
channel.basicPublish("", SIMPLE_QUEUE_NAME, null, msg.getBytes());
System.out.println("发送消息:" + msg);
} catch (Exception e) {
e.printStackTrace();
} finally {
channel.close();
connection.close();
}
}
/**
* 接受消息
*/
@Test
public void getSms() throws Exception {
Connection connection = null;
Channel channel = null;
//获取链接
connection = RabbitmqConnectionUtilsTest.getConnection();
//根据链接创建一个通道
channel = connection.createChannel();
//队列声明
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
//构建消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println(msg);
}
};
//监听队列
channel.basicConsume(SIMPLE_QUEUE_NAME, true, consumer);
}
/**
* |C1
* 模式二: work queue 工作队列 (一对多) P---|.........Cn
* |C2
* <p>
* 现象:
* 消费者1 和 消费者2 所处理(接收)的消息数量是一样的
* 消费者1 处理的是奇数的消息
* 消费者2 处理的是偶数的消息
* 这种方式也称作为轮训(Round-robin):结果是不管谁忙谁闲都是一人一个
*/
private void workProduceSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
//发送消息
for (int i = 0; i < 50; i++) {
String msg = "work queue" + i;
System.out.println("发送消息:" + msg);
//发布消息
Thread.sleep(i * 20);
channel.basicPublish("", SIMPLE_QUEUE_NAME, null, msg.getBytes());
}
channel.close();
;
connection.close();
}
/**
* 消费者1
*/
private void workConsumer1() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
Consumer consumer1 = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者1接收到的消息:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//监听消息(队列)
channel.basicConsume(SIMPLE_QUEUE_NAME, true, consumer1);
}
/**
* 消费者2
*/
private void workConsumer2() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
Consumer consumer2 = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者2接收到的消息:" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//监听队列消息
channel.basicConsume(SIMPLE_QUEUE_NAME, true, consumer2);
}
/**
* |C1
* 模式二: work queue 工作队列 (一对多) P---|.........Cn
* |C2
* <p>
* 现象:
* 消费者1 和 消费者2 所处理(接收)的消息数量是一样的
* 消费者1 处理的是奇数的消息
* 消费者2 处理的是偶数的消息
* 这种方式也称作为轮训(Round-robin):结果是不管谁忙谁闲都是一人一个,但是现在想让处理快的多处理一些,这个时候怎么处理呢?此时公平分发就出现了
* <p>
* 公平分发(Fair dispatch):当消费者消费完之后,手动告诉生产者,我消费完了,继续消费
* 使用公平分发必须关闭自动应答 ack 改成手动应答 ,使用basicQos(perfetch=1)
* <p>
* boolean autoAck = true;自动应答模式
* channel.basicConsume(SIMPLE_QUEUE_NAME,autoAck,consumer1);
* 消息应答模式默认为false
* boolean autoAck = true;自动应答模式,
* 一旦rabbitmq将消息发送个消费之,消息将从内存中删除,一旦杀死正在执行的消费者,处理的数据将会丢失
* boolean autoAck = false;手动应答模式
* 一旦消费者挂了,就会交付给其他的消费者,rabbitmq支持消息应答,消费者发送一个消息,告诉rabbitmq,这个消息我已经处理完了,你可以删了
* 然后rabbitmq就会把消息从内存中删掉
* <p>
* 综上:如果rabbitmq挂了 那么这个消息仍然会丢失?如果将数据存储到类似于数据库的地方,如磁盘上,你挂了就挂了,重启后读取不就好了吗.
* rabbitmq的数据是支持持久化的
* boolean durable = false;
* channel.queueDeclare(SIMPLE_QUEUE_NAME,durable,false,false,null);
* 在声明队列的时候,第二个参数的含义就是持久化的意思,设置为true即持久化,但是在代码当中不能直接修改一个已经存在的队列的 durable 为 true
* 因为该队列已经存在,在创建的时候是未持久化的,如果你想持久化,在rabbitmq的控制面板直接删除该队列运行即可
*/
private void fairProduceSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
/**
* 每个消费者确认消费之前,消息队列不发送下一个消息到消费者,一次只处理发送一条消息
*
* 限制发送给同一个消费者不得超过一条消息
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//发送消息
for (int i = 0; i < 50; i++) {
String msg = "work queue" + i;
System.out.println("发送消息:" + msg);
//发布消息
Thread.sleep(i * 20);
channel.basicPublish("", SIMPLE_QUEUE_NAME, null, msg.getBytes());
}
channel.close();
;
connection.close();
}
/**
* 消费者1
* 每次只接受一个数据,并且接收完毕后给生成这确认反馈,并且改为手动应答
*/
private void fairConsumer1() throws Exception {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
/**
* 每次只接收一条数据
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
Consumer consumer1 = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者1接收到的消息:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//确认接收后,给消费之反馈恢复
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听消息(队列)
/**
* 设置自动应答,改为false
*/
boolean autoAck = false;
channel.basicConsume(SIMPLE_QUEUE_NAME, autoAck, consumer1);
}
/**
* 消费者2
*/
private void fairConsumer2() throws Exception {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
/**
* 确认每次只接收一条消息
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
Consumer consumer2 = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者2接收到的消息:" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//确认接收后给生成这回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听队列消息
/**
* 自动应答设置为false手动应答
*/
boolean autoAck = false;
channel.basicConsume(SIMPLE_QUEUE_NAME, autoAck, consumer2);
}
/***
* |----->队列 1 ------C1
* 模式三 订阅模式(publish subscirbe) P---X----|
* |------>队列 2 ------C2
*
* P 生产者 :
* 一个生产者对应多个消费者
* 每个消费者都有自己的队列
* X 交换机 转换器 exchange:生产者没有直接把消息发送给队列,而是发给了交换机
* 每个队列都要绑定在交换机上面
* 生产者发送的消息,经过交换机转发到队列,就能够实现一个生产者呗多个消费者消费
* 这就是订阅模式
* 如果只有交换机,没有队列,那么执行代码没问题,但是,消息就不知道哪去了,消息丢失
* 在 rabbitmq中 交换机是没有存储能力的,只有队列是有存储能力的,要想消息存在,那么就得交换机和队列绑定 消费者存在队列 直接绑定
*
*/
//定义一个交换机
public static final String EXCHANGE_NAME = "exchange_fanout";
//生产者
public static void publishSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//通过交换机分发 fanout(不处理路由键)
String msg = "publish subscirbe";
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
System.out.println("Send:" + msg);
channel.close();
connection.close();
}
//消费之1
public void publishRevice1() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
//绑定交换机
channel.queueBind(SIMPLE_QUEUE_NAME, EXCHANGE_NAME, "");
//创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者1接收消息:" + msg);
}
};
//监听队列
channel.basicConsume(SIMPLE_QUEUE_NAME, false, consumer);
}
//消费之2
public void publishRevice2() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(SIMPLE_QUEUE_NAME, false, false, false, null);
//绑定交换机
channel.queueBind(SIMPLE_QUEUE_NAME, EXCHANGE_NAME, "");
//创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者2接收消息:" + msg);
}
};
//监听队列
channel.basicConsume(SIMPLE_QUEUE_NAME, false, consumer);
}
/****
* 模式四:路由模式(routing)
*
* 在说路由模式之前先说一下交换机或者是转换器Exchange
* channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//通过交换机分发 fanout(不处理路由键)
*
*fanout exchange:不处理路由键
* fanout Exchange :当消息发送过来后 通过交换机接收,然后只要是绑定了该交换机的队列,都会接收到消息,然后分发给各自的消费者
* 不处理路由键发送消息的时候 路由键是空的 第二个参数 routingKey ="" channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
*
*direct exchange: 处理路由键
*
*路由模式图:
* |------queue1(队列)----->C
* |(key=error)
* |
* P ----->X(type=direct)- |
* |(key=info)
* |(key=waring)
* |(key=error)
* |-----queue2(队列-------C
*
*图解:
* 生产者发送消息到交换机,此时设置交换机类型为 direct,
* 当交换机所持有的key为error的时候,队列1和队列2 都可以接收到消息,然后给自己的消费者
* 当key=info的时候,只有队列2能接收到消息进行存储发给自己的消费者
*
*
*/
private static final String EXCHANGE_NAME_DIRECT = "exchange_direct";
public void directExchangeSend() throws Exception {
//P----X----QUEUE--C
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取通过
Channel channel = connection.createChannel();
//声明交换机,并设置交换机类型为路由键direct
channel.exchangeDeclare(EXCHANGE_NAME_DIRECT, "direct");
//设置消息
String msg = "hello exchange_direct";
System.out.println(msg);
//设置路由键key
String routingKey = "error";
//发送消息
channel.basicPublish(EXCHANGE_NAME_DIRECT, routingKey, null, msg.getBytes());
channel.close();
connection.close();
}
/**
* 消费者 1
*/
//队列1
private static final String QUEUE1_DIRECT_NAME = "queue_direct1";
private static final String QUEUE2_DIRECT_NAME = "queue_direct2";
public void directReviceConsumer() throws Exception {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
//声明队列
boolean durable = false;//数据是否需要持久化
channel.queueDeclare(QUEUE1_DIRECT_NAME, false, false, false, null);
//绑定交换机,第三个参数是绑定路由键的key
String routingKey = "error";
channel.queueBind(QUEUE1_DIRECT_NAME, EXCHANGE_NAME_DIRECT, routingKey);
//设置为公平分发,当消费者确认接收消息后再告诉发送者,然后发送者将数据从内存中删除,限制发给同一消费者同一时间不得超过一条消息
int prefertchCount = 1;
channel.basicQos(prefertchCount);
//设置消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("消费者1接收消息:" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//使用公平分发,确认接收后,给消费之反馈恢复
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听消费者
boolean autoAck = false;//自动应答模式设置为false ,false为手动应答
channel.basicConsume(QUEUE1_DIRECT_NAME, false, consumer);
}
/**
* 消费者2
*/
public void directReviceConsumer2() throws Exception {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
//声明队列
boolean durable = false;//设置数据是否需要持久化
channel.queueDeclare(QUEUE2_DIRECT_NAME, durable, false, false, null);
//绑定交换机
//因为路由键有多个,所以这里绑定多个
String routingKey1 = "error";
String routingKey2 = "info";
String routingKey3 = "waring";
channel.queueBind(QUEUE2_DIRECT_NAME, EXCHANGE_NAME_DIRECT, routingKey1);
channel.queueBind(QUEUE2_DIRECT_NAME, EXCHANGE_NAME_DIRECT, routingKey2);
channel.queueBind(QUEUE2_DIRECT_NAME, EXCHANGE_NAME_DIRECT, routingKey3);
//设置公平分发,每个消费者确认消费之前,消息队列不发送下一个消息到消费者,一次只处理发送一条消息
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//设置消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
//获取到消息
String msg = new String(body, "utf-8");
System.out.println("消费者2接收到的消息:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//手动回执,当消费者接收到消息后,告诉发送者,发送者将数据从内存冲删除
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听消费者
//设置应答类型为false 因为使用公平分发,这里需要设置为false
boolean autoAck = false;
channel.basicConsume(QUEUE2_DIRECT_NAME, autoAck, consumer);
}
/***
* 模式五 :主题模式(topic)
* Topic Exchange :
* 将路由键和某种模式匹配
*
*模型图:
* |(*.orange.*)----->QUEUE1------C
* |
* |
* P-------X (type=topic)--
* |
* |(*.*.rabbit)
* | ------QUEUE2-------C
* | (lazy.#)
*注释: * 匹配一个 # 匹配多个
*由模型图可以看出 主题模式的交换机类型设定为 topic ,routingKey为自定义设置
*
*
* 例子:
* 商品:发布 修改 删除 查询
*/
//创建一个交换机名称
private static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
public void topicExchangeSend() throws Exception {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(TOPIC_EXCHANGE_NAME, "topic");
//生成消息
String msg = "hello topic";
//设置路由键key
String routingKey = "good.add";
//发布消息
channel.basicPublish(TOPIC_EXCHANGE_NAME, routingKey, null, msg.getBytes());
channel.close();
connection.close();
}
//创建队列的名称
private static final String QUEUE_TOPIC1_NAME = "topic_queue_1";
private static final String QUEUE_TOPIC2_NAME = "topic_queue_2";
/**
* 消费者1
*/
public void topicReviceConsumer() throws Exception {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
//声明队列
boolean durable = false;//设置数据是否需要持久化
channel.queueDeclare(QUEUE_TOPIC1_NAME, false, false, false, null);
//绑定交换机
String routingKey = "good.#";
channel.queueBind(QUEUE_TOPIC1_NAME, TOPIC_EXCHANGE_NAME, routingKey);
//设置公平分发,每个消费者确认消费之前,消息队列不发送下一个消息到消费者,一次只处理发送一条消息
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("主题模式消费者1接收消息为:" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//设置回执,消费者确认接收到消息后告诉消费者
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听消费者
//因为是公平分发,手动触发,所以设置自动回答为false
boolean autoAck = false;
channel.basicConsume(QUEUE_TOPIC1_NAME, autoAck, consumer);
}
/**
* 消费者2
*/
public void topicReviceConsumer2() throws Exception {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
//声明队列
boolean durable = false;//设置rabbitmq发送的数据是否需要持久化,因为他是支持持久化的
channel.queueDeclare(QUEUE_TOPIC2_NAME, durable, false, false, null);
//绑定交换机
channel.exchangeBind(QUEUE_TOPIC2_NAME, TOPIC_EXCHANGE_NAME, "good.add");
channel.exchangeBind(QUEUE_TOPIC2_NAME, TOPIC_EXCHANGE_NAME, "good.update");
channel.exchangeBind(QUEUE_TOPIC2_NAME, TOPIC_EXCHANGE_NAME, "good.query");
//设置公平分发,确认每个消费者同一时间内处理一条消息 ,消费者接收到消息确认后,再回执给发送者,然后发送者再清楚数据
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
//接收消息
String msg = new String(body, "utf-8");
System.out.println("主题模式消费者2接收到的消息为:" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//当消费者确认消息接收后,回执给发送者
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听消费者
//回执应答设置为false (手动的)
boolean autoAck = false;
channel.basicConsume(QUEUE_TOPIC2_NAME, autoAck, consumer);
}
/***
* Rabbitmq的消息确认机制(事务+confirm)
*
* 在rabbitmq中持久化数据可以解决rabbitmq异常导致数据丢失问题,在消费者中设置
* 生产者将消息发送之后,数据到底有没有到rabbitmq服务器,默认情况是不知道的
*
* 两种方式:(都是对生产者设置)
*
* AMQP 实现了事务机制(与mysql类似)
* Confirm 模式
*
* AMQP 事务机制 降低了rabbitmq的吞吐量
* txSelect txCommit txRollback
*
* txSelect : 将当前channle设置成transion模式
* txCommit : 用于提交事务
* txRollback : 事务回滚
*
*Confirm 最大的好处在于它是异步的
*
* 开启confirm模式
* channel.confirmSelect();
*
* 编程模式
*
* 1 普通的 发送一条消息 waitForConfirms()
* 2 批量的 发送多条消息 waitForComfirms()
* 3 异步的confir模式提供一个回调函数
*
* confirm的异步模式 :
*
* confirm对象提供的ConfirmListener()回调方法只包含deliveryTag
*
* deliveryTag : 生产者没发送一条消息,该调消息对应的序列号
*
* 我们需要为每一个Channel维护一个unconfirm(未接收的消息的集合)的结集合,每publish一条数据集合元素+1
* 每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录
* 从效率上来看,这个集合最好采用有序集合SortedSet存储结构
*/
private static final String QUEUE_NAME_TX = "tx_queue_transion";
public void txTransionSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME_TX, false, false, false, null);
try {
String msg = "事务";
//开启事务
channel.txSelect();
//发送消息
channel.basicPublish("", QUEUE_NAME_TX, null, msg.getBytes());
//提交事务
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
e.printStackTrace();
}
}
public void txTransionConsumer() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME_TX, false, false, false, null);
//接收消息
channel.basicConsume(QUEUE_NAME_TX, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("msg===" + msg);
}
});
}
/**
* confirm模式
*/
private static final String QUEUE_NAME_CONFIRM = "confirm_queue_transion";
public void confirmSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME_CONFIRM, false, false, false, null);
//开启cnfirm事务,使用confirm的时候不能使用事务tx开头的 ,会产生异常
channel.confirmSelect();
String msg = "confirm hello";
channel.basicPublish("", QUEUE_NAME_CONFIRM, null, msg.getBytes());
/**
* 批量发送的时候 创建多条消息发送即可,注意的是 先发送消息 再确定是否发送成功
*/
//发完消息再确认
if (!channel.waitForConfirms()) {
System.out.println("消息发送成功");
} else {
System.out.println("消息发送失败");
}
channel.close();
connection.close();
}
/**
* confirm 异步模式
*
*/
private static final String CONFIRM_QUEUE_NAME="confirm_queue_name";
public void confirmTransionSend() throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME_CONFIRM, false, false, false, null);
//生产者调用confirmSelect(),将channel设置为confirm模式
//未确认的消息标识
final SortedSet<Long> confirmSet = (SortedSet<Long>) CollectionUtils.synchronizedCollection(new TreeSet<Long>());
channel.addConfirmListener(new ConfirmListener() {
/**
*消息发送成功回调handleAck
*/
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if(multiple){
System.out.println("发送成功");
confirmSet.headSet(deliveryTag+1).clear();
}else{
confirmSet.remove(deliveryTag);
}
}
/**
*消息发送失败回调handleAck
*/
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
if(multiple){
System.out.println("发送成功");
confirmSet.headSet(deliveryTag+1).clear();
}else{
confirmSet.remove(deliveryTag);
}
}
});
String msg = "confirmtransion";
while(true){
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("",CONFIRM_QUEUE_NAME,null,msg.getBytes());
confirmSet.add(seqNo);
}
}
看如下