记一次差点跑路的 RabbitMQ 运维经历

你好,我是看山。

这是一次比较苦逼的运维,完全不熟悉的系统、不清楚环境、不清楚配置,两眼一抹黑。为啥?就是因为原来的负责人撤了、交接人休假、再次交接人也休假,再再次交接人只有一份不全的文档。而我是再、再、再次交接人,连文档也没有。更要命的是,这是生产环境,大家都懂得,生产环境就是不能出问题,自封一个“奉命于危难之间”吧。

抱怨了一整段了,还是简单的说下这次运维吧,运维的是 RabbitMQ 集群,3 个节点 A、B、C,每个节点上启动了 2 个实例 a1/a2、b1/b2、c1/c2,其中 a1、b1、c1 组成一套集群环境 rabbit cluster1,a1 是磁盘节点;a2、b2、c2 组成一套集群环境 rabbit cluster2,c2 是磁盘节点。

就是因为完全不熟悉 RabbitMQ 集群,所以基本上趟了一堆的坑,碰到了一堆不应该出现的错误,也算是新手村长经验了。按照套路,这里先说说正确的启动方式,然后再说说碰到的异常。

1. 正确启动

1.1 启动各个节点

因为找不到自己启动的历史了,就是摘取 官网 的部分内容放在这里。有 3 个服务器节点,hostname 分别是:rabbit1、rabbit2、rabbit3。

分别在 3 个节点上启动 Rabbit MQ:

rabbit1$ rabbitmq-server -detached

rabbit2$ rabbitmq-server -detached

rabbit3$ rabbitmq-server -detached

通过命令rabbitmq-server -detached就可以启动 rabbit 服务。

然后在每个节点上查看集群状态:

rabbit1$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1]}]},{running_nodes,[rabbit@rabbit1]}]
...done.

rabbit2$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit2]}]},{running_nodes,[rabbit@rabbit2]}]
...done.

