一、RabbitMQ
1、MQ介绍
MQ(Message Queue):消息队列又叫消息中间件。业务系统里面可通过典型的生产者和消费者模型实现系统A和系统B之间的解耦。因为消息的生产和消费都是异步的,而且双方只关心消息的发送和接收,中间没有业务逻辑的侵入,可以轻松实现系统间的解耦。
2、不同MQ的特点
① ActiveMQ
ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持JMS规范的的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎!但是数据量一旦达到某个量级,性能就会很差。
② Kafka
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务(进行大数据的分析)。
③ RocketMQ
RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。
④ RabbitMQ
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
3、安装前置依赖Erlang
先运行下面的指令为erlang设置好仓库。
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
# 安装erlang
yum install erlang
出现了上面的错误,不急,先使用下面的指令安装完deltarpm之后,再执行yum install erlang
。
yum provides '*/applydeltarpm' #查看依赖包的位置
yum -y install deltarpm #安装命令
如下图则erlang安装成功。
可以使用下面指令查看erlang是否成功安装。
erl
使用下面指令安装socat。
yum install socat
4、安装RabbitMQ
安装RabbitMQ。
rpm -ivh rabbitmq-server-3.8.5-1.el7.noarch.rpm
如下图则表示RabbitMQ安装成功。
注意:高版本的RabbitMQ不带配置文件,需要去github上面拷贝并放到/etc/rabbitmq
目录下面。
rabbitmq-server-3.8.5-1.el7.noarch.rpm包下载链接,提取码:o9z3
5、RabbitMQ常用指令
① 启动rabbitmq中的插件管理。
rabbitmq-plugins enable rabbitmq_management
出现下图则表示启动成功。
② RabbitMQ服务的启动、重启与停止。
# 启动RabbitMQ服务
systemctl start rabbitmq-server
# 重启RabbitMQ服务
systemctl restart rabbitmq-server
# 停止RabbitMQ服务
systemctl stop rabbitmq-server
③ 查看RabbitMQ服务的状态。
systemctl status rabbitmq-server
下图表示RbbitMQ处于运行状态。
6、管理界面测试
启用下面配置可以允许所有用户访问,而不仅仅是本地访问。
开启rabbitmq服务。
service rabbitmq-server start
开启web管理接口。
rabbitmq-plugins enable rabbitmq_management
使用IP:15672
。
二、RabbitMQ的消息模型
RabbitMQ中生产者和消费者机制是这样的:生产者负责生产消息并将该消息发送到交换机(需指定路由key),消费者根据路由key从交换机上找到对应的消息队列,从这个队列中取消息。
注意:生产端和消费端都可以申明交换机或者队列,只要所申明的属性不变,RabbitMQ自己回去判断是否一存在过,从而保证交换机或者队列只创建一次。
1、Hello World消息模型
这个消息模型不需要用到交换机,所以生产者在投递消息的时候,将路由key直接指定为消费者要消费的队列的名字即可。
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
String queueName = "hello world_queue";
//声明通道对应的消息队列(表示创建队列, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示队列名,
//第二个参数表示队列是否开启持久化
//第三个参数表示队列是否开启独占模式(独占表示一个队列只能有一个channel监听)
//第四个参数表示是否自动删除队列
//第五个参数表示额外附加参数
channel.queueDeclare(queueName, false, false, false, null);
String msg = "Hello, I am rabbitMQ!";
//第一个参数表示交换机名字
//第二个参数表示路由key(当第一个参数为空字符串即交换机为default时, 这个参数直接指向队列名称)
//第三个参数表示传递消息的额外设置
//第四个参数表示消息的具体内容
channel.basicPublish("", queueName, null, msg.getBytes());
channel.close();
connection.close();
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
String queueName = "hello world_queue";
//声明通道对应的消息队列(表示创建队列, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示队列名,
//第二个参数表示队列是否开启持久化
//第三个参数表示队列是否开启独占模式(独占表示一个队列只能有一个channel监听)
//第四个参数表示是否自动删除队列
//第五个参数表示额外附加参数
channel.queueDeclare(queueName, false, false, false, null);
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者消费了一条消息: " + new String(body));
}
});
}
}
2、Work Queues消息模型
这个模型和Hello World消息模型本质是一样的,只不过是队列上面多了几个消费者,这些消费者共同消费队列中的这些消息。
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
String queueName = "work_queues";
//声明通道对应的消息队列(表示创建队列, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示队列名,
//第二个参数表示队列是否开启持久化
//第三个参数表示队列是否开启独占模式(独占表示一个队列只能有一个channel监听)
//第四个参数表示是否自动删除队列
//第五个参数表示额外附加参数
channel.queueDeclare(queueName, false, false, false, null);
String msg = "Hello, I am rabbitMQ!";
//第一个参数表示交换机名字
//第二个参数表示路由key(当第一个参数为空字符串即交换机为default时, 这个参数直接指向队列名称)
//第三个参数表示传递消息的额外设置
//第四个参数表示消息的具体内容
for (int i = 0; i < 20; i++) {
channel.basicPublish("", queueName, null, msg.getBytes());
}
channel.close();
connection.close();
}
}
消费者1代码:
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
String queueName = "work_queues";
//声明通道对应的消息队列(表示创建队列, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示队列名,
//第二个参数表示队列是否开启持久化
//第三个参数表示队列是否开启独占模式(独占表示一个队列只能有一个channel监听)
//第四个参数表示是否自动删除队列
//第五个参数表示额外附加参数
channel.queueDeclare(queueName, false, false, false, null);
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, 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));
}
});
}
}
消费者2代码:
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
String queueName = "work_queues";
//声明通道对应的消息队列(表示创建队列, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示队列名,
//第二个参数表示队列是否开启持久化
//第三个参数表示队列是否开启独占模式(独占表示一个队列只能有一个channel监听)
//第四个参数表示是否自动删除队列
//第五个参数表示额外附加参数
channel.queueDeclare(queueName, false, false, false, null);
//消费端限流, 一次只接收一条未确认的消息
channel.basicQos(1);
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//第一个参数是消息的唯一标识符, 第二份参数表示是否开启批量签收
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("消费者2消费了一条消息: " + new String(body));
}
});
}
}
3、Publich/Subscribe消息模型
这个消息模型类似广播模式,生产者将消息投递给交换机,交换机不需要指定路由key,每个消费者有自己的队列,交换机会把消息发送给所有绑定该交换机的队列,只要消费者自己的消息队列绑定的是同一个交换机,就可以实现一条消息被多个消费者消费。交换机类型需要指定为fanout
。
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("publish subscribe_exchange", "fanout");
String msg = "Hello, I am rabbitMQ!";
//第一个参数表示交换机名字
//第二个参数表示路由key(当第一个参数为空字符串即交换机为default时, 这个参数直接指向队列名称)
//第三个参数表示传递消息的额外设置
//第四个参数表示消息的具体内容
channel.basicPublish("publish subscribe_exchange", "", null, msg.getBytes());
channel.close();
connection.close();
}
}
消费者1代码:
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//创建临时队列, 每个消费者有自己的队列
String queueName = channel.queueDeclare().getQueue();
//声明交换机(表示创建交换机, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示交换机的名字
//第二个参数表示交换机的类型
channel.exchangeDeclare("publish subscribe_exchange", "fanout");
//绑定队列和交换机, fanout即发布订阅模式不需要路由key
channel.queueBind(queueName, "publish subscribe_exchange", "");
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, 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));
}
});
}
}
消费者2代码:
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//创建临时队列, 每个消费者有自己的队列
String queueName = channel.queueDeclare().getQueue();
//声明交换机(表示创建交换机, 不管是消费者那声明还是生产者那声明都可以)
//第一个参数表示交换机的名字
//第二个参数表示交换机的类型
channel.exchangeDeclare("publish subscribe_exchange", "fanout");
//fanout即发布订阅模式不需要路由key
channel.queueBind(queueName, "publish subscribe_exchange", "");
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, 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));
}
});
}
}
4、Routing消息模型
生产者将消息投递到交换机同时指定路由key,消费者根据路由key从对应的消息队列中获取消息进行消费,这个模型必须当双方的路由key相同时才能匹配到。
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare("routing_exchange", "direct");
String msg = "Hello, I am rabbitMQ!";
//第一个参数表示交换机名字
//第二个参数表示路由key(当第一个参数为空字符串即交换机为default时, 这个参数直接指向队列名称)
//第三个参数表示传递消息的额外设置
//第四个参数表示消息的具体内容
channel.basicPublish("routing_exchange", "hello routing", null, msg.getBytes());
channel.close();
connection.close();
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//创建临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定队列和交换机
channel.queueBind(queueName, "routing_exchange", "hello routing");
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者消费了一条消息: " + new String(body));
}
});
}
}
5、Topics消息模型
这个消息模型其实就是Routing消息模型的升级版,消费者的路由key可以使用#表示匹配大于等于零个词
,*表示匹配一个词
,其他都和第四种消息模型一样。
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare("topics_exchange", "topic");
String msg = "Hello, I am rabbitMQ!";
//第一个参数表示交换机名字
//第二个参数表示路由key(当第一个参数为空字符串即交换机为default时, 这个参数直接指向队列名称)
//第三个参数表示传递消息的额外设置
//第四个参数表示消息的具体内容
channel.basicPublish("topics_exchange", "user", null, msg.getBytes());
channel.close();
connection.close();
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//创建临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定队列和交换机, #表示匹配零个、一个或多个词, *表示只匹配一个词
channel.queueBind(queueName, "topics_exchange", "user.#");
//创建消费者
//第一个参数表示队列名
//第二个参数表示是否开启自动签收消息
//第三个消息是消费时候的回调接口
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者消费了一条消息: " + new String(body));
}
});
}
}