RabbitMQ笔记
主流MQ简单了解
衡量标准
- 服务性能
- 数据存储
- 集群架构
ActiveMQ
适用于中小型企业,但在高并发情况下效率较低,不适用与并发
Kafka
高吞吐、速度快,但不支持事务,不能保证消息的可靠性
一般用于日志传输,不适用于事务场景
RocketMQ
阿里开发的商业性MQ,商业版收费
高吞吐、高可用
适用于大规模分布式
RabbitMQ
性能略差于RocketMQ,高于ActiveMQ,且支持大规模分布式,可靠性高,稳定性好
基于AMQP协议开发
AMQP核心概念
- Server:又称Broker,接收客户端连接,实现AMQP实体服务
- Connection:连接,应用程序与Broker的网络连接
- Channel:网络信道,几乎所有操作都在Channel中进行操作,是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务
- Message:消息,服务器和应用程序之间传送的数据,又Properties和Body组成。Properties可以对消息进行修饰,如延迟时间,消息优先级等,Body中就是存放消息内容
包含几种属性:
correlationId:消息唯一ID
deliverMode:2为持久化消息
expiration:过期时间
headers:消息头,可以存放自定义消息
- Virtual host:虚拟地址,用于逻辑隔离,最上层的消息路由。一个VH里可以有若干的Exchange和Queue,同一个VH里不能有相同名称的Exchange和Queue
- Exchange:交换机,接收消息,根据路由键转发到绑定的队列上
包含几种属性:
Name:交换机名称
Type:交换机类型direct、topic、fanout、headers
Durability:是否持久化
Auto Delete:当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
Internal:当前Exchange是否用于RabbitMQ内部使用,默认false
Arguments:扩展参数,扩展AMQP协议自定义化使用
- Binding:Exchange和Queue之间的虚拟连接,Binding中可以包含routing key
- Routing key:一个路由规则,虚拟机可用它来确定如何路由一个特定消息
- Queue:消息队列,保存消息并把它们转发给消费者
包含几种属性:
Name:队列名称
Durability:是否持久化
Auto Delete:当最后一个监听删除后,自动删除该Queue
基本命令
- rabbitmqctl(控制相关,队列/交换机/集群等操作)
- rabbitmq-plugins(rabbitmq插件)
- rabbitmq-server(服务相关操作,如启动,停止)
消息模式
普通队列
//初始化连接
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
//服务ip
String HOST = "127.0.0.1";
//默认端口号
Integer PORT = 5672;
//虚拟主机名称
String VIRTUALHOST = "/vhost_z";
//用户名
String USERNAME = "user_z";
//密码
String PASSWORD = "123";
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
connectionFactory.setVirtualHost(VIRTUALHOST);
Connection connection = connectionFactory.newConnection();
return connection;
}
//生产者
public static void main(String[] args) {
try {
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.获取通道
Channel channel = connection.createChannel();
//3.声明队列 可以在消费者端声明
//channel.queueDeclare("test_queue",false,false,false,null);
String msg = "sent message!";
//4.发送消息 如果不声明交换机 那么消息会自动发到amqp的默认交换机上
// 默认交换机会根据routingkey去找匹配的队列
channel.basicPublish("","test_queue",null,msg.getBytes());
//5.关闭通道和连接
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//消费者
public static void main(String[] args) {
try {
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("test_queue",false,false,false,null);
//消费者对象
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("consumer1===="+msg);
}
};
//4.监听队列
channel.basicConsume("test_queue",false,defaultConsumer);
} catch (Exception e) {
e.printStackTrace();
}
}
工作队列
QOS(服务质量保证),在非自动确认消息的前提下(一定要手动确认消息),如果一定数目的消息未被确认前,不进行消费新的消息
//生产者
public static void main(String[] args) {
try {
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.获取通道
Channel channel = connection.createChannel();
//是否持久化 true 是
boolean flag = true;
//3.声明队列
channel.queueDeclare("test_queue",flag,false,false,null);
for (int i=0;i<50;i++){
String msg = "sent message i= "+i+"!";
//4.发送消息
channel.basicPublish("","test_queue",null,msg.getBytes());
}
//5.关闭通道和连接
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//消费者1
public static void main(String[] args) {
try {
Thread.sleep(2000);
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("test_queue",true,false,false,null);
//保证只发一个消息 需要手动应答 默认工作队列使用轮询,给连个消费者均分消息
//若一个消费者消费的快,想提高效率 那么可以用手动应答
//修改basicQos限制每次消费1条消息 谁先消费完 就应答 再让生产者发送一条消息
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("consumer1===="+msg);
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//4.监听队列
channel.basicConsume("test_queue",false,defaultConsumer);
} catch (Exception e) {
e.printStackTrace();
}
}
//消费者2
public static void main(String[] args) {
try {
Thread.sleep(1000);
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.定义消费者
channel.queueDeclare("test_queue",false,false,false,null);
//保证只发一个消息
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("consumer2===="+msg);
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//4.监听队列
channel.basicConsume("test_queue",true,defaultConsumer);
} catch (Exception e) {
e.printStackTrace();
}
}
消息应答和消息持久化
在自动应答模式下,消息发送完后消息就会删除,但此时消费者服务宕机,消息就会丢失
手动应答模式,在消息消费完,应答之后消息才会删除,但内存是断电清空,为了防止MQ服务器宕机,可以将消息持久化,将队列也声明为持久化(已声明的队列无法再次修改属性)
//手动应答 将第二个参数设为false
channel.basicConsume("test_queue",flase,defaultConsumer);
//生产者
public static void main(String[] args) {
try {
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.获取通道
Channel channel = connection.createChannel();
String exchangeName = "test_ack_exchange";
String routingKey = "ack.info";
//5.发送消息
for(int i = 0;i<5;i++){
Map<String,Object> headers = new HashMap();
String msg = "sent ack message is info" + i;
headers.put("num",i);
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder().
deliveryMode(2).//消息持久化设置
contentEncoding("utf-8").
headers(headers).
build();
channel.basicPublish(exchangeName,routingKey,true,basicProperties,msg.getBytes());
}
//6.关闭通道和连接
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//消费者
public static void main(String[] args) {
try {
//1.获取连接
Connection connection = ConnectionUtils.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
String exchangeName = "test_ack_exchange";
String queueName = "test_ack_queue";
String routingKey = "ack.#";
//3.声明队列和交换机
channel.exchangeDeclare(exchangeName,"topic",true,false,false,null);
channel.queueDeclare(queueName,false,false,false,null);
//4.绑定队列和路由key
channel.queueBind(queueName,exchangeName,routingKey);
//保证只发一个消息
//第一个参数指消息总数量
//第二个参数时每次消费次数
//第三个参数 是否将整个channel都设置成该配置
channel.basicQos(0,1,false);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
Integer num = (Integer) properties.getHeaders().get("num");
System.out.println("body="+new String(body,"utf-8") +" i="+num);
String msg = new String(body,"utf-8");
if(num.equals(0