0.前言
目前手上的一个中长期任务就是消息队列的升级,继RocketMQ之后,开始对RabbitMQ进行研究,首先当然还是简单的原理了解加集群环境搭建。
1.集群原理
RabbitMQ 是一个由 Erlang 语言开发的 AMQP (Advanced Message Queue,高级消息队列协议)的开源实现,因为Erlang自带集群特性,因此RabbitMQ的集群也是基于此实现的。RabbitMQ的集群有两种模式,分别为默认集群模式和镜像模式。
-
默认模式:消息实体只存在于一个节点上,其它节点仅存储对应的元数据,当客户端连接到未存储消息实体的节点时,RabbitMQ会根据元数据从对应节点取得消息后再转发给客户端。这样做有一个显而易见的问题是,当某个节点宕机时,无论该节点是否有做消息持久化,存储在该节点的未消费消息都会暂时或者永久无法被消费。
镜像模式:镜像模式顾名思义就是将一个节点存储的消息在镜像节点上进行主动同步,以防止单个节点宕机造成的问题。当然这种模式的消息高可靠性也是建立在性能损耗上的,适用于对消息可靠性要求特别高的场合,如订单类消息。
本文后面的讲解都会基于默认的集群模式来进行。
1.1集群元数据的同步
当在集群中声明队列、交换器、绑定的时候,这些操作会直到所有集群节点都成功提交元数据变更后才返回。RabbitMQ集群会始终同步四种类型的内部元数据(类似索引):
- a.队列元数据:队列名称和它的属性;
- b.交换器元数据:交换器名称、类型和属性;
- c.绑定元数据:一张简单的表格展示了如何将消息路由到队列;
- d.vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性;
因此,当用户访问其中任何一个RabbitMQ节点时,通过rabbitmqctl查询到的queue/user/exchange/vhost等信息都是相同的。
1.2内存节点和磁盘节点
集群中的节点有内存节点和磁盘节点两种类型,内存节点虽然不写入磁盘,但是它的执行比磁盘节点要好。内存节点可以提供出色的性能,磁盘节点能保障配置信息在节点重启后仍然可用,那集群中如何平衡这两者呢?
RabbitMQ 只要求集群中至少有一个磁盘节点,所有其他节点可以是内存节点,当节点加入火离开集群时,它们必须要将该变更通知到至少一个磁盘节点。如果只有一个磁盘节点,刚好又是该节点崩溃了,那么集群可以继续路由消息,但不能创建队列、创建交换器、创建绑定、添加用户、更改权限、添加或删除集群节点。换句话说集群中的唯一磁盘节点崩溃的话,集群仍然可以运行,但知道该节点恢复,否则无法更改任何东西。
2.集群搭建
我的RabbitMQ集群搭建在两台centos6.9服务器上。这里我使用了最简单的通过yum方式安装RabbitMQ,由于RabbitMQ依赖于Erlang环境,通过yum的方式安装会自动处理依赖的程序,所以会自动安装好Erlang,对于不熟悉Erlang的人来说省去了很多麻烦。
2.1单机安装
首先看一下yum源中的RabbitMQ版本,通过下述命令查询
yum list | grep rabbitmq
查到最新的安装包为rabbitmq-server.noarch,版本为3.1.5-1.el6,虽然不是最新的,但暂时应该够用了,通过以下命令安装
yum -y install rabbitmq-server.noarch
安装完成后首先通过erl -v
命令检查Erlang环境是否已安装
Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]
Eshell V5.8.5 (abort with ^G)
1>
可以看到R14B04版本已经安装完成,这也是目前yum源自带的最高版本了,如果要安装更高版本的RabbitMQ这个Erlang版本是肯定不够的,就需要再另外自行安装更高版本Erlang。这里顺带提一下,要退出Erlang的控制台需要输入halt().
命令,否则只能通过ctrl+z
强行退出。
2.2服务启动
首先打开服务
chkconfig rabbitmq-server on
启动RabbitMQ
service rabbitmq-server start
查看状态
rabbitmqctl status
看到如下信息则证明RabbitMQ已正常启动
Status of node rabbit@host05 ...
[{pid,8927},
{running_applications,[{rabbit,"RabbitMQ","3.1.5"},
{os_mon,"CPO CXC 138 46","2.2.7"},
{mnesia,"MNESIA CXC 138 12","4.5"},
{xmerl,"XML parser","1.2.10"},
{sasl,"SASL CXC 138 11","2.1.10"},
{stdlib,"ERTS CXC 138 10","1.17.5"},
{kernel,"ERTS CXC 138 10","2.14.5"}]},
{os,{unix,linux}},
{erlang_version,"Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:30] [kernel-poll:true]\n"},
{memory,[{total,27533368},
{connection_procs,2704},
{queue_procs,5408},
{plugins,0},
{other_proc,9049416},
{mnesia,61424},
{mgmt_db,0},
{msg_index,34160},
{other_ets,798568},
{binary,5312},
{code,14419209},
{atom,1355273},
{other_system,1801894}]},
{vm_memory_high_watermark,0.4},
{vm_memory_limit,1607422771},
{disk_free_limit,1000000000},
{disk_free,18049622016},
{file_descriptors,[{total_limit,924},
{total_used,3},
{sockets_limit,829},
{sockets_used,1}]},
{processes,[{limit,1048576},{used,124}]},
{run_queue,0},
{uptime,74460}]
...done.
2.3加入集群
首先有两个点需要注意:
- RabbitMQ节点的命名一般是
rabbit@服务器host
,因此需要在/etc/hosts文件中配置对应的ip和hostname,防止host无法解析的情况; - RabbitMQ集群是基于Erlang的集群的,因此需要保证Erlang节点间的互通。Erlang的节点互通需要节点之间有相同的cookie,因此将节点2的
/var/lib/rabbitmq/.erlang.cookie
修改为和节点1一样,同时为保证cookie文件的权限,需要执行以下命令:
chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
chmod 400 /var/lib/rabbitmq/.erlang.cookie
接着将节点2加入到节点1的集群中,首先需要停止节点2上的应用(不是停止Erlang节点),否则会报以下错误
Clustering node rabbit@host05 with rabbit@host04 ...
Error: mnesia_unexpectedly_running
在节点2上运行以下命令
rabbitmqctl stop_app
清楚节点2上的队列信息
rabbitmqctl reset
加入集群
rabbitmqctl join_cluster rabbit@节点1
如果要以内存节点模式加入则执行以下命令
rabbitmqctl join_cluster --ram rabbit@节点1
重新启动节点2上的应用
rabbitmqctl start_app
查看集群状态
rabbitmqctl cluster_status
看到如下显示证明集群建立成功
Cluster status of node rabbit@host04 ...
[{nodes,[{disc,[rabbit@host04,rabbit@host05]}]},
{running_nodes,[rabbit@host05,rabbit@host04]},
{partitions,[]}]
...done.
3.管理页面
3.1页面启动
rabbitmq-plugins enable rabbitmq_management
RabbitMQ很贴心的自带管理页面,该页面属于一种插件功能,通过上述命令启动,启动后默认通过http://ip:15672
登陆访问
这里顺带提一下其它默认的端口:
- 4369 (epmd)
- 25672 (Erlang distribution)
- 5672, 5671 (AMQP 0-9-1 without and with TLS)
- 15672 (if management plugin is enabled)
- 61613, 61614 (if STOMP is enabled)
- 1883, 8883 (if MQTT is enabled)
3.2创建账号
rabbitmqctl add_user admin admin
登录管理页面需要创建账号,如上所示命令用于创建admin/admin账号
3.3赋予角色
rabbitmqctl set_user_tags admin administrator
上述命令将administrator角色赋给admin用户,至此就可以通过admin/admin账号进行登陆了
3.4其它命令
其它常用用户管理命令如下:
- list_users,用户列表
- add_user {username} {password},添加用户
- delete_user {username},删除用户
- change_password {username} {newpassword},修改密码
- clear_password {username},删除密码,密码删除后就不能访问了
- authenticate_user {username} {password},用户认证
- set_user_tags {username} {tag ...},为用户设置角色,tag可以是0个、一个、或多个
常用权限管理命令如下:
- list_vhosts [vhostinfoitem ...],获取vhosts列表
- add_vhost {vhost}, eg:rabbitmqctl add_vhost test
- delete_vhost {vhost}
- set_permissions [-p vhost] {user} {conf} {write} {read},给用户分在对应的vhost上分配相应的权限。eg:rabbitmqctl set_permissions -p /myvhost chris "^chris-." "." ".*",这条指令,给用户chris在myvhost分配了权限,权限包括:以"chris-"开头的全部资源的配置权限,和所有资源的读写权限
- clear_permissions [-p vhost] {username},清除权限
- list_permissions [-p vhost],vhost权限分配列表
- list_user_permissions {username},user权限列表
4.遇到的问题
服务无法启动,并报了如下的错误:
BOOT FAILED
===========
Error description:
{error,{cannot_log_to_file,"/var/log/rabbitmq/[email protected]",
{error,eacces}}}
Log files (may contain more information):
/var/log/rabbitmq/[email protected]
/var/log/rabbitmq/[email protected]
Stack trace:
[{rabbit,ensure_working_log_handler,5},
{rabbit,ensure_working_log_handlers,0},
{rabbit,'-boot/0-fun-1-',0},
{rabbit,start_it,1},
{init,start_it,1},
{init,start_em,1}]
{"init terminating in do_boot",{rabbit,failure_during_boot,{error,{cannot_log_to_file,"/var/log/rabbitmq/[email protected]",{error,eacces}}}}}
Crash dump was written to: erl_crash.dump
init terminating in do_boot ()
查阅资料发现是rabbitmq没有/var/log/rabbitmq目录的权限造成的,使用chown -R rabbitmq:rabbitmq /var/log/rabbitmq/
赋予权限后再次启动,问题解决,但问题造成的原因未知。