rabbitmq安装及基本操作(含集群配置)

一、rabbitmq的安装

因为rabbitmq是基于 erlang语言开发,所有要先安装erlang

1、安装erlang

这里我下载的是19.2的版本,地址为https://www.erlang.org/downloads/19.2

下载编译安装包:

cd  /mnt
wget http://erlang.org/download/otp_src_19.2.tar.gz

解缩

tar  -zxvf otp_src_19..tar.gz

安装编译依赖

yum -y install gcc gcc-c++ autoconf automake  zlib zlib-devel openssl openssl-devel pcre-devel  ncurses-devel

编译安装

cd /mnt/otp_src_19. && ./configure  --prefix=/opt/er1ang && make  &&  make   install 

设置环境变量

修改 /etc/profile在最后添加如下代码

ERLANG_HOME=/opt/erlang
export PATH=$PATH:$ERLANG_HOME/bin
export ERLANG_HOME source /etc/profile

2、rabbitmq安装

进入官网选择下载的版本

http://www.rabbitmq.com/releases/rabbitmq-server/

我选择的是3.6.10版本

cd /opt

wget  http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-generic-unix-3.6.10.tar.xz

xz  -d rabbitmq-server-generic-unix-3.6.10.tar.xz

tar -xvf rabbitmq-server-generic-unix-3.6.10.tar

mv rabbitmq-server-generic-unix-3.6.10   rabbitmq

设置环境变量

编辑/etc/profile文件,在最后添加如下代码

export PATH=$PATH:/opt/rabbitmq/sbin
export RABBITMQ_HOME=/opt/rabbitmq source /etc/profile

运行rabbitmq

rabbitmq-server -detached

查看启动状态 rabbitmqctl status

[root@node-2 opt]# rabbitmqctl status
Status of node 'rabbit@node-2'
[{pid,27273},
{running_applications,
[{rabbit,"RabbitMQ","3.6.10"},
{mnesia,"MNESIA CXC 138 12","4.14.3"},
{ranch,"Socket acceptor pool for TCP protocols.","1.3.0"},
{ssl,"Erlang/OTP SSL application","8.1.1"},
{public_key,"Public key infrastructure","1.4"},
{crypto,"CRYPTO","3.7.3"},
{os_mon,"CPO CXC 138 46","2.4.2"},
{rabbit_common,
"Modules shared by rabbitmq-server and rabbitmq-erlang-client",
"3.6.10"},
{xmerl,"XML parser","1.3.13"},
{compiler,"ERTS CXC 138 10","7.0.4"},
{asn1,"The Erlang ASN1 compiler version 4.0.4","4.0.4"},
{syntax_tools,"Syntax tools","2.1.1"},
{sasl,"SASL CXC 138 11","3.0.3"},
{stdlib,"ERTS CXC 138 10","3.3"},
{kernel,"ERTS CXC 138 10","5.2"}]},
{os,{unix,linux}},
{erlang_version,
"Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:2:2] [async-threads:64] [hipe] [kernel-poll:true]\n"},
{memory,
[{total,49620264},
{connection_readers,0},
{connection_writers,0},
{connection_channels,0},
{connection_other,0},
{queue_procs,2832},
{queue_slave_procs,0},
{plugins,0},
{other_proc,17182792},
{mnesia,61040},
{metrics,184080},
{mgmt_db,0},
{msg_index,43528},
{other_ets,2088952},
{binary,49848},
{code,21385570},
{atom,891849},
{other_system,7911021}]},
{alarms,[]},
{listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]},
{vm_memory_high_watermark,0.4},
{vm_memory_limit,779028070},
{disk_free_limit,50000000},
{disk_free,45861027840},
{file_descriptors,
[{total_limit,924},{total_used,2},{sockets_limit,829},{sockets_used,0}]},
{processes,[{limit,1048576},{used,152}]},
{run_queue,0},
{uptime,5047},
{kernel,{net_ticktime,60}}]

二、rabbitmq管理

