前言
相对于其他消息中间件,Rabbitmq高可靠性应该算是不错的特点了吧
Rabbitmq应用场景
1.RabbitMQ的消息应当尽可能的小,并且只用来处理实时且要高可靠性的消息。
2.消费者和生产者的能力尽量对等,否则消息堆积会严重影响RabbitMQ的性能。
3.集群部署,使用热备,保证消息的可靠性。
本文参照官网Rabbitmq Tutorial内容对Rabbitmq进行特性分析
目前一共七种模型,前五种常用,后两种也是刚刚接触,一边学习一边整理吧,肯定有遗漏的地方,望见谅!
一、 Hello World!
学习任何一门语言,都离不开 “Hello World!”,就是一个入门案例!Rabbitmq的 "Hello World!"指的是一种直连的方式,何为直连呢?这就不得不提Rabbitmq的一些概念了!
Rabbitmq中在目前初步学习的阶段,最需要了解的概念就是生产者、消费者、虚拟主机、交换机、队列,当然还有集群,但这节主要是前几种!
如上图所示:
- 一个RabbitmqServer中是有多个虚拟主机的,每个虚拟主机中又存在多个交换机和队列
- 平时工作开发是以虚拟主机为基础进行的,交换机和队列是AMQP协议中的概念,引用百度百科的解释
在服务器中,三个主要功能模块连接成一个处理链完成预期的功能:
“exchange”接收发布应用程序发送的消息,并根据一定的规则将这些消息路由到“消息队列”。
“message queue”存储消息,直到这些消息被消费者安全处理完为止。
“binding”定义了exchange和message queue之间的关联,提供路由规则。
按我的理解就是,exchange对应的是生产者,queue对应的是消费者
3. 每个虚拟机都有一个默认的交换机
大概的概念就是这样,主要的就要知道,我们是基于虚拟主机进行开发的,生产者对应的是exchange,消费者对应的是queue,这样看一下Hello World模型,这个模型也是最简单的模型,在这个模型中是没有交换机的概念的,看一下官网的流程图
Producer和Consumer直接和queue相连接,也就是说我们Producer发送消息直接发送到queue中,其实这是一个错觉,因为Rabbitmq是基于AMQP协议开发的,怎么可能没有exchange呢,那么为什么上面的模型中没有exchange呢?
其实这是因为每个虚拟主机都有一个默认的exchange,在这个模型中,message是默认发送到默认的exchange(AMQP default)中的,然后路由到queue中,最后被Consumer接收!
这种模型也类似于一种直连的方式
下面是一个简单的案例!
1.POM坐标
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
2.工具类
简单写了一个获取连接的工具类
/**
* @program: rabbitmq-demo
* @description: Rabbitmq工具类
**/
public class RabbitmqConfig {
/**
* 创建连接mq的连接工厂对象
*/
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
// 连接rabbitmq的主机
connectionFactory.setHost("127.0.0.1");
// 设置连接端口号
connectionFactory.setPort(5672);
/**
* 设置连接虚拟主机,虚拟主机:类似于nacos中命名空间概念
* 举个例子:
* 搭建一个Rabbitmq消息中间件后,此时有多组服务进行通信,原则上讲
* 每一组服务之间是相互隔离的,也就是说,只允许A组内部服务之间进行相互通信,
* 不允许A组和B组相互通信,这样就可以将它们划分在不同的虚拟主机中,完成这个功能
*/
connectionFactory.setVirtualHost("/test");
/**
* 设置用户名
*/
connectionFactory.setUsername("admin");
// 设置密码
connectionFactory.setPassword("admin");
}
/**
* 获取连接对象
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
return connectionFactory.newConnection();
}
/**
* 获取连接中的通道
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Channel getChannel() throws IOException, TimeoutException {
return getConnection().createChannel();
}
}
3.Producer
/**
* @program: rabbitmq-demo
* @description: 消息生产者
**/
public class Producer {
@Test
public void publishing() {
try {
// 通过工具类获取Channel
Channel channel = RabbitmqConfig.getChannel();
/**
* 声明队列
* queue: 队列名称 ,若队列已存在,但是参数不一致,则报错!
* durable:队列持久化,若为true,则rabbitmq重启后仍然存在
* exclusive:排它性,true:只可以此Connection连接这个queue,当当前Connection关闭时,这个queue会自动删除!
* 并且在存在过程中,其他的Connection不可以连接这个queue,原理嘛,就是加个排它锁
* autoDelete:自动删除,true:当queue不再使用后自动删除;不再使用:即queue中无数据,没有其它connection连接此queue
* arguments:额外参数
*/
channel.queueDeclare("hello",false,false,false,null);
/**
* 发布消息
* exchange: exchange名称
* routingKey:路由名称,不一定是某个对应的queue名称,当然肯定是需要匹配到某个queue的
* props:额外配置
* body:消息体
*/
channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
// 关闭channel
channel.close();
// 关闭Connection
channel.getConnection().close();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 测试队列持久化及消息持久化
*/
@Test
public void testDurable() throws IOException, TimeoutException {
Channel channel = RabbitmqConfig.getChannel();
// 声明队列持久化
channel.queueDeclare("durableQueue",true,false,false,null);
// 声明消息持久化
channel.basicPublish("","durableQueue", MessageProperties.PERSISTENT_TEXT_PLAIN,"持久化消息".getBytes());
}
/**
* 测试消息过期时间
*/
@Test
public void testTTL() throws IOException, TimeoutException {
Map<String, Object> arguments = new HashMap<String, Object>();
// 队列内消息十秒过期
arguments.put("x-message-ttl", 10000);
// 队列十秒没有消费者访问该队列则自动删除
arguments.put("x-expires", 20000);
Channel channel = RabbitmqConfig.getChannel();
// 声明队列内消息的过期时间
channel.queueDeclare("ttlQueue",false,false,false,arguments);
channel.basicPublish("","ttlQueue",null,"10秒后消息过期".getBytes());
// 设置单个消息过期时间
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder().expiration(20000+"");
channel.basicPublish("","durableQueue",properties.build(),"20秒后消息过期".getBytes());
}
/**
* x-max-length:用于指定队列的长度,如果不指定,可以认为是无限长,例如指定队列的长度是4,当超过4条消息,前面的消息将被删除,给后面的消息腾位,类似于栈的结构,
* 当设置了x-max-priority后,优先级高的排在前面,所以基本上排除的话就是排除优先级高的这些
* x-max-length-bytes: 用于指定队列存储消息的占用空间大小,当达到最大值是会删除之前的数据腾出空间
* x-max-priority: 设置消息的优先级,优先级值越大,越被提前消费。
*/
@Test
public void testMax() throws IOException, TimeoutException {
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-max-length", 4);
arguments.put("x-max-length-bytes", 1024);
arguments.put("x-max-priority", 5);
Channel channel = RabbitmqConfig.getChannel();
declareDead(arguments,channel);
channel.queueDeclare("maxQueue",false,false,false,arguments);
for (int i=1; i<=6;++i){
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder().priority(i);
channel.basicPublish("","maxQueue",properties.build(),("第"+i+"条消息,优先级是:"+i).getBytes());
}
}
/**
* 声明Dead-exchange、dead-queue
*/
public void declareDead(Map<String, Object> arguments,Channel channel) throws IOException {
channel.exchangeDeclare("EXCHANGE_DEAD",BuiltinExchangeType.DIRECT);
channel.queueDeclare("QUEUE_DEAD",false,false,false,null);
// 若不指定exchange、queue,则默认使用AMQP default默认exchange,默认使用queue的名字作为routingkey
channel.queueBind("QUEUE_DEAD","EXCHANGE_DEAD","routing_dead");
arguments.put("x-dead-letter-exchange", "EXCHANGE_DEAD");
arguments.put("x-dead-letter-routing-key", "routing_dead");
}
/**
* 测试排它性exclusive
*/
public static void main(String[] args) {
Connection connection = null;
try {
connection = RabbitmqConfig.getConnection();
Channel channel = connection.createChannel();
// 声明一个排它queue
channel.queueDeclare("testExclusive",false,true,false,null);
// 关闭当前channel,以证明queue的排它性和channel没关系
channel.close();
testExclusiveA(connection);
testExclusiveB(connection);
// 在一个服务项目中是没有办法测试排它性的,因为本项目中所有对rabbitmq的连接,在Rabbitmq看来都是一个Connection,所以是不会触发排它锁的,如果需要测试,可以再创建一个项目进行测试
testExclusiveC();
// 等待十秒,这区间可以看一下Rabbitmq ui界面,此时rabbitmq ui是可以看到此queue的
Thread.sleep(10000);
// 关闭连接,关闭连接后,testExclusive这个queue也会随之删除
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 测试发送消息
* @param connection
* @throws IOException
* @throws TimeoutException
*/
public static void testExclusiveA(Connection connection) throws IOException, TimeoutException {
Channel channel = connection.createChannel();
channel.basicPublish("","testExclusive",null,"hello testExclusiveA".getBytes());
System.out.println("testExclusiveA");
channel.close();
}
/**
* 测试发送消息
* @param connection
* @throws IOException
* @throws TimeoutException
*/
public static void testExclusiveB(Connection connection) throws IOException, TimeoutException {
Channel channel = connection.createChannel();
channel.basicPublish("","testExclusive",null,"hello testExclusiveB".getBytes());
System.out.println("testExclusiveB");
channel.close();
}
/**
* 这里重新建了一个ConnectionFactory,并且获取一个新的Connection,但是事实证明,也是可以发送消息到testExclusive的,因为在同一个项目中建立的连接,
* 在Rabbitmq看来是一个Connection
* @throws IOException
* @throws TimeoutException
*/
public static void testExclusiveC() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/test");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.basicPublish("","testExclusive",null,"hello testExclusiveC".getBytes());
System.out.println("testExclusiveC");
channel.close();
connection.close();
}
}
4.Consumer
/**
* @program: rabbitmq-demo
* @description: 消费者-接受消息
**/
public class Consumer {
public static void main(String[] args) {
try {
Channel channel = RabbitmqConfig.getChannel();
/**
* 这里解释一下为什么需要在订阅queue之前,提前queueDeclare一下,这个是为了防止provider还没有启动,而consumer先启动了,
* 如果不提前声明的话,那么在rabbitmq中是不存在hello的,那么是没办法订阅消息的,反馈到程序中就是报错!
* 但是声明时,也要特别注意参数不要弄错
*/
channel.queueDeclare("hello",false,false,false,null);
/**
* 消费消息
* queue:队列名称
* autoAck:消息确认机制;true:自动确认消息,false:手动确认消息
* callback:Consumer接口,