简单的生产者消费者演示
生成者生产消息到消息队列中,消费者再从消息队列中获取消息来进行消费。
public class Producer {
public static final String QUEUE_NAME = "HELLO";
public static void main(String[] args) {
// 创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("47.100.54.149");
factory.setUsername("admin");
factory.setPassword("password");
//channel实现了自动close接口 自动关闭 不需要显示关闭
try(Connection connection = factory.newConnection();
Channel channel = connection.createChannel())
{
/** *生成一个队列*
* 1.队列名称
* *2.队列里面的消息是否持久化 默认消息存储在内存中
* *3.该队列是否只供一个消费者进行消费 是否进行共享 true可以多个消费者消费
* *4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* *5.其他参数
* */
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String message="hello world";
/** *发送一个消息
* *1.发送到那个交换机
* *2.路由的key是哪个
* *3.其他的参数信息
* *4.发送消息的消息体
* */
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
public class Consumer {
// name
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("47.100.54.149");
factory.setUsername("admin");
factory.setPassword("password");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
/** *消费者消费消息
* *1.消费哪个队列
* *2.消费成功之后是否要自动应答 true代表自动应答 false手动应答
* *3.消费者未成功消费的回调
* */
//推送的消息如何进行消费的接口回调
DeliverCallback deliverCallback=
(consumerTag,delivery)->{
String message= new String(delivery.getBody());
System.out.println(message);
};
//取消消费的一个回调接口 如在消费的时候队列被删除掉了
CancelCallback cancelCallback=
(consumerTag)->{
System.out.println("消息消费被中断");
};
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
实际情况是一个消费者无法满足工作要求,需要多个消费者即工作线程来同时工作。且每个线程只能处理一次,不能多次处理。
消息应答
消息应答:为了保证消息在发送过程中不丢失,rabbitmq引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉rabbitmq它已经处理了,rabbitmq可以把该消息删除了。
消息应答方式:一个肯定确认两个否定确认
Channel.basicAck(用于肯定确认)
RabbitMQ已知道该消息并且成功的处理消息,可以将其丢弃了
Channel.basicNack(用于否定确认)
Channel.basicReject(用于否定确认)
与Channel.basicNack相比少一个参数不处理该消息了直接拒绝,可以将其丢弃了。
批量应答:
channel.basicAck("",true); // 第二个参数表示是否批量 应答
true代表批量应答channel上未应答的消息
比如说channel上有传送tag的消息 5,6,7,8 当前tag是8 那么此时5-8的这些还未应答的消息都会被确认收到消息应答,false同上面相比,只会应答tag=8的消息 5,6,7这三个消息依然不会被确认收到消息应答。
重新入队:如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失),导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。
手动应答实例:
public class Task2 {
public static final String task_queue_name = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
channel.queueDeclare(task_queue_name,false,false,false,null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
channel.basicPublish("",task_queue_name,null,message.getBytes("UTF-8"));
System.out.println("the message has send"+message);
}
}
}
消费者:
public class Work2 {
public static final String TASK_QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback deliverCallback =(consumerTag,message)->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("get message"+new String(message.getBody(),"UTF-8"));
/** *
* 1.消息标记tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
boolean autoACK = false;
channel.basicConsume(TASK_QUEUE_NAME,autoACK,deliverCallback,(consumerTag)->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑"); });
}
}
手动应答的情况下,如果在一定的时间内消费者没有应答,消息将被分发给另一个消费者重新进行处理。
持久化:
如何保障当RabbitMQ服务停掉以后消息生产者发送过来的消息不丢失。默认情况下RabbitMQ退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息都标记为持久化。
持久化是为了保证当MQ宕机的时候队列不消失。
持久化的方式,在声明队列的时候,将对第二个参数改为true。
队列持久化:
channel.queueDeclare(task_queue_name,true,false,false,null);
features里面为D 表示这个队列是持久化的。
消息持久化:
在发布消息的时候,第三个参数设置消息是否持久化
channel.basicPublish("",task_queue_name, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
MessageProperties.PERSISTENT_TEXT_PLAIN 这个参数表示保存到磁盘上。
将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。
不公平分发:
MQ默认的轮训分发,即对所有的消费者轮训分发,一般需要改为不公平分发,即根据消费者的处理能力进行消费。
需要在消费者中将信道设置为不公平分发:
channel.basicQos(1); // 0是轮训分发,1 是不公平分发
预取值:给消费者设定消费的消息数,比如两个消费者,一共有五条消息,就可以先设定预取值,给每个消费者分配几条消息。
发布确认
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
发布确认是生成者发送消息后,MQ接收到后会将信息保存到磁盘上,然后通知生产者这部分消息以及持久化了,可以确认了。
要使生产者发布的消息绝对不丢失,需要做到三点:
1、队列必须持久化
2、队列中的消息必须持久化
3、发布确认
channel.confirmSelect(); // 将整个信道,开启发布确认
确认发布有三种:
单个确认发布:这是一种简单的确认方式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布。
批量法消息,单个确认是发一个确认一个
// 1 单个确认发布
public static void publishMessageIndividually() throws Exception {
Channel channel = RabbitMQUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,false,false,false,null);
channel.confirmSelect();
long begin = System.currentTimeMillis();
// 批量法消息,单个确认是发一个确认一个
for(int i = 0;i
批量确认 发布: 发布确认后,如果有失败的,不知道是哪个消息发送确认失败。
// 2 批量确认发布
public static void publishMessageBatch() throws Exception {
Channel channel = RabbitMQUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,false,false,false,null);
channel.confirmSelect();
long begin = System.currentTimeMillis();
int batchSize = 100;
// 批量法消息,批量确认
for(int i = 0;i
异步确认发布:
这种方式跟计网中,确认ACK报文段一样,也是编号,然后根据确认的位置进行回调。可以理解为滑动窗口。
处理未确认的消息:最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,比如说用ConcurrentLinkedQueue这个队列在confirm callbacks与发布线程之间进行消息的传递。
将消息都放到这个队列中,一旦确认,就将消息从中删除,剩下的就是没有确认的。
// 异步确认发布
public static void publishMesaageAsync() throws Exception {
Channel channel = RabbitMQUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
channel.confirmSelect();
/**
* 线程安全有序的哈希表,使用与高并发
* 1、轻松的将序号和消息进行关联
* 2、批量删除条目
*/
ConcurrentSkipListMap outstandingCofirm =
new ConcurrentSkipListMap<>();
// 消息确认成功回调函数
/**
* 1、消息标识
* 2、是否为批量确认
*/
ConfirmCallback ackCallBack = (deliveryTag, multiple)->{
System.out.println("成功"+deliveryTag);
if(multiple){
// 删除已经确认的消息 批量确认
ConcurrentNavigableMap confirmed =
outstandingCofirm.headMap(deliveryTag);
confirmed.clear();
}else{
// 单个确认
outstandingCofirm.remove(deliveryTag);
}
};
// 消息确认失败回调函数
ConfirmCallback nackCallback = (deliveryTag, multiple)->{
String message = outstandingCofirm.get(deliveryTag);
System.out.println("未确认的消息是"+message);
System.out.println("失败"+deliveryTag);
};
// 需要准备一个消息监听器,哪些消息发送成功了,哪些发送失败了需要重发
channel.addConfirmListener(ackCallBack,nackCallback);
// 异步发布
long begin = System.currentTimeMillis();
for(int i = 0;i