1、多租户与权限

每一个RabbitMQ 服务器都能创建虚拟的消息服务器,我们称之为虚拟主机 (virtual host) , 简称为 vhost 。每一个 vhost 本质上是一个独立的小型 RabbitMQ 服务器,拥有自己独立的队列、 交换器及绑定关系等,井且它拥有自己独立的权限。 vhost 就像是虚拟机与物理服务器一样,它 们在各个实例间提供逻辑上的分离,为不同程序安全保密地运行数据,它既能将同 一个 RabbitMQ 中的众多客户区分开,又可以避免队列和交换器等命名冲突。 vhost 之间是绝对隔离 的,无法将 vhostl 中的交换器与 vhost2 中的队列进行绑定,这样既保证了安全性,又可以确保 可移植性。如果在使用 RabbitMQ 达到一定规模的时候,建议用户对业务功能、场景进行归类 区分,并为之分配独立的 vhost。

vhost是AMQP 概念的基础,客户端在连接的时候必须制定一个 vhost. RabbitMQ 默认创 建的 vhost为 “/”, 如果不需要多个 vhost 或者对 vhost 的概念不是很理解,那么用这个默认的 vhost 也是非常合理的,使用默认的用户名 guest 和密码 guest 就可以访问它。但是为了安全和 方便,建议重新建立 个新的用户来访问它。

  可以使用 rabbitmqctl add vhost {vhost} 命令创建一个新的 vhost ,大括号里的参 数表示 vhost 的名称。

[root@node- opt]# rabbitmqctl  add_vhost  vhost
Creating vhost "vhost"

查看vhosts列表

[root@node- opt]# rabbitmqctl  list_vhosts
Listing vhosts
/
vhost

对应的删除 vhost 的命令是: rabbitmqctl delete_vhost  {vhost) ,其中大括号里 面的参数表示 vhost 的名称。删除一个 vhost 同时也会删除其下所有的队列、交换器、绑定关系、 用户权限、参数和策略等信息。

[root@node- opt]# rabbitmqctl  delete_vhost  vhost
Deleting vhost "vhost"
[root@node- opt]#
[root@node- opt]# rabbitmqctl list_vhosts
Listing vhosts
/

AMQP 协议中并没有指定权限在 host 级别还是在服务器级别实现,由具体的应用自定义。 在RabbitMQ 中,权限控制则是以 host 为单位的 。当创建一个用户时,用户通常会被指派给至少一个 vhost ,并且只能访问被指派的 vhost 内的队列、交换器和绑定关系等。因此, RabbitMQ 中的授予权限是指在 vhost 级别对用户而言的权限授予。

相关的授予权限命令为: rabbitmqctl set permissions [-p vhost] {user) {conf) {write) {read) 。其 中各个参数的含义如下所述。 ~ vhost 授予用户访问权限的 host 名称,可以设置为默认值,即vhosts为 “/”

user:可以访问指定 vhost 的用户名。

conf: 一个用于匹配用户在哪些资源上拥有可配置权限的正则表达式。

write:一个用于匹配用户在哪些资源上拥有可写权限的正则表达式。

read:一个用于匹配用户在哪些资源上拥有可读权限的正则表达式

rabbitmq安装及基本操作(含集群配置)

授予root用户可以访问虚拟主机vhost,并且在所有资源上都具备可配置、可读、可写的权限,代码如下:

[root@node- ~]# rabbitmqctl set_permissions -p vhost root ".*" ".*" ".*"
Setting permissions for user "root" in vhost "vhost"

授予root用户可访问虚拟主机vhost2,在以“queue”开头的资源上具备可配置权限,并且在所有资源上拥有可写、可读权限

[root@node- ~]# rabbitmqctl  set_permissions  -p vhost2 root "^queue.*" ".*" ".*"
Setting permissions for user "root" in vhost "vhost2"

