1、引入rabbitmq的客户端jar包
com.rabbitmq
amqp-client
5.6.0
2、rabbitmq的模型
第一种模型直连(直连)
P:生产者,发送消息的程序;
C:消费者,消息的接收者,会一直等待消息的到来;
queue:消息队列,类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
创建rabbitmq的链接工具类
public class RabbitUtil {
private static ConnectionFactory connectionFactory;
static{
//创建mq的连接工厂
connectionFactory = new ConnectionFactory();
//设置主机
connectionFactory.setHost("00.00.00.00");
//设置端口
connectionFactory.setPort(5672);
//设置虚拟主机
connectionFactory.setVirtualHost("/athome");
//设置用户名和密码
connectionFactory.setUsername("test");
connectionFactory.setPassword("123456");
}
public static Connection getConnection() {
Connection connection = null;
try {
connection = connectionFactory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
public static void close(Channel channel,Connection connection){
try {
if(channel!=null) channel.close();
if(connection !=null) connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消息生产者:
public class Producer {
@Test
public void test() throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
//获取连接中的channel
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//1、队列名称,如果不存在自动创建
//2、durable 用来定义队列特性是否要持久化 ,true 是 false 否 当rabbitmq重启后,队列全部丢失
//3、exclusive 是否独占队列 true 是 false否
//4、autodelete 是否在消费消息后删除队列 true 是 false 否
//5、额外参数
channel.queueDeclare("bb", true, false, true, null);
//发布消息
//1、交换机名称
//2、队列名称 routingkey 决定了向哪个队列发送消息
//3、消息的额外设置 MessageProperties.PERSISTENT_BASIC用来设置消息持久化
//4、消息的具体内容跟
channel.basicPublish("", "bb", MessageProperties.PERSISTENT_BASIC, "Hello word".getBytes());
RabbitUtil.close(channel, connection);
}
}
表示将队列持久化到磁盘中了。
A 表示消息被消费后 自动删除队列(前提是消费端断开连接)
消息消费者
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello", false, false, false, null);
channel.basicConsume("", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("..............消息体:" + new String(body));
}
});
}
第二种模型work queue(解决的问题是,当一个消费者消费速度小于生产者速度时,会产生消息堆积,因此采用增加消费者的数量,只要消费掉一个就从队列中删除一个,不会产生消息重复消费的问题)
任务模型,当消息处理比较耗时时,可能生产消息的速度远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型,让多个消费者绑定到一个队列,共同消费队列中的消息,队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
P:生产者,任务的发布者;
c1:消费者1,领取任务并且完成任务,假设完成速度较慢;
c2: 消费者2,领取任务并且完成任务,假设完成速度较快;
消息生产者
public class ProducerWork {
@Test
public void test() throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
//获取连接中的channel
Channel channel = connection.createChannel();
//通道绑定对应消息队列
//1、队列名称,如果不存在自动创建
//2、durable 用来定义队列特性是否要持久化 ,true 是 false 否 当rabbitmq重启后,队列全部丢失
//3、exclusive 是否独占队列 true 是 false否
//4、autodelete 是否在消费消息后删除队列 true 是 false 否
//5、额外参数
channel.queueDeclare("work", true, false, false, null);
//发布消息
//1、交换机名称
//2、队列名称 routingkey 决定了向哪个队列发送消息
//3、消息的额外设置 MessageProperties.PERSISTENT_BASIC用来设置消息持久化
//4、消息的具体内容跟
for (int i = 0; i < 10; i++) {
channel.basicPublish("", "work", null, (i + "Hello word").getBytes());
}
RabbitUtil.close(channel, connection);
}
}
消费者一
public class ConsumerWork1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work", true, false, false, null);
channel.basicConsume("work", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("..............消息体:" + new String(body));
}
});
}
}
消费者二
public class ConsumerWork2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work", true, false, false, null);
channel.basicConsume("work", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("..............消息体:" + new String(body));
}
});
}
}
可以看出消息被两个消费者平均消费;产生的问题就是性能差的机器会处理慢。
解决方式:
1、每次从队列只取出来一个消息
2、将自动提交改为手动提交,这样根据机器性能处理快的多处理;
public class ConsumerWork1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//每次只处理一个消息
channel.basicQos(1);
channel.queueDeclare("work", true, false, false, null);
//参数2是将自动提交改为手动提交
channel.basicConsume("work", false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("..............消息体:" + new String(body));
//手动确认,参数1、手动确认标识;参数2、每次确认一个
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
第三种消息模型fanout也称为广播
在广播模式下,消息发送流程是这样的:
可以有多个消费者;
每个消费者有自己的queue;
每个队列都要绑定到Exchange;
生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定;
交换机把消息发送给绑定过的所有队列;队列的消费者都能拿到消息,实现一条消息被多个消费者消费;
生产者
public class Producer {
@Test
public void test() throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//通道声明指定交换机,参数1交换机名称,参数2交换机类型,fanout 广播
channel.exchangeDeclare("logs", "fanout");
//参数1交换机名称 参数2 路由key
channel.basicPublish("logs", "", null, "交换机".getBytes());
RabbitUtil.close(channel, connection);
}
}
消费者
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//通道绑定交换机
channel.exchangeDeclare("logs", "fanout");
//临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queueName, "logs", "");
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者二:" + new String(body));
}
});
}
}
第四种模型:Routing
1、Routing之订阅模型-Direct(直连)
在fanout模式中,一条消息会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange.
在Direct模型下:
队列与交换机的绑定,不能任意绑定了,而是要指定一个RoutingKey;
消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey;
Exchange不把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断,只有队列的RoutingKey与消息的Routingkey完全一致,才会接受到消息;
流程:
生产者,向Exchange发送消息,发送消息时,会指定一个Routingkey;
Exchange,接收生产者的消息,然后把消息递交给与routingkey完全匹配的队列;
消费者1,其所在的队列指定了需要routingkey 为error的消息;
消费者2,其所在的队列指定了需要routinkey为info error warning的消息;
生产者:
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//对构建出来的通道声明一个交换机,类型为direct
channel.exchangeDeclare("logs_direct", "direct");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//队列和交换机绑定,消费路由类型为error的消息
channel.queueBind(queue, "logs_direct", "error");
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费1-error到的消息为:" + new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//对构建出来的通道声明一个交换机,类型为direct
channel.exchangeDeclare("logs_direct", "direct");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//队列和交换机绑定,消费路由类型为error的消息,可以绑定多个路由
channel.queueBind(queue, "logs_direct", "info");
channel.queueBind(queue, "logs_direct", "warn");
channel.queueBind(queue, "logs_direct", "error");
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费2-info到的消息为:" + new String(body));
}
});
}
}
第五种模式routing之订阅模型-topic
Topic类型的Exchange与Direct相比,都是可以根据Routingkey把消息路由到不同的队列,只不过topic类型的exchange可以让队列在绑定routingkey的时候使用通配符,这种模型的routingkey一般都是由一个或多个单词组成,多个单词之间以“.”分割;
通配符
*匹配一个单词;
#匹配一个或多个词;
生产者
public class Producer {
@Test
public void test() throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//绑定交换机和交换机类型
channel.exchangeDeclare("topics", "topic");
//声明路由
String routingkey = "user.save.aa";
//消息发送
channel.basicPublish("topics", routingkey, null, "hello world".getBytes());
RabbitUtil.close(channel, connection);
}
}
消费者1
public class Consumer10 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("topics", "topic");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, "topics", "user.*");
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息10消费到的消息:" + new String(body));
}
});
}
}
消费者2
public class Consumer11 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("topics", "topic");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, "topics", "user.#");
channel.basicConsume(queue, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息11消费到的消息:" + new String(body));
}
});
}
}