这是我参与更文挑战的第4天,活动详情查看: 更文挑战
RabbitMQ基本消息模型
我在本地虚拟机已经搭建好了RabbitMQ,并建立了用户。
RabbitMQ是一个消息代理接收消息和转发消息,可以看作是一个快递员,有人把邮寄的东西给快递员,你可以确定快递员最后会把东西送到你指定的人手上,RabbitMQ时取件员,快递公司,派件员。
P(producer/ publisher):生产者,一个发送消息的用户应用程序。
C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序
队列(红色区域):rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。
我们通过java代码编写生产者和消费者,生产者连接到Rabbitmq发送消息然后退出
public class Producer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
//4.创建 channel
Channel channel = connection.createChannel();
//5.创建queue
/*
* `1.queue队列名称
* 2.durable 是否持久化
* 3.exclusive 是否独占,只有一个消费者监听队列2是否关闭时删除队列
* 4.outdelete 是否自动删除
* 5.argments 参数信息
* */
channel.queueDeclare("hello_world",true,false,false,null);
//6.发送消息
/*
* 1.exchange 交换机名称,简单模式使用默认的
* 2.routingkey 路由名称
* 3.props 配置信息
* 4,。bady 数据信息
* */
String bady ="hello RabbitMQ";
channel.basicPublish("","hello_world",null,bady.getBytes());
//7.释放资源
channel.close();
connection.close();
}
}
控制台没有报错
再看一下RabbitMQ,有一个 hello world的队列一条消息
再运行一遍,在看控制台有两条未被消费的消息
现在我们用消费者连上mq来消费者来消费这两条消息
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
//4.创建 channel
Channel channel = connection.createChannel();
channel.queueDeclare("hello_world",true,false,false,null);
//6.接受消息
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("exchange:"+envelope.getExchange());
System.out.println("routingkey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
System.out.println("consumerTag:"+consumerTag);
}
};
channel.basicConsume("hello_world",true,consumer);
}
}
控制台输出
刚才的两条消息都被消费掉了,再看RabbitMq控制台,刚才的消息都被消费掉了,变成了0,但是消费者没有停止运行,一直监听着队列,一旦队列有了新消息,就会立即消费。
RabbitMQ怎么知道消息有没有被消呢?
我们再运行一遍生产者你会发现,我们发送的消息几乎瞬间被消费者消费掉,在控制台基本看不到消息停留,那如果消费者休闲异常死掉了,拿到消息有没有消费成功我们就不不知道了,这样我们的目的就没有达到,因此QabbitMq有他一个ACk机制,消费者获取到消息后,会给RabbitMQ发送回执消息,告知消息已被接受,有两种情况:
- 自动ACK:消息一旦被接收,消费者自动发送ACK
- 手动ACK:消息接收后,不会发送ACK,需要手动调用
之前都是自动ACK, 那自动ack存在什么问题?
生产者不做任何修改,发送一条消息,发送成功
我们在消费者制造异常
运行消费者,并没有打印之前消费的信息,
控制台消息被消费了
手动ACK只需要修改下列方法的第二个参数,如果第二个参数为true,则会自动进行ACK;如果为false,则需要手动ACK。
public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException {
return this.basicConsume(queue, autoAck, "", callback);
}
修改完成
channel.basicConsume("hello_world",false,consumer);
运行生产者发送消息
运行消费者接收消息
查看RabbitMq控制台,因为我们设置了手动ack,但是代码里没有返回,消息没有被真正消费掉,变成unacked没有收到ack。
现在我们关掉消费者,消息有重新回到Ready状态了
修改消费者手动ACK确认,消费成功。