在 RabbitMQ 中有两个 Shell 命令可以列举权限信息。第一个命令是 rabbitmqctl list_permissions [-p vhost) ,用来显示虚拟主机上的权限;第二个命令是 rabbitmqctl list_user permissions {username) ,用来显示用户的权限。

rabbitmq安装及基本操作(含集群配置),

清除也是在vhost级别对用户而言的,清除权限的命令为clear_permissions [-p <vhost>] <username>  其中vhost用于设置禁止用户访问的虚拟主机名称,默认为“/”, username为禁止访问虚拟主机的用户名

[root@node- ~]# rabbitmqctl clear_permissions -p vhost root
Clearing permissions for user "root" in vhost "vhost"

2、用户管理

在RabbitMQ 中,用 户是访问控制 (Access Control)的基本单元,且单个用户可以跨越 vhost 进行授权。针对一至多个 vhost ,用户可以被赋予不同级别的访问权限,并使用标准的 用户名和密码来认证用户。

创建用户的命令为 rabbitmqctl add user {username} {password}   ,username表示创建的用户,password表示为创建用户的登录密码

创建一个用户为root,密码为root的用户

rabbitmqct1 add_user root root

可以通过 rabbitmqctl change_password  {username}  {newpassword} 命令来更 改指定用户的密码 其中 username 表示要变更密码的用户名称, newpassword 表示要变更的新的密码。

rabbitmq安装及基本操作(含集群配置)

同样可以清除密码,这样用户就不能使用密码登录了,对应的操作命令为 rabbitmqctl clear_password {username} ,其中 username 表示要清除密码的用户名称。

使用 rabbitmqctl    authentiçate_user {userηame} {password} 可以通过密码来验证用户,其中 username 表示需要被验证的用户名称, password 表示密码

下面示例中分别采用root123和root321来验证root用户

[root@node- ~]# rabbitmqctl  authenticate_user   root root321
Authenticating user "root"
Error: failed to authenticate user "root"
[root@node- ~]#
[root@node- ~]# rabbitmqctl authenticate_user root root123
Authenticating user "root"
Success

删除用户命令为  rabbitmqctl  delete_user <username>

删除root用户

[root@node- ~]# rabbitmqctl delete_user root
Deleting user "root"

罗列所有用户

[root@node- ~]# rabbitmqctl   list_users
Listing users
guest [administrator]

用户的角色分为 5种类型。

none:无任何角色。新创建的用户的角色默认为none

management:可以访问 Web 管理页面。

policymaker:包含 management 的所有权限。

monitoring:包含 management 的所有权限,并且可以看到所有连接、信道及节点相关的信息。

administartor:包含 monitoring 的所有权限,井且可以管理用户、 机、 权限、策略、参数等。 administator 代表了最高的权限。

用户的角色可以通过rabbitmqctl  set_user_tags <username> <tag> 设置,其中 username 参数表示需要设置角色的用户名称;tag 参数用于设置 0个、1 个或者多个的角色,设置之后任何之前现有的身份都会被删除。

[root@node- ~]# rabbitmqctl  list_users
Listing users
guest [administrator]
root []
[root@node- ~]# rabbitmqctl set_user_tags root monitoring
Setting tags for user "root" to [monitoring]
[root@node- ~]# rabbitmqctl list_users -q
guest [administrator]
root [monitoring]
[root@node- ~]# rabbitmqctl set_user_tags root policymaker,management
Setting tags for user "root" to ['policymaker,management']
[root@node- ~]#
[root@node- ~]# rabbitmqctl list_users -q
guest [administrator]
root [policymaker,management]

3、web端管理

  前面讲述的都是使用 rabbitmqctl 工具来管理 RabbitMQ,有些时候是否会觉得这种方式是不是不太友好?而且为了能够运行 rabbitmqctl 工具,当前的用户需要拥有访问 Erlang cookie 的权限,由于服务器可能是 guest 或者 root 用户身份来运行的,因此你需要获得这些文件的访问权限 ,这样就引申出来 些权限管理的问题。

  RabbitMQ开发团队也考虑到了这种情况,并且开发了 RabbitMQ management 插件 RabbitMQ management 插件同样是由 Erlang 语言编写 的,并且和 RabbitMQ 服务运行在同一个 Erlang 虚拟机中。