rabbit3$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit3 ...
[{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
...done.

RabbitMQ 服务节点的名字是 rabbit@shorthostname,所以上面 3 个节点分别是 rabbit@rabbit1、rabbit@rabbit2、rabbit@rabbit3。需要注意的是使用rabbitmq-server基本执行的名字都是小写的,如果是在 Windows 中使用rabbitmq-server.bat,那节点名字就是大写的了,比如 rabbit@RABBIT1。

1.2 创建集群

这里把 rabbit@rabbit2 和 rabbit@rabbit3 加入 rabbit@rabbit1 中,也就是说 rabbit@rabbit1 是磁盘节点,其他两个都是内存节点。

先把 rabbit@rabbit2 加入到 rabbit@rabbit1 中:

rabbit2$ rabbitmqctl stop_app
Stopping node rabbit@rabbit2 ...done.

rabbit2$ rabbitmqctl join_cluster rabbit@rabbit1
Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.

rabbit2$ rabbitmqctl start_app
Starting node rabbit@rabbit2 ...done.

如果没有报错,rabbit@rabbit2 就已经加入到 rabbit@rabbit1 中了,可以使用命令rabbitmqctl cluster_status查看集群状态:

rabbit1$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
...done.

rabbit2$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
...done.

通过join_cluster --ram可以指定节点以内存节点的形式加入集群。然后在 rabbit@rabbit3 上执行相同的操作即可,这里不再赘述。

1.3 拆分集群

拆分集群实际上就是在想要从集群中删除的节点上执行reset即可,他会通知集群中所有的节点不要再理这个节点了。

rabbit3$ rabbitmqctl stop_app
Stopping node rabbit@rabbit3 ...done.

rabbit3$ rabbitmqctl reset
Resetting node rabbit@rabbit3 ...done.

rabbit3$ rabbitmqctl start_app
Starting node rabbit@rabbit3 ...done.

在各个节点上查看集群状态:

rabbit1$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit1 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
...done.

rabbit2$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
 {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
...done.

rabbit3$ rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit3 ...
[{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
...done.

还可以在某节点上删除别的节点,可以使用forget_cluster_node来进行,这里不进行赘述,后面的一种异常中会用到这个命令。

2. 几个异常

2.1 一台机器上启动多个实例

RabbitMQ 允许在一台机器上启动多个实例,自己在这次运维中占用时间最长的就是不知道这 3 个节点上部署了两套集群,通过ps -ef|grep rabbit命令看到有两个实例,就天真的以为是有一个没有成功关闭,所以直接把两个都kill了。所以这里记录一下如果在一台机器上启动多个实例。

$ RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=hare rabbitmq-server -detached
$ rabbitmqctl -n hare stop_app
$ rabbitmqctl -n hare join_cluster rabbit@rabbit1
$ rabbitmqctl -n hare start_app

执行命令需要使用-n来指定执行命令的实例,这个是需要记住的。

这个已经找不到错误的具体描述了,就从 Google 上找了一条,基本类似。

rabbitmqctl cluster rabbit@vmrabbita
Clustering node rabbit@vmrabbitb with [rabbit@vmrabbita] ...
Error: {unable_to_join_cluster,
          [rabbit@vmrabbita],
          {merge_schema_failed,
              "Bad cookie in table definition rabbit_durable_queue: rabbit@vmrabbitb = {cstruct,rabbit_durable_queue,set,[],[rabbit@vmrabbitb],[],0,read_write,[],[],false,amqqueue,[name,durable,auto_delete,arguments,pid],[],[],{{1266,16863,365586},rabbit@vmrabbitb},{{2,0},[]}}, rabbit@vmrabbita = {cstruct,rabbit_durable_queue,set,[],[rabbit@vmrabbitc,rabbit@vmrabbita],[],0,read_write,[],[],false,amqqueue,[name,durable,auto_delete,arguments,pid],[],[],{{1266,14484,94839},rabbit@vmrabbita},{{4,0},{rabbit@vmrabbitc,{1266,16151,144423}}}}\n"}}

主要的就是Bad cookie in table definition rabbit_durable_queue这句,这是因为节点之间是通过“the Erlang Cookie”彼此识别的,存储在$HOME/.erlang.cookie 中。如果因为某种原因,集群中几个节点服务器上的 cookie 不一致,就会不能够彼此识别,出现这样那样的错误。更多的是上面的这个”Bad cookie。”,还会有”Connection attempt from disallowed node”、 “Could not auto-cluster”。

2.3 already_member

这个问题就是比较 2 的一个问题了,自己给自己挖的坑,只能自己填了。集群几个节点之间不能通信,然后我就把一个内存节点的var/lib/rabbitmq/mnesia中的文件夹删了,然后又执行了reset,当执行join_cluster命令的时候,就会报出错误:

Error: {ok,already_member}

分析一下可以明白,当前节点上没有待加入集群的信息,但是待加入集群中已经有了该节点的信息,但是发现两个信息不一致。所以当当前节点期望加入到集群的时候,出于安全考虑,集群就说你小子已经是集群里的一员了,不要再加了。扒出日志来看:

=INFO REPORT==== 25-Jul-2016::20:11:10 ===
Error description:
   {could_not_start,rabbitmq_management,
       {{shutdown,
            {failed_to_start_child,rabbit_mgmt_sup,
                {'EXIT',
                    {{shutdown,
                         [{{already_started,<23251.1658.0>},
                           {child,undefined,rabbit_mgmt_db,
                               {rabbit_mgmt_db,start_link,[]},
                               permanent,4294967295,worker,
                               [rabbit_mgmt_db]}}]},
                     {gen_server2,call,
                         [<0.618.0>,{init,<0.616.0>},infinity]}}}}},
        {rabbit_mgmt_app,start,[normal,[]]}}}

Log files (may contain more information):
   ./../var/log/rabbitmq/hare.log
   ./../var/log/rabbitmq/hare-sasl.log

既然集群中已经有个该节点信息,所以不要该节点重复加入。那就把集群里该节点信息删了,再加入集群,不就应该类似与一个全新的节点加入集群一样吗?

rabbitmqctl -n hare forget_cluster_node hare@rabbit1

这样,集群中就没有 hare@rabbit1 的信息了,之后就重新执行join_cluster命令即可。

2.4 千万不要在磁盘节点上删除 var/lib/rabbitmq/mnesia 中的文件

这个文件夹中的内容是磁盘节点用于记录集群信息的文件,一旦删除,会出现各种各样的异常。

  1. 如果是磁盘节点,集群中配置的 Exchanges、Queues、User 等信息全都丢失
  2. 如果是内存节点,连接集群的信息丢失,重新加入集群是会失败

因为是两套集群,这两个问题我都碰到了。生产环境啊,可以想象当我正在为集群正常启动后得意的时候,突然发现所有的 Exchanges、Queues 信息全都没了的时候的心情吗?幸亏还有一套预生产环境,直接把预生产环境的内容导出,然后在生产环境导入。

唉,有种劫后余生的感动。


你好,我是看山,公众号:看山的小屋,10 年老猿,开源贡献者。游于码界,戏享人生。关注我,领取资料。

个人主页:https://www.howardliu.cn
个人博文:记一次差点跑路的 RabbitMQ 运维经历
CSDN 主页:http://blog.csdn.net/liuxinghao
CSDN 博文:记一次差点跑路的 RabbitMQ 运维经历

公众号:看山的小屋