1.前言
2.RabbitMQ-Plugins插件安装
3.简单队列
4.work queues 工作队列 轮询分发
5.公平分发
6.发布订阅模式
7.路由模式
8.Topic exchange
9.rabbitMQ的消息确认机制(事物+confirm)
10.Spring集合rabbitmq
1.前言
1.消息列队解决了什么问题??
异步处理
应用解耦
流量削峰(高并发:抢红包、秒杀)
日志处理……
2.java操作RabbitMQ
3.Spring AMQP协议
2.RabbitMQ-Plugins插件安装
打开
进⼊入rabbitmq的
sbin⽬目录(我的⽬目录是:C:\Program Files\RabbitMQ
Server\rabbitmq_server-3.7.2\sbin),输⼊入:
rabbitmq-plugins enable rabbitmq_management
命令,稍等会会发现出现plugins安装成功的提示
打开浏览器进入 http://localhost:15672/ 用户名guest 密码guest
virtual hosts 相当于mysql 的db
一般以/开头
我们需要对用户进行授权
com.rabbitmq
amqp-client
3.4.1
org.slf4j
slf4j-log4j12
1.7.5
log4j
log4j
1.2.17
junit
junit
4.11
3.简单队列
P:消息的生产者-->队列-->消费者
连接rabbitmq
public static Connection getConnections() throws IOException{
//定义一个连接工厂
ConnectionFactory factory=new ConnectionFactory();
//设置服务器ַ
factory.setHost("127.0.0.1");
// AMQP 5672
factory.setPort(5672);
//vhost
factory.setVirtualHost("/vhost_mmr");
//用户名
factory.setUsername("huyanglin");
//密码
factory.setPassword("huyanglin");
Connection newConnection = factory.newConnection();
return newConnection;
}
发送消息
public static void main(String[] args) throws IOException {
//1.获取链接
Connection connections = ConnectionUtils.getConnections();
//2.创建通道
Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msg="hello simple";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println(msg);
channel.close();
connections.close();
}
监听(接收消息)
private static final String QUEUE_NAME="test_simple_queue";
public static void main(String[] args) throws IOException {
//建立链接、创建通道
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME, false, false, false, null) ;
//定义消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
//获取到达的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg=new String(body,"utf-8");
System.out.println("取数据"+msg);
};
};
//消费队列
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
简单队列的不足
4.work queues 工作队列 轮询分发
为什么会出现工作队列
simple队列 是一一对应的,实际开发中,生产者发送消息是毫不费力的,而消费者一般是与业务结合的,消费者接收到消息后就需要处理,可能需要花费时间。这时候队列就会挤压着很多消息。
生产者:
public class Send {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, InterruptedException {
// 获取链接
Connection connections = ConnectionUtils.getConnections();
// 获取channel
Channel channel = connections.createChannel();
//
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 50; i++) {
String msg = "workQueue" + i;
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("发送的消息" + msg);
Thread.sleep(i + 20);
}
channel.close();
connections.close();
}
}
消费者1
public class Recv1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
// 获取链接
Connection connections = ConnectionUtils.getConnections();
// 获取通道
Channel channel = connections.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 一旦有消息到达,就触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者1" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
}
}
};
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
消费者2
public class Recv2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
// 获取链接
Connection connections = ConnectionUtils.getConnections();
// 获取通道
Channel channel = connections.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 一旦有消息到达,就触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("消费者2" + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done");
}
}
};
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
现象:消费者1和消费者2处理的数据消息是一样的,
消费者1都是偶数
消费者2都是奇数
这种方式叫 轮询分发(round-robin)结果是 不管谁忙或者谁闲,任务消息都是一边一个轮询发
5.公平分发
生产者:在消费者返回确认消息之前,只分发一个消息
/**
* 每个消费者发送确认消息之前,消息队列不发送下一个消息,一次只处理一个消息
*/
int prefecthCount=1;
channel.basicQos(prefecthCount);
消费者 1.确认每次只收到一个消息
2.每次处理完消息要返回确认信息
3.自动应答 关闭
现象:消费者1处理得比消费者2多(能者多劳)
boolean autoAck = false;//自动应答=false
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
boolean autoAck = true;
自动确认模式,一旦rabbitmq将消息分发给消费者,就会从内存中删除
这种情况,如果杀死正在执行任务的消费者,则会丢失正在处理的消息
boolean autoAck = false;
手动模式 ,如果有一个消费者挂掉,就会交付给其他消费者。rabbitMQ支出消息应答,消费者处理完消息后,给abbitmq发送确认消息,rabbitmq收到后就会删除内存中的消息
消息应答默认是打开的==>false
消息的持久化
boolean durable=false;//持久化
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
我们将程序中的 boolean durable=false 直接改成true是不可以的,因为test_work_queue队列已经定义成未持久化的队列,rabbitmq不允许重新定义已经存在的队列。
6.发布订阅模式
X:交换机、转发器
解读:1.一个生产者,多个消费者
2.每一个消费者都有自己的队列
3.生产者没有直接将消息发送到队列。而是发送到了交换机
4.每个队列都要绑定到交换机上
5.生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消费
生产者:
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");// 分发
String msg = "hello ps";
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
System.out.println("send " + msg);
channel.close();
connections.close();
}
}
这时候消息哪去了??
消息丢失了!因为交换机没有存储能力,在rabbitMQ里面,只有队列有存储能力
消费者1:
public class Recv1 {
private static final String EXCHANGE_NAME = "test_exchange_fanout";
private static final String QUEUE_NAME = "email_queue";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("recv11 "+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
channel.basicConsume(QUEUE_NAME, false,defaultConsumer);
}
}
消费者2
public class Recv2 {
private static final String EXCHANGE_NAME = "test_exchange_fanout";
private static final String QUEUE_NAME = "massage_queue";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("recv22 "+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
channel.basicConsume(QUEUE_NAME, false,defaultConsumer);
}
}
Exchange(交换机 转发器)
一方面是接收生产者的消息,一方面将消息推送到各个消费者的队列
Fanout(不处理路由键)
Direct (处理路由键)
7.路由模式
模型:
生产者:
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String msg = new String("routing test");
String routingKey="info";
System.out.println("send " + msg);
channel.basicPublish(EXCHANGE_NAME,routingKey, null, msg.getBytes());
channel.close();
connections.close();
}
}
消费者1:
public class Recv1 {
private static final String QUEUE_NAME="erro_info_manage";
private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg=new String(body,"utf-8");
System.out.println("recv1"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME,false, defaultConsumer);
}
}
消费者2:
public class Recv2 {
private static final String QUEUE_NAME="all_info_manage";
private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "info");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "warning");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg=new String(body,"utf-8");
System.out.println("recv2 "+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME,false, defaultConsumer);
}
}
8.Topic exchange
将路由与某模式进行匹配
#-------匹配一个或者多个
*--------匹配一个
生产者:
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String msg = new String("商品-----");
String routingKey="goods.del";
System.out.println("send --- " + msg);
channel.basicPublish(EXCHANGE_NAME,routingKey, null, msg.getBytes());
channel.close();
connections.close();
}
}
消费者1:
public class Recv1 {
private static final String QUEUE_NAME = "queue_topic_1";
private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.add");
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("recv1 " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
}
}
消费者2:
public class Recv2 {
private static final String QUEUE_NAME = "queue_topic_2";
private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException {
Connection connections = ConnectionUtils.getConnections();
final Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.*");
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("recv2 " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
}
}
9.rabbitMQ的消息确认机制(事物+confirm)
在rabbitmq中,我们可以通过持久化数据,解决rabbitmq的服务器异常 的数据丢失问题
问题:生产者将消息发送出去后,是否到达rabbitmq服务器?默认的情况下是不知道的
两种方式解决:
1.AMQP 实现了事物机制
2.confirm 模式
AMQP 事物机制
txSelect 用户将当前channel设置成transation模式
txCommit 用于提交事物
txRollback 回滚事物
这种模式比较耗时,降低了rabbitmq的吞吐量
confirm模式
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理;
confirm模式最大的好处是 异步
rabbitmq如果服务器异常或者崩溃,就会发送一个nack消息
开启confirm模式
channel.confirmSelect();
编程模式
1.普通 发一条 waitForConfirm()
2.批量 发一批 waitForConfirms()
3.异步confirm模式 提供一个回调方法
confirm单条
confirm多条
public class SendMany {
private static final String QUEUE_NAME = "confirm_test_1";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 生产者调用confirmSelect 将channel设置为confirm模式
channel.confirmSelect();
String msg = "hello confirm msg";
System.out.println("send--confirm---" + msg);
for (int i = 0; i < 10; i++) {
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
if (!channel.waitForConfirms()) {
System.out.println("massage send failed");
} else {
System.out.println("massage send ok");
}
connections.close();
}
}
异步confirm模式
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。实际上,SDK中的waitForConfirms()方法也是通过SortedSet维护消息序号的。
public class Send3 {
private static final String QUEUE_NAME = "confirm_test_1";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connections = ConnectionUtils.getConnections();
Channel channel = connections.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 生产者调用confirmSelect 将channel设置为confirm模式
channel.confirmSelect();
// 存放未确认的消息标识
final SortedSetconfirmSet = Collections.synchronizedNavigableSet(new TreeSet ());
// 添加通道监听
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliverTag, boolean mutiple) throws IOException {
if (mutiple) {
System.out.println("---handleAck-------mutiple----");
confirmSet.headSet(deliverTag + 1).clear();
} else {
System.out.println("---handleAck-------mutiple---false");
confirmSet.remove(deliverTag);
}
}
public void handleNack(long deliverTag, boolean mutiple) throws IOException {
if (mutiple) {
System.out.println("---handleNack-------mutiple----");
confirmSet.headSet(deliverTag + 1).clear();
} else {
System.out.println("---handleNack-------mutiple---false");
confirmSet.remove(deliverTag);
}
}
});
String msg = "hello confirm msg";
while (true) {
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
confirmSet.add(seqNo);
}
}
}
10.Spring集合rabbitmq
配置:
生产者:
消费者: