概览
- message queue 消息队列 先进先出的数据结构
- 解耦
- 提高可用性
- 流量削峰
- 数据分发 服务从MQ中获取数据
优点
- 解耦
- 流量削峰
- 数据分发
缺点
- 系统复杂性提高了
- 系统可用性降低了
- 一致性问题
控制台概览

HelloWord
导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.2.0</version>
</dependency>
发送者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* @author wangz
*/
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "HelloWorld";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
接受者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
}
}
五种中间件模式
生产者消费者的channel属性必须严格一致
直连模式

- 消费者持续监听队列
- 生产者将消息放入队列中
- ==生产者--队列--消费者==直连
生产者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* @author wangz
*/
public class Send {
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/snail");
factory.setUsername("name");
factory.setPassword("pwd");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello", false, false, false, null);
channel.basicPublish("", "hello", null, "hello mq".getBytes(StandardCharsets.UTF_8));
System.out.println("success");
channel.close();
connection.close();
}
}
消费者
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @author wangz
*/
public class Recv {
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/snail");
factory.setUsername("user");
factory.setPassword("pwd");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello", false, false, false, null);
channel.basicConsume("hello", 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));
}
});
}
}
细节
// queue给通道声明队列,当队列不存在时,可以创建相对的队列
// durable 持久化,在磁盘中保存队列,设置为 false 时, 当服务重启时队列会丢失,注意是队列
// exclusive 是否独占队列
// autoDelete 消费完成之后是否删除消息
channel.queueDeclare("hello", false, false, false, null);
work queue

- 在直连模式中,仅有一个消费者消费. 可能会造成消息的大量堆积. 使用
工作队列解决这一问题. - 默认会使用
轮询来解决多个消费者的问题 - 可以调整使用
ack机制达到最大的吞吐量
- 将
自动ack设置为false channel.basicQos(5);设置窗口大小
- 设置ack机制,累计确认是否开启
生产者
package workqueue;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import utiles.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author wangz
*/
public class Send {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = Connection.getChannel();
channel.queueDeclare("hello", false, false, false, null);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
Connection.close();
}
}
消费者
package workqueue;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import utiles.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangz
*/
public class ConsumerTwo {
public static void main(String[] args) throws IOException, TimeoutException {
consumer();
}
static void consumer() throws IOException, TimeoutException {
Channel channel = Connection.getChannel();
channel.queueDeclare("hello", false, false, false, null);
channel.basicConsume("hello", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
System.out.println(this.getClass().getName()+" : "+ new String(body));
}
});
}
}
package workqueue;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangz
*/
public class ConsumerOne {
public static void main(String[] args) throws IOException, TimeoutException {
ConsumerTwo.consumer();
}
}
Publish/Subscribe
扇出/广播

- 可以有多个消费者
- 每个消费者都有自己的工作队列
- 每个队列都要绑定到
exchange交换机X - 生产者发送消息只能发送到交换机,由交换机决定发送到那个队列
- 交换机把消息发送给
绑定过的所有队列 - 实现一条消息被多个消费者消费,不同种的消费者
生产者
package fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* @author wangz
* 将消息发送给交换机
*/
public class Prov {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 通道绑定交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String message = "hello exchange";
// 将消息发送给交换机
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
消费者
package fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* @author wangz
*/
public class Consumer {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 通道绑定交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//自动创建一个queue兵器额返回他的名称
String queueName = channel.queueDeclare().getQueue();
System.out.println(queueName);
//绑定交换机和队列
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
// 回调函数相关
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
Routing

-
fanout模式仅能进行简单广播,不能实现精准的通知 -
在发送消息时加上一个路由,然后将消息发送给交换机
-
交换机根据queue持有的
routerKey分发消息
生产者
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String router = "router";
String message = "router test";
// 发送消息时携带路由信息
channel.basicPublish(EXCHANGE_NAME, router, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + router + "':'" + message + "'");
消费者
//声明交换机以及类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 创建临时的 queue 并且获取名称
String queueName = channel.queueDeclare().getQueue();
// 通道绑定交换机、queue ,同时携带 routerKey
channel.queueBind(queueName, EXCHANGE_NAME, "router");
Topic exchange

-
动态路由 -
当路由的规则太多时,会有大量的路由信息
-
使用通配符简化
生产者
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 创建的交换机的类型为 topic
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 这里的 routerKey与简单的路由中不同
String routingKey = "aa.bb.cc";
String message = "dynamic router";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
}
}
//..
}
消费者
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 注意类型
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
// 使用 * 通配符 或者 # 通配符 .
// * 是模糊匹配 ,# 是精确匹配
channel.queueBind(queueName, EXCHANGE_NAME, "aa.*.*");
show(channel, queueName);
}
springboot整合
a914af59de501c7794b9dfc89a3ca953