为root用户设置所有权限

  RabbitMQ management 插件可以提供 Web 管理界面用来管理如前面所述的虚拟主机、用 户等,也可以用来管理队列、交换器 、绑定关系、策略、 参数等 ,还可以用来监控 RabbitMQ 服务的状态及一些数据统计类信息,可谓是功能强大,基本上能够涵盖所有 RabbitMQ 管理的 功能。

  在使用 Web 管理界面之前需要先启用 RabbitMQ management 插件, RabbitMQ 提供了很多 的插件,默认存放在 $RABBITMQ_HOME /plugins 目录下,如下所示

rabbitmq安装及基本操作(含集群配置)

上面以.ez结尾的都是rabbitmq的插件,其中rabbitmq_management-3.6.10.ez就是rabbitmq management插件,启动插件的命令是rabbitmq-plugins,其语法格式为

rabbitmq-plugins [-n <node>] <command> [<command options>] 启动插件是使用rabbitmq-plugins    enable    [plugin-name]  关闭插件的命令是 rabbitmq-plugins    disable   [plugin-name]

执行rabbitmq-plugins enable rabbitmq_management来启动rabbitmq management插件

[root@node- plugins]# rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
amqp_client
cowlib
cowboy
rabbitmq_web_dispatch
rabbitmq_management_agent
rabbitmq_management Applying plugin configuration to rabbit@node-... started plugins.

可以通过 rabbitmq-plugins list 命令来查看当前插件的使用情况 如下所示。其中 标记为 [E*]的为显式启动,而 [e*] 为隐式启动, 如显式启动 rabbitmq_management 插件 ,会同时隐式启动 amqp_client 、cowboy、 cowlib rabbitmq_management_age、rabbitmq_web_dispatch 等另外 5个插件。

[root@node- plugins]# rabbitmq-plugins list
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@node-
|/
[e*] amqp_client 3.6.
[e*] cowboy 1.0.
[e*] cowlib 1.0.
[ ] rabbitmq_amqp1_0 3.6.
[ ] rabbitmq_auth_backend_ldap 3.6.
[ ] rabbitmq_auth_mechanism_ssl 3.6.
[ ] rabbitmq_consistent_hash_exchange 3.6.
[ ] rabbitmq_event_exchange 3.6.
[ ] rabbitmq_federation 3.6.
[ ] rabbitmq_federation_management 3.6.
[ ] rabbitmq_jms_topic_exchange 3.6.
[E*] rabbitmq_management 3.6.
[e*] rabbitmq_management_agent 3.6.
[ ] rabbitmq_management_visualiser 3.6.
[ ] rabbitmq_mqtt 3.6.
[ ] rabbitmq_recent_history_exchange 3.6.
[ ] rabbitmq_sharding 3.6.
[ ] rabbitmq_shovel 3.6.
[ ] rabbitmq_shovel_management 3.6.
[ ] rabbitmq_stomp 3.6.
[ ] rabbitmq_top 3.6.
[ ] rabbitmq_tracing 3.6.
[ ] rabbitmq_trust_store 3.6.
[e*] rabbitmq_web_dispatch 3.6.
[ ] rabbitmq_web_mqtt 3.6.
[ ] rabbitmq_web_mqtt_examples 3.6.
[ ] rabbitmq_web_stomp 3.6.
[ ] rabbitmq_web_stomp_examples 3.6.
[ ] sockjs 0.3.
[root@node- plugins]#

默认guest用户只能在本机*问进入管理页面,其他用户需设置

[root@node- plugins]# rabbitmqctl set_user_tags  root  administrator
Setting tags for user "root" to [administrator]
[root@node- plugins]#
[root@node- plugins]# rabbitmqctl list_users
Listing users
guest [administrator]
root [administrator]
[root@node- plugins]#

