RabbitMQ
0. 前言
0.1 为什么要学习消息中间件rabbitmq?
① 中高级java程序员的必备的升职加薪利器;
② 在分布式架构体系技术栈中的不可或缺的技术;
③rabbitmq作为开源免费的消息中间件,其优势显著;
④国内大多数的银行系统,采用了-该消息中间件,作为技术方案中的一个组合;
⑤rabbitmq可以有效的解决我们在项目中遇到的工程性能技术问题.
0.2 rabbitmq作为主流消息中间件的优势
① 异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
② 解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
③ 冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
④ 扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
⑤ 过载保护
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
⑥ 可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
⑦ 顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
⑧ 缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
⑨ 数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。
ActiveMQ | RocketMQ | RabbitMQ | Kafka |
---|---|---|---|
Apache下的一个子项目。使用Java完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,少量代码就可以高效地实现高级应用场景。可插拔的传输协议支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多种语言客户端 C++、Java、.Net,、Python、 Php、 Ruby等。 | 阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,是阿里参照kafka设计思想使用java实现的一套mq。同时将阿里系内部多款mq产品(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下mq的架构,目前主要多用于订单交易系统。能够保证严格的消息顺序 提供针对消息的过滤功能 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅机制 亿级消息堆积能力 | 使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB整合。 | Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,具有以下特性:快速持久化:通过磁盘顺序读写与零拷贝机制,可以在O(1)的系统开销下进行消息持久化;高吞吐:在一台普通的服务器上既可以达到10W/s的吞吐速率;高堆积:支持topic下消费者较长时间离线,消息堆积量大;完全的分布式系统:Broker、Producer、Consumer都原生自动支持分布式,依赖zookeeper自动实现复杂均衡;支持Hadoop数据并行加载:对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。 |
0.3 rabbitmq的官方网站关键信息提取
1. 安装配置
1.1 docker下的安装和配置
-
docker search rabbitmq:management
查询rabbitmq -
docker pull rabbitmq:management
从镜像仓库拉取镜像 -
docker run -d --name rabbitmq --publish 5671:5671 --publish 5672:5672 --publish 4369:4369 --publish 25672:25672 --publish 15671:15671 --publish 15672:15672 rabbitmq:management
创建容器
1.2 centos7下的安装和配置
- 根据要安装的rabbitmq版本,下载安装对应的Erlang版本
- 根据官方文档提供的安装教程,进行安装和配置,详细参见https://www.rabbitmq.com/down...
1.3 windows下的安装和配置
- 参照官方安装教程
2. 基础知识
❓ 发送端没有设置接收的队列名称, rabbitMQ如何处理?
? 若没有设置“queue”的名字时,会根据“Routing Key”去找是否存在接收队列的名称和“Routing key”一致的队列。存在,则可以传递消息成功,否则,无法传递数据。使用的队列是(AMQP default),若是找不到接收队列,会删除传递的数据。
RabbitMQ的优点:
因为开发语言为Erlang语言,而Erlang语言最初作为交换机使用。因此,对于数据的通信可以达到Socket的水平,因此,性能较好。
2.1 Virtual host(虚拟主机)
虚拟主机,用于进行逻辑分离,最上层的消息路由;
一个Virtual host里面可以有多个Exchange和Queue;
但是,在同一个Virtual host中不能存在同名的Exchange和Queue。
2.2 Exchange(交换机)
2.2.1 属性
name: 名称
Type:交换机类型:direct,topic,fanout,headers;
DuDurability:是否需要持久化;
Auto delete:当最后一个绑定到exchange上的队列删除后,自动删除该exchange;
InterInternal:当前exchange是否用于rabbitMQ内部使用,默认为false;
2.2.2 Direct Exchange(直连交换机)
概念:所有发送到Direct Exchange的消息被转发到Routing key中指定的Queue。
操作:可以使用RabbitMQ自带的Exchange:default Exchange,所以不需要将Exchange进行任何绑定操作,消息传递时,Routing Key完全匹配时,才能被队列接收,否则该消息被丢弃。
2.2.3 Topic Exchange(主题交换机)
概念:
所有发送到Topic Exchange的消息被转发到所有关心Routing key中指定的Topic的Queue上。
Exchange将Routing key和某个Topic进行模糊匹配,此时队列需要绑定一个Topic。
可以使用通配符进行模糊匹配;"#":匹配一个或多个词;"*":只能匹配一个词;
eg: "log.#“能够匹配到"log.ingo.aa”;
eg: “log.*” 能够匹配到"log.erro"
注意事项
使用log.的时候, 查看是否会出现两个routing key, 一个为log.#、log.;?
queue绑定了routing key 不会自动删除routing key;?
2.2.4 Fanout Exchange
概念:
不处理路由键,只需要简单的将队列绑定到交换机上;
发送到交换机的消息都会被转发到与该交换机绑定的所有队列上;
是转发消息最快的;
不需要使用routing key, 直接转发到队列,减少了很多匹配规则,所以,速度很快;
2.3 Binding(绑定)
可以绑顶Exchange和Exchange、Queue之间的联系关系;也就是说,可以Exchange之间进行绑定,发送消息的路径就是:Queue-> Exchange->Exchange->Queue。
Binding中可以包含Routing Key或参数。
2.4 Queue(消息队列)
参数:
Durability:是否持久化;Durable:持久化;Transient:不持久化;
Auto Delete:当最后一个监听被移除的时候,该Queue也会被删除。
2.5 Message(消息)
本质上就是一段数据,由properties和payload(body)组成。
常用属性:delivery mode:1不持久化 2持久化, headers(自定义属性)。
其他属性:
content_type:消息格式,eg:json、xml等等。
content——encoding:消息编码;
priority:优先级。优先级高的优先被消费,但是,在集群模式下,无法保证消息消费的顺序性。
correlation_id:消息唯一id,常用业务id+时间戳组成;可以用于ACK以及幂等性的控制。
replay_to:消息失败时,返回到哪个队列。
expiration:消息的过期时间。
message_id/timestamp/type/user_id/app_id/cluster_id;
3. 官方demo案例
3.1 “Hello World”
创建Maven项目,添加必要依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
编写主要代码
//消息生产者
public class MqProducer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
}
//消息消费者
public class MqConsumer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
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(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
3.2 Work queues
通常RabbitMQ Server的宕机和C端的程序死掉,会导致消息不能够被正确的消费;那么如何确保work queue中的message被正确的消费和不会丢失?
- C端? 编程式,前后消息可达性校验; 设置autoAck为false,默认为true
- S端? 持久化queue和message; S端的对queue和消息的持久化durable,但官方提示该方法是并不能强保证queue持久化的完全一致性.
如果消息队列中的需要处理的消息所消耗的时间和系统资源是不同的,消息分配的不合理可能会导致系统资源的浪费和不合理的使用.比如所有的奇数消息(Weight)都被分发给了C1处理,所有的偶数消息(Light)都被分配给了C2处理,结果会发现C2早早的处理完消息,闲置在那;而C1却仍然在忙碌的工作中.
如何让C端能够合理的处理消息队列呢?
- 每次C端仅仅接受一个消息,消息处理完之后,再领取下一个消息处理
-
但该方案中的如果每个Message都是Weight的,那么会导致Queue堆积过多消息,可以通过多加C端来消化掉.
int prefetchCount = 1; channel.basicQos(prefetchCount);
代码编写
//生产者
public class MqProducer {
private final static String QUEUE_NAME = "taskQueue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//5. 持续的生产消息
while (true){
String message = "Hello World! "+ LocalDateTime.now().toString();
TimeUnit.SECONDS.sleep(1);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Send '" + message + "'");
}
}
}
//消费者
public class MqConsumer {
private final static String QUEUE_NAME = "taskQueue";
public static void main(String[] argv) throws Exception {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 4, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
threadPoolExecutor.execute(() ->{
try {
TimeUnit.SECONDS.sleep(4);
consumeMsg(connection);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
threadPoolExecutor.execute(() ->{
try {
TimeUnit.SECONDS.sleep(4);
consumeMsg(connection);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
threadPoolExecutor.shutdown();
}
public static void consumeMsg(Connection connection) throws IOException {
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message;
try {
message = new String(delivery.getBody(), "UTF-8");
System.out.println(Thread.currentThread().getName()+" Received '" + message + "'");
} finally {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
System.out.println(Thread.currentThread().getName()+" Received Done");
}
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}
}
3.3 Publish/Subscribe
Exchange的类型包含direct,topic,fanout,header
//生产者
public class MqProducer {
private final static String EXCHANGE_NAME = "fanoutExchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明exchange & queue
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
channel.queueDeclare("queue01", false, false, false, null);
channel.queueDeclare("queue02", false, false, false, null);
channel.queueDeclare("queue03", false, false, false, null);
//5. bind exchange和queue
channel.queueBind("queue01",EXCHANGE_NAME,"");
channel.queueBind("queue02",EXCHANGE_NAME,"");
channel.queueBind("queue03",EXCHANGE_NAME,"");
//6. 持续的生产消息
while (true){
String message = "Hello World! "+ LocalDateTime.now().toString();
TimeUnit.SECONDS.sleep(1);
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println(" [x] Send '" + message + "'");
}
}
}
//消费者
public class MqConsumer {
private final static String EXCHANGE_NAME = "fanoutExchange";
public static void main(String[] argv) throws Exception {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message;
try {
message = new String(delivery.getBody(), "UTF-8");
System.out.println(Thread.currentThread().getName()+" Received '" + message + "'");
} finally {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
System.out.println(Thread.currentThread().getName()+" Received Done");
}
};
channel.basicConsume("queue01", false, deliverCallback, consumerTag -> { });
channel.basicConsume("queue02", false, deliverCallback, consumerTag -> { });
channel.basicConsume("queue03", false, deliverCallback, consumerTag -> { });
}
}
3.4 Routing
rabbitmq默认情况下,不配置exchange的话,会使用默认的amqp的default exchange (类型为direct);
//生产者
public class MqProducer {
private final static String EXCHANGE_NAME = "directExchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明exchange & queue
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
channel.queueDeclare("directQueue01", false, false, false, null);
channel.queueDeclare("directQueue02", false, false, false, null);
//5. bind exchange和queue
channel.queueBind("directQueue01",EXCHANGE_NAME,"error");
channel.queueBind("directQueue02",EXCHANGE_NAME,"error");
channel.queueBind("directQueue02",EXCHANGE_NAME,"info");
channel.queueBind("directQueue02",EXCHANGE_NAME,"warning");
//6. 持续的生产消息
// 6.1 生产error消息
new Thread(()->{
while (true){
String message = "Error! "+ LocalDateTime.now().toString();
try {
TimeUnit.SECONDS.sleep(1);
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 6.2 生产info消息
new Thread(()->{
while (true){
String message = "Info! "+ LocalDateTime.now().toString();
try {
TimeUnit.SECONDS.sleep(1);
channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 6.3 生产warning消息
new Thread(()->{
while (true){
String message = "Warning! "+ LocalDateTime.now().toString();
try {
TimeUnit.SECONDS.sleep(1);
channel.basicPublish(EXCHANGE_NAME,"warning",null,message.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//消费者
public class MqConsumer {
private final static String EXCHANGE_NAME = "directExchange";
public static void main(String[] argv) throws Exception {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message;
try {
message = new String(delivery.getBody(), "UTF-8");
System.out.println(Thread.currentThread().getName()+" Received '" + message + "'");
} finally {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
System.out.println(Thread.currentThread().getName()+" Received Done");
}
};
channel.basicConsume("directQueue01", false, deliverCallback, consumerTag -> { });
channel.basicConsume("directQueue02", false, deliverCallback, consumerTag -> { });
}
}
3.5 Topic
//生产者
public class MqProducer {
private final static String EXCHANGE_NAME = "topicExchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明exchange & queue
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
channel.queueDeclare("topicQueue01", false, false, false, null);
channel.queueDeclare("topicQueue02", false, false, false, null);
//5. bind exchange和queue
channel.queueBind("topicQueue01",EXCHANGE_NAME,"*.topic.animal.#");
channel.queueBind("topicQueue02",EXCHANGE_NAME,"*.topic.plant.#");
//6. 持续的生产消息
String[] routingKeys=new String[]{"a.topic.animal.dog","p.topic.plant.tree","p.topic.plant.grass","m.topic.animal.cat.yellowCat","p.topic.plant.fruit.apple"};
while(true){
int i = new Random().nextInt(5);
System.out.println(i+routingKeys[i]);
channel.basicPublish(EXCHANGE_NAME,routingKeys[i],null,routingKeys[i].getBytes());
}
}
}
//消费者
public class MqConsumer {
private final static String EXCHANGE_NAME = "topicExchange";
public static void main(String[] argv) throws Exception {
//1. 创建并配置connectionFactory的连接参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.31.118");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 2.获取连接
Connection connection = connectionFactory.newConnection();
//3. 通过connection获取channel
Channel channel = connection.createChannel();
//4. 通过channel声明queue
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message;
try {
message = new String(delivery.getBody(), "UTF-8");
System.out.println(Thread.currentThread().getName()+" Received '" + message + "'");
} finally {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
System.out.println(Thread.currentThread().getName()+" Received Done");
}
};
channel.basicConsume("topicQueue01", false, deliverCallback, consumerTag -> { });
channel.basicConsume("topicQueue02", false, deliverCallback, consumerTag -> { });
}
}
3.6 RPC
TODO
3.7 Publisher Confirms
package com.gh.basic.middleware.rabbitmq;
/**
* PublisherConfirms
*
* @author: Golphin
* @date: 2019/10/12
* @description:
*/
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.BooleanSupplier;
public class PublisherConfirms {
static final int MESSAGE_COUNT = 50_000;
static Connection createConnection() throws Exception {
ConnectionFactory cf = new ConnectionFactory();
cf.setHost("192.168.31.118");
cf.setUsername("guest");
cf.setPassword("guest");
return cf.newConnection();
}
public static void main(String[] args) throws Exception {
publishMessagesIndividually();
publishMessagesInBatch();
handlePublishConfirmsAsynchronously();
}
static void publishMessagesIndividually() throws Exception {
try (Connection connection = createConnection()) {
Channel ch = connection.createChannel();
String queue = UUID.randomUUID().toString();
ch.queueDeclare(queue, false, false, true, null);
ch.confirmSelect();
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
ch.basicPublish("", queue, null, body.getBytes());
ch.waitForConfirmsOrDie(5_000);
}
long end = System.nanoTime();
System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static void publishMessagesInBatch() throws Exception {
try (Connection connection = createConnection()) {
Channel ch = connection.createChannel();
String queue = UUID.randomUUID().toString();
ch.queueDeclare(queue, false, false, true, null);
ch.confirmSelect();
int batchSize = 100;
int outstandingMessageCount = 0;
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
ch.basicPublish("", queue, null, body.getBytes());
outstandingMessageCount++;
if (outstandingMessageCount == batchSize) {
ch.waitForConfirmsOrDie(5_000);
outstandingMessageCount = 0;
}
}
if (outstandingMessageCount > 0) {
ch.waitForConfirmsOrDie(5_000);
}
long end = System.nanoTime();
System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static void handlePublishConfirmsAsynchronously() throws Exception {
try (Connection connection = createConnection()) {
Channel ch = connection.createChannel();
String queue = UUID.randomUUID().toString();
ch.queueDeclare(queue, false, false, true, null);
ch.confirmSelect();
ConcurrentNavigableMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> {
if (multiple) {
ConcurrentNavigableMap<Long, String> confirmed = outstandingConfirms.headMap(
sequenceNumber, true
);
confirmed.clear();
} else {
outstandingConfirms.remove(sequenceNumber);
}
};
ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> {
String body = outstandingConfirms.get(sequenceNumber);
System.err.format(
"Message with body %s has been nack-ed. Sequence number: %d, multiple: %b%n",
body, sequenceNumber, multiple
);
cleanOutstandingConfirms.handle(sequenceNumber, multiple);
});
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
outstandingConfirms.put(ch.getNextPublishSeqNo(), body);
ch.basicPublish("", queue, null, body.getBytes());
}
if (!waitUntil(Duration.ofSeconds(60), () -> outstandingConfirms.isEmpty())) {
throw new IllegalStateException("All messages could not be confirmed in 60 seconds");
}
long end = System.nanoTime();
System.out.format("Published %,d messages and handled confirms asynchronously in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis());
}
}
static boolean waitUntil(Duration timeout, BooleanSupplier condition) throws InterruptedException {
int waited = 0;
while (!condition.getAsBoolean() && waited < timeout.toMillis()) {
Thread.sleep(100L);
waited = +100;
}
return condition.getAsBoolean();
}
}
4. springboot整合
TODO
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。