文章目录
安装跳过,不会的请百度,这篇文章主要记录理论以及编码
什么是rabbitmq
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
基本概念
- message: 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
- Publisher: 消息的生产者,也是一个向交换器发布消息的客户端应用程序。
- Exchange: 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
- Binding: 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
- Queue: 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
- Connection: 网络连接,比如一个TCP连接。
- Channel: 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
- Consumer: 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
- Virtual Host: 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
- Broker: 表示消息队列服务器实体。
不与SpringBoot整合的写法
先写一个工具类,获取连接等
package cn.com.laoli.rabitmq.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtils {
/**
* 获取mq的连接
*
* @return
*/
public static Connection getConnection() throws IOException, TimeoutException {
//定义一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//设置端口号 AMQP协议
factory.setPort(5672);
//设置vhost
factory.setVirtualHost("你设置的vhost");
//用户名
factory.setUsername("你的用户名");
//密码
factory.setPassword("你的密码");
return factory.newConnection();
}
}
简单队列模式
模型
- p:消息的生产者
- 红色的: 队列
- c: 消费者
生产者生产消息
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String QUEUE_NAME = "test_sample_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取一个连接
Connection connection = ConnectionUtils.getConnection();
//创建一个通道
Channel channel = connection.createChannel();
//创建队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg = "hello simple !";
//发布
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
System.out.println("------send msg:"+msg);
channel.close();
connection.close();
}
}
消费者接受消息
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeoutException;
public class Receive {
private static final String QUEUE_NAME = "test_sample_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
/*//老版本 定义队列消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//监听队列
channel.basicConsume(QUEUE_NAME,true,queueingConsumer);
while (true){
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("[recv] msg :"+msg);
}*/
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
//事件模型,一旦有消息进入就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println("new api recv:" + msg);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
简单消息队列不足
耦合性高,生产者一一对应消费者(如果我想有多个消费者消费队列中的消息,这时就不行了)
队列名变更,这时候得同时变更
Work queues 工作队列之轮询分发
模型
为什么会出现工作队列
Simple队列 是一一对应的,且实际开发,生产者发送消息是毫不费力的,而消费者一般是要跟业务相结合的。消费者接收到消息之后就需要处理 可能需要花费时间,这时候队列就积压了好多消息
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String QUEUE__NAME = "test_work_queue";
/** |----c1
* p----Queue-----
* |----c2
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE__NAME,false,false,false,null);
for (int i=0;i<50;i++){
String msg = "hello "+i;
channel.basicPublish("",QUEUE__NAME,null,msg.getBytes());
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
消费者1
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Receive {
private static final String QUEUE__NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE__NAME,false,false,false,null);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv1 msg:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("1 done");
}
}
};
//后续会说明这个是什么
boolean autoAck = true;
//监听队列
channel.basicConsume(QUEUE__NAME,autoAck,consumer);
}
}
消费者2
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Receive2 {
private static final String QUEUE__NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE__NAME,false,false,false,null);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv2 msg:"+msg);
try {
Thread.sleep(1000);
} 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)结果就是不管谁忙或闲,都不会多给一个
任务消息总是你一个我一个
Work queues 工作队列之公平分发
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String QUEUE__NAME = "test_work_queue";
/** |----c1
* p----Queue-----
* |----c2
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE__NAME,true,false,false,null);
/**
* 每个消费者 发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
*
* 限制发送给同意消费者不超过一条
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
for (int i=0;i<50;i++){
String msg = "hello "+i;
channel.basicPublish("",QUEUE__NAME,null,msg.getBytes());
System.out.println("发送"+i+":"+msg);
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
消费者1
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Receive {
private static final String QUEUE__NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE__NAME,false,false,false,null);
//保证一次只发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv1 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,consumer);
}
}
消费者2
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Receive2 {
private static final String QUEUE__NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE__NAME,false,false,false,null);
//保证一次只发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv2 msg:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("2 done");
//第一个参数是一个标识 表示已经收到消息 手动回值
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//自动应答
boolean autoAck = false;
channel.basicConsume(QUEUE__NAME,autoAck,consumer);
}
}
现象
消费者2处理的比消费者1多 能者多劳 公平分发
消息应答
boolean autoAck = false;
channel.basicConsume(QUEUE__NAME,autoAck,consumer);
autoAck为false时
手动模式 如果有一个消费者挂掉 就会交付给其他消费者
rabbitmq支持消息应答,消费者发送一个消息应答告诉rabbitmq这个消息我已经处理完成,可以删了,然后rabbitmq就删除内存中的数据
autoAck为true时
自动确认模式 表示一旦rabbitmq将消息发送给消费者,消息就会从内存中删除
如果rabbitmq挂掉了 消息可能会丢失
如果杀死正在执行的消费者,就会丢失正在处理的消息
消息应答默认是打开的 false
问题 如果rabbitmq挂了怎么办 我们的任务仍然会丢失
消息持久化
//声明队列
boolean durable = false
channel.queueDeclare(QUEUE__NAME,durable,false,false,null);
注意
我们将程序中的durable=false改为true是不可以的,尽管代码是正确的,也不会运行成功!!!
因为我们已经定义了一个叫test_work_queue,这个queue是未持久化的,tabbitmq不允许重新定以(不同参数)一个已存在的队列
publish_subscribe订阅模式
模型
- 一个生产者,多个消费者
- 每一个消费者都有自己的队列
- 生产者没有直接把消息发送到队列中,而是发到了exchange(交换器)
- 每个队列都要绑定到交换器上
- 生产者发送的消息经过交换器到达队列吗,就能实现一个消息被多个消费者消费
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String EXCHANGE_NAME="test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//通道
Channel channel = connection.createChannel();
//声明交换机\
//分发类型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//发送消息
String msg = "hello_ps";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
System.out.println("send:"+msg);
channel.close();
connection.createChannel();
}
}
消费者1
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv {
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 = ConnectionUtils.getConnection();
//获取通道channel
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证一次只发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv1 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,consumer);
}
}
消费者2
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv2 {
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 = ConnectionUtils.getConnection();
//获取通道channel
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证一次只发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义一个消费者
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv2 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,consumer);
}
}
Exchange(交换机/转发器)
- 一方面接受生产者的消息
- 另一方面是向队列推送消息
Fanout(不处理路由键)
只要是绑定了的都能收到消息
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Direct(处理路由键)
发送的时候需要带一个key,队列也需要带一个key,如果相匹配就会把消息转到对应的队列里
Topic Exhange(主题模式)
将路由键和某个模式匹配
- #匹配一个或者多个
- *匹配一个
路由模式
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//声明exchange 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
String msg = "hello Direct";
String routingKey = "info";
channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
System.out.println("send:"+msg);
channel.close();
connection.close();
}
}
消费者1
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv {
private static final String EXCHANGE_NAME = "test_exchange_direct";
private static final String QUEUE_NAME = "test_queue_direct1";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
//每次只发一个消息
channel.basicQos(1);
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv1 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,consumer);
}
}
消费者2
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv2 {
private static final String EXCHANGE_NAME = "test_exchange_direct";
private static final String QUEUE_NAME = "test_queue_direct2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");
//每次只发一个消息
channel.basicQos(1);
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv2 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,consumer);
}
}
Topic
#匹配多个,*匹配一个
商品crud
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String msg = "商品.....";
System.out.println("send:"+msg);
channel.basicPublish(EXCHANGE_NAME,"goods.add",null,msg.getBytes());
channel.close();
connection.close();
}
}
消费者1
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv {
private static final String EXCHANGE_NAME = "test_exchange_topic";
private static final String QUEUE_NAME = "test_queue_topic1";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
//每次只发一个消息
channel.basicQos(1);
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv1 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,consumer);
}
}
消费者2
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv2 {
private static final String EXCHANGE_NAME = "test_exchange_topic";
private static final String QUEUE_NAME = "test_queue_topic2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
//每次只发一个消息
channel.basicQos(1);
Consumer consumer =new DefaultConsumer(channel){
//消息到达就会触发这个方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body,"utf-8");
System.out.println("recv2 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,consumer);
}
}
Rabbitmq的消息确认机制(事务+confirm)
在rabbitmq中,我们可以通过持久化数据,解决rabbitmq服务器异常的数据丢失问题
问题
生产者将消息发送出去之后吗,消息到底有没有到达rabbitmq服务器,默认情况是不知道的
两种方式
- AMQP协议实现了事务机制
- Confirm模式
事务机制
- TxSelect:用于将当前channel设置成transtation模式
- TxCommit:用于提交数据事务
- TxRollback:回滚事务
- 缺点:降低了rabbitmq的吞吐量
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send {
private static final String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg = "hello";
try {
channel.txSelect();
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
int xx = 1/0;
channel.txCommit();
}catch (Exception e){
channel.txRollback();
System.out.println("seng msg rollback");
}
channel.close();
connection.createChannel();
}
}
消费者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv {
private static final String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("rev msg :"+new String(body,"utf-8"));
}
});
}
}
confirm模式
生产者端confirm的实现原理
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理
confirm最大的好处在于他是异步
串行模式-单条
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Send1 {
private static final String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//生产者调用 confirmSelect 将channel设为confirm模式 注意得是一个新队列如果之前开了事务模式在开confirm模式会报错 rabbitmq不允许
channel.confirmSelect();
String msg = "hello confir message";
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
if (!channel.waitForConfirms()){
System.out.println("消息发送失败");
}else{
System.out.println("发送成功");
}
channel.close();
connection.close();
}
}
消费者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Recv {
private static final String QUEUE_NAME = "test_queue_confirm3";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("rev msg :"+new String(body,"utf-8"));
}
});
}
}
串行模式-批量
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author: LiDeLin 18234780819@163.com
* @description : 普通模式 多条 比单条效率高 但是如果有一条失败那么全部消息都会被返回
*/
public class Send2 {
private static final String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//生产者调用 confirmSelect 将channel设为confirm模式 注意得是一个新队列如果之前开了事务模式在开confirm模式会报错 rabbitmq不允许
channel.confirmSelect();
String msg = "hello confir message";
for (int i = 0; i<=9 ;i++){
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
if (!channel.waitForConfirms()){
System.out.println("消息发送失败");
}else{
System.out.println("发送成功");
}
channel.close();
connection.close();
}
}
消费者
同上
confirm异步
channel对象提供的ConfirmListener()回调方法只包含deliverTag(当前Channelfa发出的消息序列号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publishi一条数据,集合元素加1,每回调一次handleAck方法吗,unconfirm集合删掉相应的一条(multiple=false)或者多条(multiple=true)记录,从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构
生产者
import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;
public class Send3 {
private static final String QUEUE_NAME = "test_queue_confirm3";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//生产者调用confirmSelect 将channel设置为confirm模式
channel.confirmSelect();
//存放未确认的消息的标识
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
channel.addConfirmListener(new ConfirmListener() {
//没有问题的
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple){
System.out.println("handleAck----multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("handleAck----multiple false");
confirmSet.remove(deliveryTag);
}
}
//回值有问题的
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
if (multiple){
System.out.println("nak-----multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("nak-----multiple---false");
confirmSet.remove(deliveryTag);
}
}
});
String msgStr = "sssss";
while (true){
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("",QUEUE_NAME,null,msgStr.getBytes());
confirmSet.add(seqNo);
}
}
}
消费者
同上
与SpringBoot整合的写法
yml配置
server:
port: 8091
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 开启confirm
publisher-confirms: true
listener:
simple:
# 手动ack
acknowledge-mode: manual
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
前提
注入
@Autowired
private RabbitTemplate rabbitTemplate;
这个是springboot提供的
简单模型
生产者
/**
* 简单模型
*/
@Test
public void test(){
rabbitTemplate.convertAndSend("hello","yue xin 10k");
}
消费者
@Component
/**
* 简单模式
* queuesToDeclare:没有队列就声明一个队列
* queue的属性可以直接设置,布尔值是字符串,默认非持久化,非独占,不是自动删除的队列
*/
//@RabbitListener(queuesToDeclare = @Queue(value = "hello",declare = "true",autoDelete = "true"))
@RabbitListener(queuesToDeclare = @Queue("hello"))
@Slf4j
public class HelloConsumer {
/**
* 取出消息后的回调函数
* @param message
*/
@RabbitHandler
public void receive(String message){
log.info("收到的消息:{}",message);
}
}
工作队列
生产者
/**
* 工作队列,默认是轮询分发,开启自动应答即可能者多劳,配置文件中开启,当前已开启
*/
@Test
public void testWork(){
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("work","work模型");
}
}
消费者
@Component
@Slf4j
public class WorkConsumer {
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive1(String message) throws InterruptedException {
// Thread.sleep(2000);
System.out.println("消费者1");
log.info("消费者1:------------------>{}",message);
}
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive2(String message) throws InterruptedException {
// Thread.sleep(1000);
System.out.println("消费者2");
}
}
fanout广播
生产者
/**
* fanout广播
*/
@Test
public void testFanout(){
rabbitTemplate.convertAndSend("logs","","Fanout的模型传递发送的消息");
}
消费者
@Component
public class FanoutConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "logs",type = "fanout") //绑定的交换机
)
})
public void receive1(String message){
System.out.println("message1"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "logs",type = "fanout") //绑定的交换机
)
})
public void receive2(String message){
System.out.println("message2"+message);
}
}
route路由模式
生产者
/**
* route 路由模式
*/
@Test
public void testRoute(){
rabbitTemplate.convertAndSend("directs","info","发送info的key的路由信息");
}
消费者
@Component
public class RouteConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "directs",type = "direct"), //绑定的交换机
key = {"info","error","debug"} //路由key
)
})
public void receive1(String message){
System.out.println("message1"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "directs",type = "direct"), //绑定的交换机
key = {"error"}
)
})
public void receive2(String message){
System.out.println("message2"+message);
}
}
动态路由/广播模式
生产者
/**
* topic 动态路由/订阅模式
*/
@Test
public void testTopic(){
rabbitTemplate.convertAndSend("topics","user.save","user.save 路由消息");
}
消费者
@Component
public class TopicConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "topics",type = "topic"), //绑定的交换机
key = {"user.save","user.*"} //路由key
)
})
public void receive1(String message){
System.out.println("message1"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//创建临时队列
exchange = @Exchange(value = "topics",type = "topic"), //绑定的交换机
key = {"order.#","product.#","user.*"}
)
})
public void receive2(String message){
System.out.println("message2"+message);
}
}