1. 异步处理
2. 应用解耦
3. 流量削峰
4. 日志处理
暂不更新
1. simple(简单队列)
1.1 模型:
P:消息生产者
红色:队列
C:消息消费者
public class ConnectionUtil {
/**
* 获取MQ的连接
* @return
*/
public static Connection getConnection() throws IOException, TimeoutException {
// 定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置服务地址
factory.setHost("xxx.xxx.xx.xx");
// 设置服务端口
factory.setPort(xxxx);
// 设置库
factory.setVirtualHost("/xx");
// 用户名
factory.setUsername("xx");
// 密码
factory.setPassword("xx");
return factory.newConnection();
}
}
1.2.2 定义生产者:
public class Producer {
private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接
Connection connection = ConnectionUtil.getConnection();
// 从连接中获取通道
Channel channel = connection.createChannel();
// 创建队列声明,QUEUE_NAME指定向哪个队列发送消息
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg = "Hello Simple";
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
System.out.println("======= Producer msg :"+msg);
channel.close();
connection.close();
}
}
1.2.2 定义消费者:
public class Consumer {
private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接
Connection connection = ConnectionUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 创建队列声明,QUEUE_NAME指定向哪个队列发送消息
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("======= Consumer msg :"+msg);
}
};
// 监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
channel.close();
connection.close();
}
}
1.3 简单队列的不足:
耦合性高 生产者一一对应消费者(如果模型有多个消费者消费队列中的消息,就会出现问题)
队列名变更需要同时变更
2. work queues(工作队列)
2.1 模型:
工作队列:可以解决消息队列中消息积压
2.2 示例:
2.2.1:定义生产者
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 50; i++) {
String msg = "Hello Work_queue msg :"+i;
System.out.println(msg);
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
2.2.2:定义消费者 1
public class Consumer1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 定义通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("======= Consumer{1} msg :"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("Consumer{1} done");
}
}
};
boolean autoAck = true;
// 监听队列
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
2.2.3:定义消费者 2
public class Consumer2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 定义通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("======= Consumer{2} msg :"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("Consumer{2} done");
}
}
};
boolean autoAck = true;
// 监听队列
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
2.3 结果:
在Consumer 1睡眠1秒,Consumer 2睡眠2秒的情况下,Consumer 1 与 Consumer 2 处理的消息是一样的 ,
并且Consumer 1都为偶数,Consumer 2为奇数,这种方式就叫做轮询分发(round-robin)
3. 公平分发 fair dipatch
/*
* 在上一份示例上做改动
* 改动_1 :分别在生产者 消费者的代码中增加如下:
* 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
* 限制发送给同一个消费者不得超过一条数据或消息
* */
channel.basicQos(1);
/*
* 改动_2 :消费者的代码中增加如下:
* */
// 手动回执
channel.basicAck(envelope.getDeliveryTag(),false);
// 将自动应答改为手动
channel.basicConsume(QUEUE_NAME,false,consumer);
3.1 生产者:
public class Producer {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/*
* 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
* 限制发送给同一个消费者不得超过一条数据或消息
* */
int prefatchCount = 1;
channel.basicQos(prefatchCount);
for (int i = 0; i < 50; i++) {
String msg = "Hello Work_queue msg :"+i;
System.out.println(msg);
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
Thread.sleep(i*5);
}
channel.close();
connection.close();
}
}
3.2 消费者1:
public class Consumer1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 定义通道
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1); // 保证一次之分发一个
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("======= Consumer{1} msg :"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("Consumer{1} done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
// 自动应答 改为 false
boolean autoAck = false;
// 监听队列
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
3.3 消费者2:
public class Consumer2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 定义通道
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1); // 保证一次之分发一个
// 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("======= Consumer{2} msg :"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("Consumer{2} done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;
// 监听队列
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
3.4 结果: 在Consumer 1睡眠2秒,Consumer 2睡眠1秒的情况下,
Consumer 2消费的消息明显高于Consumer 1,这种方式也叫做能者多劳
**4. 消息应答与持久化 **
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
if(autoAck){
为自动确认模式:一旦rabbitMQ将消息分发给消费者,就会从内存中删除;这种情况下,如果杀死正在执行的消费者,就会丢失正在处理的消息,显然,这种时不安全的
}else {
该情况为手动模式:如果有一个消费者挂掉,就会交付给其他的消费者,rabbitMQ支持消息应答,消费者发送一个消息应答,告诉rabbitMQ这个消息已经处理完成,可以将消息删除,然后rabbitMQ再将消息删除。
}
- 消息应答默认是打开的,false
// 声明队列
boolean durable = false;
channel.queueDeclare(QUEUE_NAME,durable ,false,false,null);
注意:我们将程序中的durable = false改为true,是不可以的,尽管代码是正确的,也不会运行成功,因为我们已经定义了一个叫 test_work_queue 的队列,这个队列是未持久化的,rabbitMQ不允许重新定义一个不同参数已经存在的队列
办法:将test_work_queue的队列删除,或者,重新声明新的队列
5.publish / subscrobe
5.1 模型:
解读:
1、一个生产者,多个消费者
2、每个消费者都有自己的队列
3、生产者没有直接把消息发送到队列,而是发到了交换机,转发器Exchanges
4、每个队列都要绑定到交换机上
5、生产者发送的消息,经过交换机,到达队列,就能实现一条消息被多个消费者消费
业务场景:
|-----------------> 邮箱
1、注册 ---->|
|-----------------> 短信
5.2 生产者:
/**
* 订阅发布
* @author 马文达
* @date 2020/6/1 19:41
*/
public class Producer {
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout"); // 分发
// 发送消息
String msg = "Hello pb";
System.out.println("Producer msg" +msg);
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
channel.close();
connection.close();
}
}
交换机没有存储的能力,在rabbitMQ里边,只有队列有存储能留,因为这时候还没有队列绑定到这个交换机,造成数据丢失。
5.3:消费者 1 :
/**
* @author 马文达
* @date 2020/6/9 10:07
*/
public class Consumer1 {
// 声明队列名称
private static final String QUEUE_NAME = "test_queue-fanout_email";
// 声明交换机名称
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.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, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1]Consumer1 msg : " +msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,defaultConsumer);
}
}
5.4:消费者 2
public class Consumer2 {
private static final String QUEUE_NAME = "test_queue-fanout_sms";
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.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, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2]Consumer2 msg : " +msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,defaultConsumer);
}
}
4. routing
5. Topics
6. 手动和自动确认消息
7. 队列的持久化与非持久化
8. RabbitMQ的延迟队列