浏览器中输入http://192.168.10.129:15672

输入root的用户名和密码成功登录

rabbitmq安装及基本操作(含集群配置)

三、rabbitmq集群搭建

这里演示为三台服务器搭建的一个集群环境,每台服务器上都安装rabbitmq

1、绑定hosts(三台都绑定)

vi  /etc/hosts

192.168.10.129 node-1
192.168.10.130 node-2
192.168.10.131 node-3

2、修改每台服务器上的.erlang.cookie

以节点 node-1的.erlang.cookie为目标,修改node2和node3上的.erlang.cookie

注意:如不能保存,先chmod  600 .erlang.cookie  修改后在改回400权限chmod  400 .erlang.cookie

3、首先启动 node-l node-2 node-3 节点 的rabbitmq服务

[root@node- sbin]# rabbitmq-server -detached
[root@node- sbin]# rabbitmq-server -detached
[root@node- sbin]# rabbitmq-server -detached

4、集群加入

接下来为了将每个节点组成一个集群,需要以 node-l 节点为基准,将 node-2 、node-3 节点 加入 node-l 节点的集群中。这3 个节点是平等的,如果想调换彼此的加入顺序也未尝不可。首 先将 node2 节点加入 nodel 节点的集群中,需要执行如下4 个命令步骤。

