RabbitMQ是一个基于AMQP协议的高级消息中间件,它主要的技术特点是可用性,安全性,集群,多协议支持,可视化的客户端,活跃的社区。
Broker(消息代理) : 实际上就是消息服务器实体。
Exchange(交换机) : 用来发送消息的AMQP实体,它指定消息按什么路由规则,路由到哪个队列。
Queue(消息队列) :每个消息都会被投入到一个或多个队列。
Binding(绑定) : 它的作用就是把交换机(Exchange)和队列(Queue)按照路由规则绑定起来。
Routing Key(路由关键字) :路交换机(Exchange)根据这个关键字进行消息投递。
vhost(虚拟主机) : 虚拟主机,一个消息代理(Broker)里可以开设多个虚拟主机(vhost),用作不同用户的权限分离。
Connection(连接) :AMQP连接通常是长连接,Producer和Consumer都是通过TCP连接到RabbitMQ Server的。
Channel(通道) : AMQP通过通道(channels)来处理多连接,可以把通道理解成共享一个TCP连接的多个轻量化连接。
在RabbitMQ中消息并不会被直接投递到队列中去,而是有生产者将消息发布到交换机中,交换机和一个或多个队列绑定,通过不同的路由规则将消息路由到队列中去,供消费者消费,RabbitMQ中共提供了四种类型交换机,交换机可以有两个状态:持久(durable)、暂存(transient)。持久化的交换机会在消息代理(broker)重启后依旧存在,而暂存的交换机则不会(它们需要在代理再次上线后重新被声明)。
Publisher:
// 路由关键字
private static final String[] routingKeys = new String[] {"info", "warn", "error",""debug};
// 声明一个交换机并指定它的类型为direct
channel.exchangeDeclare("exchange_direct", "direct")
//发布消息
for (String routingKey : routingKeys) {
String message = "Send the message : " + severity;
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
}
Consumer
// 路由关键字
private static final String[] routingKeys = new String[] {"info", "warn"};
// 声明一个交换机并指定它的类型为direct
channel.exchangeDeclare("exchange_direct", "direct")
// 声明一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 根据路由关键字绑定队列
for (String routingKey : routingKeys) {
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
}
扇型交换机(funout exchange)会将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键,扇型用来交换机处理消息的广播路由(broadcast routing)。
Publisher
private static final String EXCHANGE_NAME = "exchange.fanout";
// 在发布端可以不声明队列
//channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
for(int i=0;i<5;i++){
String message="this is number"+i;
// 路由关键字不能为null,填写""
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println(" [x] sent ' " + message + " '");
}
Consumer
private static final String EXCHANGE_NAME = "exchange.fanout";
private final static String QUEUE_NAME = "queue.fanout";
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 绑定队列
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
和扇形交换机绑定的队列将全部收到收到发布端的消息。
主题交换机(topic exchanges)通过对消息的路由键和队列到交换机的绑定模式之间的匹配,将消息路由给一个或多个队列,属于多播路。,topic可以进行模糊匹配,可以使用星号和井号#这两个通配符来进行模糊匹配,其中 号可以代替一个单词 # 号可以代替任意个单词,但是需要注意的是topic交换机的路由键也不是可以随意设置的,必须是由点隔开的一系列的标识符组成。标识符一般和消息的某些特性相关,可以定义任意数量的标识符,上限为255个字节,当路由键可以模糊匹配上的时候就能将消息映射到绑定的队列中去。
首部交换机和扇形交换机一样不需要路由关键字,交换机时通过headers来将消息映射到队列的,heders是一个hash结构求携带一个键“x-match”,这个键的value可以是any或者all,all代表消息携带的Hash是需要全部匹配,any代表仅匹配一个键就可以了。首部交换机的最大特点就是匹配规则不被限制为string,而是object。
Publish
private static String EXCHANGE_NAME = "exchange.hearders";
Map<String, Object> hearders = new HashMap<String, Object>();
hearders.put("api", "login");
hearders.put("version", 1.0);
hearders.put("radom", UUID.randomUUID().toString());
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder()
.headers(hearders)
.build();
String message = "Hello RabbitMQ!";
channel.basicPublish(EXCHANGE_NAME, "", properties, message.getBytes("UTF-8"));
Consumer
private static String EXCHANGE_NAME = "exchange.hearders";
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS);
String queueName = channel.queueDeclare().getQueue();
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-match", "any");
// arguments.put("x-match", "all"); 此时将不能匹配,因为Publisher的头部中并没有dataType属性
arguments.put("api", "login");
arguments.put("version", 1.0);
arguments.put("dataType", "json");
// 队列绑定时需要指定参数,注意虽然不需要路由键但仍旧不能写成null,需要写成空字符串""
channel.queueBind(queueName, EXCHANGE_NAME, "", arguments);
默认交换机(default exchange)不是一个真正的交换机类型,实际上是一个由消息代理(Broker)预先声明好的没有名字(名字为空字符串)的直连交换机。它有一个特殊的属性:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。
很多时候我们对于一些不复杂的场景都会使用这一特殊属性。
Publisher
private static final String QUEUE_NAME="task_queue";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
Consumer
private static final String QUEUE_NAME="task_queue";
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
上面的伪代码看起来我们并没有使用交换机,而是直接将消息投递到了队列中去,但实际上这个队列被绑定到了默认交换机上,而路由键就是队列名称。
channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments);
arguments :
1. x-message-ttl(Time-To-Live):
设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒。生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除。
// 统一设置队列消息过期时间为10s
Map<String, Object> argumentsMap = new HashMap<>();
argumentsMap.put("x-message-ttl", 10000);
channel.queueDeclare(QUEUE_NAME, true, false, false, argumentsMap);
// 单独设置某条消息过期时间
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder()
.expiration("10000")
.build();
channel.basicPublish(EXCHANGE_NAME, "", properties, message.getBytes(“UTF-8”));
2.x-expires : 当队列在指定的时间没有被访问则被删除。
Map<String, Object> argumentsMap = new HashMap<>();
// 设置队列消息过期时间 单位:毫秒
argumentsMap.put("x-expires", 10000);
channel.queueDeclare(QUEUE_NAME, true, false, false, argumentsMap);
3.x-max-length : 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉,遵循先进先出的原则。(要注意的是虽然消息队列是异步处理消息,但是消息几乎是被准实时消费的,这里只能保证消息队列的堆积消息不超过最大长度,使用时要特别注意)
Map<String, Object> argumentsMap = new HashMap<>();
// 设置队列消息对列的最大长度为5
argumentsMap.put("x-max-length", 5);
channel.queueDeclare(QUEUE_NAME, true, false, false, argumentsMap);
4.x-max-length-bytes :限定队列最大占用的内存空间大小。
5.x-dead-letter-exchange : 将从队列中删除的消息(大于最大长度、或者过期的等)推送到指定的交换机中去而不是丢弃掉。
6.x-dead-letter-routing-key :将删除的消息推送到指定交换机的指定路由键的队列中去。
Dead Letter Exchange(死亡交换机) 和 Dead Letter Routing Key(死亡路由键)用做死信队列(由于某些原因消息无法被正确的投递,为了确保消息不会被无故的丢弃,一般将其置于一个特殊角色的队列,这个队列一般称之为死信队列),当队列中的消息被删除时(大于最大长度、或者过期的等),可以将这些被删除的信息推送到其他交换机中,让其他消费者订阅这些被删除的消息,处理这些消息。
public class Publisher {
private static final String DEAD_QUEUE_NAME = "dead_queue";
private static final String DEAD_EXCHANGE_NAME = "exchange.dead";
private static final String DEAD_ROUTING_KEY = "routingkey.dead";
private static final String QUEUE_NAME = "general_queue";
private static final String EXCHANGE_NAME = "exchange.general";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明接收"死亡消息"的队列和交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_ROUTING_KEY);
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-message-ttl", 15000);
arguments.put("x-expires", 30000);
arguments.put("x-max-length", 4);
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
arguments.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
channel.queueDeclare(QUEUE_NAME, true, false, false, arguments);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
for(int i = 1; i <= 5; i++) {
String message = "Hello RabbitMQ: "+ i;
channel.basicPublish(EXCHANGE_NAME, "", null, message .getBytes("UTF-8"));
System.out.println("sent message : "+ message);
}
channel.close();
connection.close();
}
}
刚开始由于队列长度是4,总共发送5条消息,所以最早进入队列的消息1将被删除掉,被推送到死亡队列中,所以看到普通队列的消息为4条,死亡队列的消息为1条。随着时间的流逝普通队列的消息全部过期,所有消息都被推送到死亡队列中,最后普通队列被删除。
7.x-max-priority : 优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高的消息先被消费。
Publisher
private static final String QUEUE_NAME = "priority_queue";
private static final String EXCHANGE_NAME = "exchange.priority";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-max-priority", 5);
channel.queueDeclare(QUEUE_NAME, true, false, false, arguments);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
for(int i = 1; i <= 5; i++) {
String message = "Hello RabbitMQ: "+ i;
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder() .priority(i) .build();
channel.basicPublish(EXCHANGE_NAME, "", properties, message .getBytes("UTF-8"));
System.out.println("sent message : "+ message);
}
channel.close();
connection.close();
}
Consumer
private static final String QUEUE_NAME = "priority_queue";
private static final String EXCHANGE_NAME = "exchange.priority";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-max-priority", 5);
channel.queueDeclare(QUEUE_NAME, true, false, false, arguments);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Receive message : " + message);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
channel.close();
connection.close();
}
8.x-queue-mode=lazy : 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中,默认为lazy。
9.x-queue-master-locator : 配置镜像队列
消费者(Consumer) :在处理消息的时候偶尔会失败或者有时消息代理会直接崩溃掉。而且网络原因也有可能引起各种问题,那么为了保证消息不会丢失保证消息被正确处理,RabbitMQ提供了两种消息确认机制:
1.自动应答 :
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
自动应答表示当消费者连接上队列,因为没有指定消费者一次获取消息的条数,所以会把队列中的所有消息一下子推送到消费者端,当消息从队列被推出的时的那一刻就表示已经对消息进行自动确认了,消息就会从队列中删除。
channel.basicConsume(QUEUE_NAME, true, consumer);
当队列被订阅后,队列中的消息全部被清空。
2.手动应答 :
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
Consumer:
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("C [x] Received '" + message + "'");
}
};
// 订阅消息
channel.basicConsume(QUEUE_NAME, false, consumer);
每执行一次channel.basicAck(envelope.getDeliveryTag(), false);Unacked和Total就会减去1,直到两个值都为0 。
注意:手动确认一定要channel.basicAck(envelope.getDeliveryTag(), false);否则会导致消息不被确认而一直堆积在队列中而不被删除。
本文转载自:https://blog.csdn.net/qq_25235807/article/details/80135088