RabbitMQ指南(二)
一、多租户与权限
每一个RabbitMQ服务器都能创建虚拟的服务器主机(vhost)。每个vhost都有用自己独立的队列,交换机,绑定关系,权限等等。客户端在连接的时候必须指定一个vhost,默认的vHost是/
。
rabbitmqctl
命令标准格式
rabbitmqctl [-n node] [-t timeout] [-q] {command} [command options] //[] 表示可选参数,{}表示必选参数
租户与权限相关的命令
rabbitmqctl add_vhost {vhost}
rabbitmqctl list_vhosts {vhostinfoitem...}
rabbitmqctl delete_vhost {vhost}
rabbitmqctl set_permissions {-p vhost} {username} {conf} {write} {read}
rabbitmqctl clear_permissions {-p vhost} {username}
rabbitmqctl list_permissions {-p vhost}
rabbitmqctl list_user_permissions {username}
二、用户管理
在RabbitMQ中,用户是访问控制的基本单元,且单个用户可以跨越多个vhost授权。针对一至多个vhost,用户可以被授权不同的访问权限。
用户相关指令
rabbitmqctl add_user {username} {password}
rabbitmqctl change_password {username} {password}
rabbitmqctl clear_password {username} {password}
rabbitmqctl authenticate_user{username} {password}
rabbitmqctl list_users
rabbitmqctl delete_user {username}
用户角色分为5类:
- none:无任何角色,默认值
- management:可以访问web管理页面
- policymaker:包含management的全部权限,并且可以管理police策略
- monitoring:包含policymaker的全部权限,并且可以看到所有连接与信道的信息
- administrator:具有monitoring,并且可以管理用户,虚拟主机等等,是代表了最高权限
三、应用管理
-
rabbitmq-server -detached
:该命令用于启动RabbitMQ服务,-detached
是为了让服务以守护进程方式来运行 -
rabbitmqctl stop [pid_file]
: 该命令用于停止RabbitMQ的Erlang虚拟机和RabbitMQ应用服务。如果指定了pid_file
,还需要等待指定的进程结束 -
rabbitmqctl shutdown
:该命令用于停止RabbitMQ和Erlang虚拟机和RabbitMQ应用服务,并且该命令是阻塞的 -
rabbitmqctl stop_app
:该命令只是停止RabbitMQ应用,不停止Erlang虚拟机 -
rabbitmqctl start_app
:启动RabbitMQ应用 -
rabbitmqctl reset
:将RabbitMQ节点重置还原到最初的状态。包括从原来所在的集群中删除次节点,删除vhost,用户,以及所有的持久化消息。在执行该命令前,必须停止RabbitMQ应用(比如:执行rabbitmqctl stop_app
) -
rabbitmqctl corce_reset
:强制RabbitMQ节点重置还原到最初的状态。它只能在数据库或集群已经损坏的情况下才能执行。在执行该命令前,必须停止RabbitMQ应用(比如:执行rabbitmqctl stop_app
) -
rabbitmqctl rotate_logs {suffix}
:指示RabbitMQ节点轮换日志文件
四、集群管理
RabbitMQ集群允许消费者和生产者在RabbitMQ单点崩溃的情况下继续运行,也可以通过添加更多的节点来线性的扩展消息通信的吞吐量。当失去一个RabbitMQ节点时,客户端能过重新连接到集群中的任何一个其他的节点,并继续生产或消费。
RabbitMQ集群对延迟非常敏感的,应当只用于局域网。广域网应该使用
Federation
或者Shovel
来代替。
RabbitMQ集群不能保证消息的万无一失。当集群中的一个RabbitMQ节点崩溃时,该节点上的所有消息也会丢失。RabbitMQ集群中的所有节点都会备份所有的元数据信息,元数据包括如下内容
- 队列的元数据:队列的名称及属性
- 交换机:交换机的名称及属性
- 绑定关系的元数据:交换机与队列或者交换机与交换机之间的绑定关系
- vhost元数据
RabbitMQ在集群中创建队列,集群只会在单个节点而不是所有节点上创建该队列的进程,只有该节点包含完整的队列信息(元数据,状态,内容)。这样只有队列的宿主(队列的所有者)知道队列的所有信息,其他的节点只知道队列的元数据和指向该队列的指针。因此,当集群中某个节点崩溃的时候,该节点上的队列进程和关联的绑定都会消失。该队列上的所有的订阅都会失效,并且任何匹配该队列的新消息也会丢失。那么为何RabbitMQ集群仅采用元数据同步的方式?
- 存储空间:如果每个集群节点都拥有所有Queue的完全数据拷贝,那么每个节点的存储空间会非常大,集群的消息积压能力会非常弱(无法通过集群节点的扩容提高消息积压能力);
- 性能:消息的发布者需要将消息复制到每一个集群节点,对于持久化消息,网络和磁盘同步复制的开销都会明显增加。
集群发送/订阅消息的基本原理
RabbitMQ集群的工作原理图如下:
RabbitMQ集群工作原理
一、客户端直接连接队列所在节点
- 如果有一个消息生产者或者消息消费者通过客户端连接至
节点一
进行消息的发布或者订阅,那么此时的集群中的消息的收发只与节点一
相关。
二、客户端连接的是非队列数据所在节点
- 如果消息生产者所连接的是
节点二
或者节点三
,此时队列1
的完整数据不在这两个节点上,那么在发送消息过程中这节点二
和节点三
主要起了路由转发作用,根据这两个节点上的元数据(指向队列1
所有者的指针)转发至节点一
上,最终发送的消息还是会存储至节点一
的队列1
上。 - 如果消息消费者所连接的
节点二
或者节点三
,那这两个节点也会作为路由节点起到路由转发作用,将会从节点一
的队列1
中拉取消息进行消费。
集群命令
-
rabbitmqctl join_cluster {cluster_node} [--ram]
将指定的节点加入加入到集群中,在这个命令前,要停止并重置RabbitMQ节点。 -
rabbitmqctl cluster_status
显示集群状态 -
rabbitmqctl change_cluster_node_type {disc|ram}
修改集群节点的类型 -
rabbitmqctl forger_cluster_node [--offline]
将节点从集群中删除 -
rabbitmqctl set_cluster_name {name}
设置集群名称。集群名称在客户端连接时会通知给客户端 -
rabbitmqctl force_boot
确保节点可以启动,即使它不是最后一个关闭的节点。
集群节点类型
集群中的节点有两种类型:
- 内存节点:将所有的元数据定义在内存中
- 磁盘节点:将所有的元数据定义在磁盘中
单个集群中必须至少有一个磁盘类型的节点,否则RabbitMQ重启之后,所有的系统配置信息都会丢失。节点类型涉及如下指令:
rabbitmqctl join_cluster name --{ram,disc}
rabbitmqctl change_cluster_node_type {ram,disc}
在集群中创建队列,交换机或绑定关系时候,这些操作直到所有集群节点都成功提交元数据后才会返回。
RabbitMQ只要求在集群中至少有一个磁盘节点。如果集群中的所有磁盘节点均崩溃了,那么集群可以继续发送或者接收消息,但是不再可以执行创建队列,交换机,绑定关系,用户等操作。
在内存节点重启后,它们会连接到预先配置的磁盘节点,下载当前集群元数据的副本。只要内存节点在集群中可以找到一个磁盘节点,那么就可以加入到集群中。
剔出集群中的单个节点
方法一
- 在节点上执行
rabbitmqctl stop_app
或rabbitmqctl stop
关闭RabbitMQ服务。 - 在集群中其他的节点上执行
rabbitmqctl forget_cluster_node nodexxx
- 如果集群中其他的节点处于未运行的状态,那么需要使用
rabbitmqctl forget_cluster_node nodexxx --offline
,添加offline
参数让起可以在非运行状态下提出节点
方法二
- 在节点上执行
rabbitmqctl stop_app
- 在节点上执行
rabbitmqctl reset
reset
命令将情况节点的状态,让其恢复到空白状态(该指令会清除节点上的包括消息在内的全部数据),并通知集群中其他的节点该节点正在离开集群。
集群节点升级
如果集群中多个节点升级,可以参考单个节点的升级的方式。
- 使用
rabbitmqctl stop
关闭所有节点的的RabbitMQ服务 - 保存各个节点的Mnesia数据
- 解压新版本的RabbitMQ到指定目录
- 指定新版本的Mnesia数据路径为旧版本备份路径(步骤2)
- 启动新版本服务。(最先启动最后关闭的那个节点)
集群注意事项
- 如果关闭了集群中的所有节点,那么在启动的时候,需要确保最后关闭的那台节点是要最先启动的,因为这个节点知道最多的事情。如果第一个启动的不是最后关闭的节点,那么这个节点也会等待(30秒,重试10次)最后关闭的那个节点启动。在重试失败之后,当前节点也会因为失败而关闭自身的应用,报错信息如下(例子):
BOOT FAILED
===========
Timeout contacting cluster nodes: [rabbit@node3,rabbit@node2].
BACKGROUND
==========
This cluster node was shut down while other nodes were still running.
To avoid losing data, you should start the other nodes first, then
start this one. To force this node to start, first invoke
"rabbitmqctl force_boot". If you do so, any changes made on other
cluster nodes after this one was shut down may be lost.
DIAGNOSTICS
===========
attempted to contact: [rabbit@node3,rabbit@node2]
rabbit@node3:
* unable to connect to epmd (port 4369) on node3: address (cannot connect to host/port)
rabbit@node2:
* unable to connect to epmd (port 4369) on node2: address (cannot connect to host/port)
Current node details:
* node name: rabbit@node1
* effective user's home directory: /var/lib/rabbitmq
* Erlang cookie hash: 753AvTJhsnDYrMG0hQ3YTg==
- 如果最后关闭的节点因为某种原因无法启动,则可以通过
rabbitmqctl_forget_cluster_node
来将其踢出集群。 - 如果集群中的所有节点由于某种原因,比如集群断电而关闭,那么集群中所有节点均会认为还有其他节点在其之后关闭,此时需要调用
rabbitmqctl forec_boot
来强制启动一个节点,之后集群才能正常启动。
五、集群迁移
RabbitMQ集群迁移主要包括三个步骤:元数据重建、数据迁移及客户端连接的切换。
元数据重建
元数据重建是指在新的集群中创建原集群的队列,交换机,绑定关系,vhost,用户,权限和Parameter等数据的信息。元数据重建是其他迁移步骤的前置任务。
有多种方法可以完成元数据重建,比如通过手动创建,或编写客户端程序。RabbitMQ推荐的方式是通过Web管理页面的Export/Import definitions
功能来完成。
但是Web管理页面迁移元数据的方式也有一些局限性,比如:
- RabbitMQ版本兼容。
- 需要使用
RabbitMQ Management
插件。 - 采用这种方法将元数据在新集群上重建,则所有的队列都只会落到同一个集群的节点上,而其他节点处于空置状态,这样所有的压力都会集中在单台节点之上。
数据迁移
数据迁移的具体策略要参考具体的业务场景。
- 一、参看[备份与还原]小结
- 二、通过程序切换
- 将生产者的客户端与原RabbitMQ集群断开,再与新的集群建立新的连接,这样新的消息会投递到新的集群中。
- 消费者客户端等待原集群中的消息全部消费完毕,然后断开连接,最后与新集群建立连接。
某些场景中需要将消费者迅速切换到新集群,这会导致原集群中遗留部分未被消费的消息。这种情况可以通过编写“中转”程序来解决(从原集群中消费消息,并作为生产者将消息投递到新集群中)
- 三、 使用zookeeper做自动化迁移(具体示例待完善)
六、备份与还原
每个RabbitMQ节点都有一个数据目录(data directory),该目录存储该节点上的所有数据。数据包含两种类型:definitions
(元数据,架构/拓扑)和消息存储(messages
)数据。
Definitions
节点和群集存储架构,元数据或拓扑等数据信息(用户,虚拟主机,队列,交换,绑定,运行时参数都属于此类) 。definitions
数据可以作为JSON
文件导出和导入。
definitions
存储在节点内部数据库中,并在所有群集节点之间复制。 集群中的每个节点都有一份完整的集群数据备份。 当definitions
的一部分更改时,所有节点均将再一个事务内完成数据的更新。这就意味着从集群中的任何一个节点,都已获取到集群的完整definitions
数据。
Messages
每个节点都有自己的数据目录,并存储其主节点(镜像队列涉及主节点)托管在该节点上的队列的消息。 messages
可以在镜像队列的各个节点之间复制。messages
存储在节点数据目录的子目录中。
Data Lifecycle
definitions
通常大多是静态的,而messages
却不断地从发布者流向消费者。执行备份时,第一步是确定是否仅备份definitions
或是一同备份messages
。 由于messages
是短暂的并且可能是瞬态的,因此不要从正在运行的节点下对其进行备份,并且可能导致数据快照不一致。definitions
只能从正在运行的节点上备份。
Definitions备份
definitions
数据可以通过导入或导出的方式进行备份,尽可能的使用这种方式来备份definitions
数据。具体方法参考指导教程。
也可以通过手动的方式进行definitions
数据备份,但是不推荐这样做。
Definitions are stored in an internal database located in the node's data directory. To get the directory path, run the following command against a running RabbitMQ node:
rabbitmqctl eval 'rabbit_mnesia:dir().'
如上指令输出的数据目录还将在子目录中包含messages数据。 如果不需要拷贝messages数据,可以不拷贝messages文件夹。
Restoring from a Manual Definitions Backup
Messages备份
在备份messages
数据前,需要将节点停止。messages
数据目前只能通过手动的方式进行备份。
In RabbitMQ versions starting with 3.7.0 all messages data is combined in the
msg_stores/vhosts
directory and stored in a subdirectory per vhost. Each vhost directory is named with a hash and contains a .vhost file with the vhost name, so a specific vhost's message set can be backed up separately.
从Rabbitmq3.7.0开始,所有的消息数据都被合并到msg_stores/vhosts
文件夹中,并且每一个vhost会以子文件夹的形式单独存储。每一个vhost文件夹都是以hash值的方式命名,文件夹内包含一个以vhost名字命名的.vhost
的文件。因此,每一个vhost的messages数据都可以独立备份。