1 特点
RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统。它是一个高可用分布式集群技术,提供消息发布订阅,消息轨迹查询,定时(延时)消息,资源统计,监控报警等一系列消息云服务,是企业级互联网架构的核心产品。
2 使用场景
在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
比如 : 秒杀抢购
3 作用
3.1 削峰限流
场景:秒杀活动,一般因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用 : 可以控制活动人数,超过此一定阈值的订单直接丢弃;可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
3.2 应用解耦
场景:用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。这样当库存系统出现故障时,订单就会失败。
4 核心概念
Rabbitmq系统最核心的组件是Exchange和Queue,下图是系统简单的示意图。Exchange和Queue是在rabbitmq server(又叫做broker)端,producer和consumer在应用端。
Broker : 简单来说就是消息队列服务器实体。
Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
它提供了FIFO的处理机制,具有缓存消息的能力。rabbitmq中,队列消息可以设置为持久化,临时或者自动删除。
1)设置为持久化的队列,queue中的消息会在server本地硬盘存储一份,防止系统crash,数据丢失;
2)设置为临时队列,queue中的数据在系统重启之后就会丢失;
3) 设置为自动删除的队列,当不存在用户连接到server,队列中的数据会被自动删除;
Exchange : 消息交换机,它指定消息按什么规则,路由到哪个队列。
Rabbitmq中,producer不是通过信道直接将消息发送给Queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,producer在传递消息的时候,会传递一个routing key,Exchange会根据这个 routing key 按照特定的路由算法,将消息路由给指定的Queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。
Exchange 有四种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别:
1) direct : 路由模式的交换器,Exchange会将消息发送到完全匹配 routing key 的Queue
2) fanout : 广播是式交换器(发布/订阅模式),不管消息的 routing key设置为什么,Exchange都会将消息转发给所有绑定在其上的Queue。
3) topic : 主题交换器,工作方式类似于组播,Exchange会将消息转发到和 routing key匹配模式相同的所有队列。比如:routing key 为user.stock的Message会转发给绑定匹配模式为 * .stock,user.stock, * . * 和#.user.stock.#的队列。( * 表是匹配一个任意词组,#表示匹配0个或多个词组)
4) headers : 消息体的header匹配(ignore)。
Binding : 绑定,它的作用就是根据binding key把queue绑定到对应的exchange。 Exchange 和Queue的绑定可以是多对多的关系
virtual host :虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。(对应于数据库里的库)
producer:指的是消息生产者。
consumer :消息的消费者。
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
5 通信过程
6 实战
RabbitMQ 有五种工作模式,这也是实际使用RabbitMQ需要重点关注的,下面以实例来分析:
6.1 简单队列
一个生产者对应一个消费者
发送者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class client {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
channel.basicPublish("",QUEUE_NAME, null, ("发送消息" + i).getBytes());
}
//关闭连接
channel.close();
connection.close();
}
}
消费者:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
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 {
System.out.println("消费者1收到内容是:" + new String(body));
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
6.2 work模式
一个生产者对应多个消费者,每个消息只能被一个消费者获得。
生产者:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class client {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
String message = "hello rabbitmq "+i;
//5、发布消息
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("[x] Sent'"+message+"'");
//模拟发送消息延时,便于演示多个消费者竞争接受消息
Thread.sleep(i*10);
}
//关闭连接
channel.close();
connection.close();
}
}
消费者 1:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.0.1");
Connection connection = factory.newConnection();
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 {
System.out.println("消费者1收到内容是:" + new String(body));
//消费者1接收一条消息后休眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer2 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
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 {
System.out.println("消费者2收到内容是:" + new String(body));
//消费者1接收一条消息后休眠1000毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
6.3 发布订阅模式(广播)
一个消费者将消息首先发送到交换机,交换机将消息发送到绑定在其上的所有队列。
PS:如果消息发送到没有队列绑定的交换器时,消息将丢失,因为交换器没有存储消息的能力,消息只能存储在队列中。
生产者:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class client {
private final static String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//定义一个交换机,类型是fanout,也就是发布订阅模式
//发布订阅模式的,因为消息是先发送到交换机中,而交换机是没有保存功能的,所以如果没有消费者,消息会丢失
channel.basicPublish(EXCHANGE_NAME, "", null, "发布订阅模式的消息".getBytes());
//关闭连接
channel.close();
connection.close();
}
}
消费者1:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer {
private static final String QUEUE_NAME = "fanout_queue_1";
private final static String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1收到内容是:" + new String(body));
//消费者1接收一条消息后休眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer2 {
private static final String QUEUE_NAME = "fanout_queue_2";
private final static String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2收到内容是:" + new String(body));
//消费者1接收一条消息后休眠1000毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
6.4 路由模式:
生产者将消息发送到direct交换机,在绑定队列到交换机的时候有一个路由key,生产者发送的消息会指定一个路由key,那么消息只会发送到相应key相同的队列。
生产者:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class client {
private final static String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");//定于路由模式的交换机
channel.basicPublish(EXCHANGE_NAME,"orange", null, "路由模式消息1".getBytes());
channel.basicPublish(EXCHANGE_NAME,"black", null, "路由模式消息2".getBytes());
channel.basicPublish(EXCHANGE_NAME,"green", null, "路由模式消息3".getBytes());
//关闭连接
channel.close();
connection.close();
}
}
消费者1:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer {
private static final String QUEUE_NAME = "direct_queue_1";
private final static String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "orange");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1收到内容是:" + new String(body));
//消费者1接收一条消息后休眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer2 {
private static final String QUEUE_NAME = "direct_queue_2";
private final static String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "black");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "green");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2收到内容是:" + new String(body));
//消费者1接收一条消息后休眠1000毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
6.5 topic模式
路由模式是根据路由key进行完整的匹配(完全相等才发送消息),这里的通配符模式通俗的来讲就是模糊匹配。
符号“#”表示匹配一个或多个词,符号“*”表示匹配一个词。
生产者:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class client {
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");//定于topic模式的交换机
channel.basicPublish(EXCHANGE_NAME,"key.1", null, "topic 模式消息1".getBytes());
channel.basicPublish(EXCHANGE_NAME,"key.1.2", null, "topic 模式消息key.1.2".getBytes());
channel.basicPublish(EXCHANGE_NAME,"abc.1.2", null, "topic 模式消息abc.1.2".getBytes());
//关闭连接
channel.close();
connection.close();
}
}
消费者1:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer {
private static final String QUEUE_NAME = "topic_queue_1";
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
//参数3 标记,绑定到交换机的时候会指定一个标记,只有和它一样的标记的消息才会被当前消费者收到
//topic 模式
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.*");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "abc.#");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1收到内容是:" + new String(body));
//消费者1接收一条消息后休眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2:
import com.rabbitmq.client.*;
import java.io.IOException;
public class consumer2 {
private static final String QUEUE_NAME = "topic_queue_2";
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//绑定队列到交换机
//参数3 标记,绑定到交换机的时候会指定一个标记,只有和它一样的标记的消息才会被当前消费者收到
//topic 模式
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.#");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "abc.#");
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我们发生消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2收到内容是:" + new String(body));
//消费者1接收一条消息后休眠1000毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//确认
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false为确认收到消息,true为拒绝收到消息
}
};
/*
注册消费者,参数2.代表我们收到消息后需要手动告诉服务器:我收到消息了
true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
发送消息,直到该消费者反馈。
*/
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}