Rabbitmq消息队列
消息队列简介
消息队列(Message Queue)别名消息中间件,是经典的生产者消费者模型。生产者不断向消息队列发送消息,消费者从消息队列消费消息,因此消息的生产和消费都是异步的。
目前消息中间件主要的几种有:RabbitMQ、ActiveMQ、Kafka、RocketMQ等。
- ActiveMQ
Apache出品的产品,行业中最流行、能力最强的消息中间件,提供丰富的API,在中小型企业很受欢迎。缺点就是吞吐量小,性能相对较弱。 - Kafka
高吞吐量,设计的初衷用在大数据中,但是对数据的一致性要求不强,偶尔会丢失数据。 - RocketMQ
纯java开发的消息中间件,具有高吞吐量、高可用性,对分布式事务有一定的支持(开源的版本不支持分布式事务)。 - RabbitMQ
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。Spring默认支持的就是RabbitMQ。AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
amqp协议介绍
qmqp也符合生产者消费者模型,它会将消息以通道的形式发送。生产者与Rabbitmq的server建立连接,建立完连接后,生产者将对应于一个虚拟主机(Virtual host),可以将该虚拟主机理解为数据库中库的概念,每个应用对应一个虚拟主机。访问虚拟主机需要权限,因此需要对用户和虚拟主机做一个绑定。当生产者进入到虚拟主机之后,就会将通道中的消息放到交换机(Exchange)中,也有的消息模型会直接将消息放入队列当中。消费者也需要连接到server,连接成功后,则一直会监听着某个虚拟主机的队列,一旦有消息则会进行消费。
Ubuntu 安装Rabbitmq
- 由于rabbitMq需要erlang语言的支持,在安装rabbitMq之前需要安装erlang,执行命令:
sudo apt-get install erlang-nox
- 添加公钥:
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
- 更新软件包:
sudo apt-get update
- 安装 RabbitMQ:
sudo apt-get install rabbitmq-server
- 查看 RabbitMq状态(默认已启动):
systemctl status rabbitmq-server
,active (running) 说明处于运行状态 - 启用 web端可视化操作界面,配置Management Plugin插件:
sudo rabbitmq-plugins enable rabbitmq_management
- 重启让配置生效:
sudo service rabbitmq-server restart
- 创建用户:
sudo rabbitmqctl add_user admin 123456
,RabbitMQ默认会创建guest用户,但是 3.3 及后续版本,guest 只能在服务本机登录。建议创建其他新用户,授权,用来做其他操作。 - 查看所有用户:
sudo rabbitmqctl list_users
- 给admin管理员角色:
sudo rabbitmqctl set_user_tags admin administrator
- 赋予virtual host中所有资源的配置、写、读权限:
sudo rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
- 浏览器访问管理页面:输入
http://虚拟机IP地址:15672/
- 停止服务:
sudo service rabbitmq-server stop
其他Rabbitmq命令
命令 | 说明 |
---|---|
sudo rabbitmqctl | 查看所有命令和帮助文档 |
sudo rabbitmqctl stop | 停止服务 |
sudo rabbitmqctl status | 查看服务状态 |
sudo rabbitmqctl list_users | 查看当前所有用户 |
sudo rabbitmqctl delete_user guest | 删掉默认用户,除guest |
sudo rabbitmqctl add_user username password | 添加新用户 |
sudo rabbitmqctl set_user_tags username administrator | 设置用户身份 |
sudo rabbitmqctl set_permissions -p / username “.” “.” “.*” | 赋予用户默认vhost的全部操作权限 |
sudo rabbitmqctl list_user_permissions username | 查看用户的权限 |
Rabbitmq的角色
-
超级管理员(administrator)
可登陆管理控制台(启用management plugin的情况下),可查看所有的信息,并且可以对用户,策略(policy)进行操作。 -
监控者(monitoring)
可登陆管理控制台(启用management plugin的情况下),同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等) -
策略制定者(policymaker)
可登陆管理控制台(启用management plugin的情况下), 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。与administrator的对比,administrator能看到这些内容 -
普通管理者(management)
仅可登陆管理控制台(启用management plugin的情况下),无法看到节点信息,也无法对策略进行管理。 -
其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
环境准备
- 在管理页面处创建测试用的虚拟主机
/test
(虚拟主机名必须以斜杠开头),测试用的用户名为user
,密码为123456
,并将虚拟主机绑定到用户上。
2. 创建maven项目,引入amqp的依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
- 创建用于连接amqp服务器的工具类
public class RabbitMQUtil {
//创建连接rabbitmq的工厂对象
public static ConnectionFactory factory;
static {
factory=new ConnectionFactory();
// 设置主机名、端口号、连接的虚拟主机
factory.setHost("192.168.192.130");
factory.setPort(5672);
factory.setVirtualHost("/test");
//设置访问虚拟主机的名称和密码
factory.setUsername("user");
factory.setPassword("123456");
}
public static Connection getCN(){
try {
return factory.newConnection();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
直连模式
无交换机,一对一模型,一个服务提供者,一个消息队列,一个消费者.
- 创建消息生产者
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection= RabbitMQUtil.getCN();
//创建发送消息的通道
Channel channel = connection.createChannel();
/*
通道绑定对应的消息队列
参数一:队列名称为hello
参数二:定义队列是否持久化,即关闭后要将队列存在磁盘,false的话重启rabbitmq会删除队列,该属性不能配置队列中的消息是否持久化
参数三:是否一个连接独占队列
参数四:是否消费完成后删除队列,false不自动删除,也即把消费者服务断开后删除队列
参数五:额外附加参数
*/
channel.queueDeclare("hello",false,false,false,null);
//发布消息
//参数一:交换机
//参数二:队列名称
//参数三:发布消息时的一些属性,比如消息是否持久化
//参数四:发送的内容
channel.basicPublish("","hello",null,"hello,rabbitmq2".getBytes());
channel.close();
connection.close();
}
}
- 创建消息消费者
Consumer端不建议关闭channel和connection,因为它要一直接收消息。
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection= RabbitMQUtil.getCN();
//创建通道
Channel channel = connection.createChannel();
//通道绑定队列
channel.queueDeclare("hello",false,false,false,null);
//消费消息
//参数一:消息队列名称
//参数二:开启消息的自动确认机制
//参数三:消费时的回调接口
channel.basicConsume("hello",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));
}
});
}
}
工作队列模式
无交换机,一对多模型,一个服务提供者,一个消息队列,多个消费者。默认情况消息被一条一条平均分配给每一个消费者消费。
消息确认机制:若设置为true,则所有消息在未消费之前就已经被分配给了每个消费者。如果期间消费者宕机的话那么被分配给它的消息就会丢失。因此一般选择关闭消息自动确认,实现能者多劳的功能。channel.basicQos(1)指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
- 创建生产者
public class Provider {
@Test
public void test() throws IOException, TimeoutException {
Connection connection= RabbitMQUtil.getCN();
Channel channel = connection.createChannel();
//队列名称为work,可以随机起
channel.queueDeclare("work",false,false,false,null);
for (int i=1;i<10;i++){
channel.basicPublish("","work",null,(i+":hello,rabbitmq").getBytes());
}
}
}
- 创建消费者
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
channel.basicQos(2);//每次只能消费一个消息
channel.queueDeclare("work",false,false,false,null);
//关闭自动确认消息
channel.basicConsume("work",false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
//手动确认消息
//参数一:确认具体消息的信息
//参数二:打开多条同时确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
channel.basicQos(2);
channel.queueDeclare("work",false,false,false,null);
channel.basicConsume("work",false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
广播模型
fanout模型(广播模型):有交换机,一个生产者,多个消费者。生产者将信息发送给交换机,由交换机发送消息给临时队列,每个消费者对应一个临时队列。交换机会将消息放松给绑定过的所有队列,实现一条消息被多个消费者消费。
- 创建生产者
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
//声明fanout类型的交换机,名称为myExchange
channel.exchangeDeclare("myExchange","fanout");
//发送消息
channel.basicPublish("myExchange","",null,"=====fanout=====".getBytes());
channel.close();
cn.close();
}
}
- 创建消费者
创建三个消费者,代码都为以下内容
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
//创建临时队列
String queueName = channel.queueDeclare().getQueue();
//队列绑定交换机,此处的路由key为空
channel.queueBind(queueName,"myExchange","");
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));
}
});
}
}
路由模型——Direct模型
跟广播模型类似,其主要是添加了一些路由规则。在广播模型中生产者的消息要发送给每一个消费者。但是在路由——direct模型中,可以实现不同的消息发送给不同的消费者。在Direct模型下,生产者在发送消息时,必须要对消息指定Routing key,消费者的临时队列与交换机的绑定也不再是任意的,需要指定Routing key。这样就可以实现消息路由。比如以下例子实现Consumer1接收error消息,Conusmer2可以接收error、info、debug消息。
- 创建生产者
public class Provider {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
//声明fanout类型的交换机,名称为myExchange
channel.exchangeDeclare("exchange_direct","direct");
//设置routing key
String routingkey="error";
//发送消息
channel.basicPublish("exchange_direct",routingkey,null,("=====direct=====routingkey:"+routingkey+"=====").getBytes());
}
}
- 创建消费者
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
//创建临时队列
String queueName = channel.queueDeclare().getQueue();
//队列绑定交换机
channel.queueBind(queueName,"exchange_direct","error");
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));
}
});
}
}
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
//创建临时队列
String queueName = channel.queueDeclare().getQueue();
//队列绑定交换机
channel.queueBind(queueName,"exchange_direct","error");
channel.queueBind(queueName,"exchange_direct","info");
channel.queueBind(queueName,"exchange_direct","debug");
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));
}
});
}
}
路由模型——Topic模型
Direct模型的拓展,支持统配的路由。临时队列和交换机绑定时可以将routing key写成通配符的情况。.
代表单词间的分隔符,*
代表一个单词,#
代表一个或多个单词。比如消费者routingkey可以是*.hello.#,表示hello前可以有一个单词,后面可以有多个单词。
- 生产者的创建
public class Provider {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
channel.exchangeDeclare("topicExchange","topic");
String routingkey="user";
channel.basicPublish("topicExchange",routingkey,null,"这是topic模型".getBytes());
}
}
- 消费者的创建
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection cn = RabbitMQUtil.getCN();
Channel channel = cn.createChannel();
channel.exchangeDeclare("topicExchange","topic");
String queuename=channel.queueDeclare().getQueue();
channel.queueBind(queuename,"topicExchange","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));
}
});
}
}
Springboot集成Rabbitmq
详情见——Springboot集成Rabbitmq
Rabbitmq的常见作用
- 异步处理:用户注册需要发送邮件和短信,如果仅仅起到的是通知的作用,那么加入消息队列后可以加快业务速度,不用等待邮件和短信发送完成后业务才完成
- 系统解耦:比如可以在订单系统和库存系统加入消息队列,当库存系统崩溃的时候,订单请求还在
- 流量削峰:在前端加入消息队列,比如秒杀活动时,如果消息队列的请求数达到一定个数后就会返回错误页面
Rabbitmq集群
- 普通集群(副本集群):主节点用来和生产者打交道,从节点负责同步主节点的数据,但只能同步exchange的数据,不能同步queue的数据。虽然从节点不包含队列数据,但其包含队列元数据,因此在每个节点的web管理界面都能看到队列的信息。消费者消费消息时,可以是任何一个节点,当如果要在从节点上消费消息,必须保证主节点是活着的,其原理是从节点会将主节点上的消息拿回来后再发给消费者。
- 准备n台虚拟机,配置各个虚拟机的IP和主机名
- 同步集群中各个节点的
/var/lib/rabbitmq/.erlang.cookie
文件
用scp命令将任意一个节点中的该文件拷贝到其他机器中即可。 - 后台方式启动所有节点:
rabbitmq-server -detached
后台启动的方式看不到web页面,因此只能再命令行测试 - 用命令
rabbitmqctl cluster_status
查看集群状态,发现每台服务器仍然是单个节点,未成集群。 - 关闭n-1个节点,剩余一个节点(该节点为主节点):
rabbitmqctl stop_app
- 将关闭掉的n-1个节点加入到集群中:
rabbitmqctl join_cluster rabbit@剩余一个节点的主机名
- 启动n-1个节点:
rabiitmqctl start_app
- 镜像集群:一主多从,每个节点都会同步主节点的所有信息,所以可以相互替换实现高可用。当主节点宕机后,某个从节点会变为主节点,从节点仍然会提供服务。主节点再次启动后会变成从节点。
在普通集群的基础上设置策略即可实现镜像集群。
查看当前策略:rabbitmqctl list_policies
设置策略的语法为:rabbitmqctl set_policy [可选参数] <name> <pattern> <definition>
参数 | 说明 |
---|---|
name | 策略名称 |
pattern | queue的匹配模式,正则表达式 |
definition | 包括ha-mode、ha-params、ha-sycn-mode |
ha-mode包含以下几种:
- all:表示在集群所有节点进行镜像
- exactly:表示在指定节点个数是进行镜像,数量由ha-params指定
- nodes:表示在指定的节点上进行镜像,节点名称在ha-params指定
ha-params用来配置ha-mode中用到的参数
ha-sycn-mode:消息的同步方式,自动还是手动,默认自动
添加策略:rabbitmqctl set_policy high-available '^hello' '{"ha-mode":"all","ha-sycn-mode":"automatic"}'
,该策略的名称为high-available,并且匹配所有hello开头的队列,在所有节点上镜像,并且自动同步。