[root@node- sbin]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-2'
[root@node- sbin]#
[root@node- sbin]# rabbitmqctl reset
Resetting node 'rabbit@node-2'
[root@node- sbin]# rabbitmqctl join_cluster rabbit@node-
Clustering node 'rabbit@node-2' with 'rabbit@node-1'
[root@node- sbin]#
[root@node- sbin]# rabbitmqctl start_app
Starting node 'rabbit@node-2'
[root@node- sbin]#
[root@node- sbin]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-2'
[{nodes,[{disc,['rabbit@node-1','rabbit@node-2']}]},
{running_nodes,['rabbit@node-1','rabbit@node-2']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-1',[]},{'rabbit@node-2',[]}]}]
[root@node- sbin]#
[root@node- sbin]#

最后将 node-3 节点也加入 node-l 节点所在的集群中,这3 个节点组成了一个完整的集群。 在任意一个节点中都可以看到如下的集群状态。

[root@node- ~]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-3'
[root@node- ~]#
[root@node- ~]# rabbitmqctl reset
Resetting node 'rabbit@node-3'
[root@node- ~]# rabbitmqctl join_cluster rabbit@node-
Clustering node 'rabbit@node-3' with 'rabbit@node-1'
[root@node- ~]# rabbitmqctl start_app
Starting node 'rabbit@node-3'
[root@node- ~]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-3'
[{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]},
{running_nodes,['rabbit@node-1','rabbit@node-2','rabbit@node-3']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-1',[]},{'rabbit@node-2',[]},{'rabbit@node-3',[]}]}]
[root@node- ~]#
[root@node- ~]#

如果在node-2上用命令rabbitmqctl stop_app关闭一个节点,在node-1和node-3上查看集群节点信息

node-2上关闭

[root@node- sbin]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-2'
[root@node- sbin]#

node-1上查看集群情况

[root@node- ~]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-1'
[{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]},
{running_nodes,['rabbit@node-3','rabbit@node-1']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-3',[]},{'rabbit@node-1',[]}]}]

node-3上查看集群情况

[root@node- ~]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-1'
[{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]},
{running_nodes,['rabbit@node-3','rabbit@node-1']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-3',[]},{'rabbit@node-1',[]}]}]

如果关闭了集群中的所有节点,必须保证最后关闭的节点第一个启动,如,我按照先后顺序关闭了node-1、node-2、node-3 也就是node-3是最后一个关闭的。那么在启动是先启动node-3节点,node-1和node-2谁先启动都可以

否则机会启动失败(10次30秒的等待时间)

如果最后一个关闭的节点最终由于某些异常而无法启动,则可以通过 rabbitrnqctl  forget_cluster_node 命令来将此节点剔出当前集群。如果集 群中的所有节点由于某些非正常因素,比如断电而关闭,那么集群中的节点都会认为还有其他 节点在它后面关闭,

此时需要调用 rabbitrnqctl  force_boot 命令来启动一个节点,之后 集群才能正常启动。

四、集群节点类型

  在使用 rabbitrnqctl cluster_status 命令来查看集群状态时会有 {nodes [{disc , [rabbit@node-l , rabbit@node-2 , rabbit@node-3] 这一项信息,其中的 disc 标注了 RabbitMQ 节点的类型。 RabbitMQ 中的每一个节点,不管是单一节点系统或者是集群中的一部分 要么是内存节点,要么是磁盘节点。 内存节点将所有的队列、 交换器、绑定关系、用户、权限和 host 的元数据定义都存储在内存中,而磁盘节点则将这些信息存储到磁盘中。单节点的集群中必然只有 磁盘类型的节点,否则当重启 RabbitMQ之后,所有关于系统的配置信息都会丢失。不过在集群中, 可以选择配置部分节点为内存节点,这样可以获得更高的性能。

比如将 node-2 节点加入 nod 节点的时候可以指定 node-2节点的类型为内存节点。

[root@node-2 /] rabbitmqctl join_cluster rabbit@node-l --ram 

这样在以 node-l和 node-2 组成的集群中就会有一个磁盘节点和一个内存节点,默认不添加 "-ram"参数则表示此节点为磁盘节点。

如果集群已经搭建好了,那么也可以使用 rabbitmqctl   change_cluster_node_type  {disc ,ram} 命令来切换节点的类型,其中 disc 表示磁盘节点,而 ram 表示内存节点。举例, 这里将上面 node-2 节点由磁盘节点转变为内存节点。

[root@node- /]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-2'
[root@node- /]# rabbitmqctl change_cluster_node_type ram
Turning 'rabbit@node-2' into a ram node
[root@node- /]#
[root@node- /]# rabbitmqctl start_app
Starting node 'rabbit@node-2'
[root@node- /]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-2'
[{nodes,[{disc,['rabbit@node-3','rabbit@node-1']},{ram,['rabbit@node-2']}]},
{running_nodes,['rabbit@node-1','rabbit@node-3','rabbit@node-2']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-1',[]},{'rabbit@node-3',[]},{'rabbit@node-2',[]}]}]

五、从集群中剔除单个节点

有两种方式将一个节点从集群中剔除,比如将node-2从集群中踢出。

1、第一种方法

在node-2上关闭服务rabbitmqctl stop_app,在node-1或者node-3上执行

[root@node- ~]# rabbitmqctl forget_cluster_node rabbit@node-
Removing node 'rabbit@node-2' from cluster
[root@node- ~]#
[root@node- ~]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-3'
[{nodes,[{disc,['rabbit@node-1','rabbit@node-3']}]},
{running_nodes,['rabbit@node-1','rabbit@node-3']},
{cluster_name,<<"rabbit@node-1">>},
{partitions,[]},
{alarms,[{'rabbit@node-1',[]},{'rabbit@node-3',[]}]}]

2、第二种方法(推荐此方法)

直接在node-2节点上执行

[root@node- /]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-2'
[root@node- /]# rabbitmqctl reset
Resetting node 'rabbit@node-2'
[root@node- /]#
[root@node- /]# rabbitmqctl start_app
Starting node 'rabbit@node-2'
[root@node- /]#
[root@node- /]# rabbitmqctl cluster_status
Cluster status of node 'rabbit@node-2'
[{nodes,[{disc,['rabbit@node-2']}]},
{running_nodes,['rabbit@node-2']},
{cluster_name,<<"rabbit@node-2">>},
{partitions,[]},
{alarms,[{'rabbit@node-2',[]}]}]
[root@node- /]#
上一篇:SDL音频播放


下一篇:Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals) B. The Meeting Place Cannot Be Changed