Julia并行计算笔记(五)

八、集群管理器(ClusterManager)

特意追加一节来介绍集群管理器。集群管理器也是一个抽象对象,类似于抽象数组AbstractArray对象。

Julia里的抽象对象相当于“模板”。这种模板可以添加自定义内部变量派生出许多子类型对象。抽象数组和集群管理器都是一种抽象对象。当我们在下文中提到ClusterManager时,要记得它是抽象对象,别搞糊涂了。

在Julia里,主进程和Worker联网组成的结构称为一个“集群”,是ClusterManager抽象对象的一个子类型,无论单机或多机。主进程和Worker的概念我们已经很熟悉了。主进程(master process)恒有PID=1,只有在这个进程上可以增加或移除其他进程。从这个角度上说,Julia的集群管理是单向的,集群管理器相当于主进程的助理。不过,任意两个进程之间都可以互相通讯,所以通讯是平等的。

集群可以在单台机器或多台机器上创建。为了避免混淆,我们称单台机器上的集群为“单机集群”,在多台机器上称为“多机集群”。单机的情况大概是相当于把单机虚拟为多机,故又称为“虚拟集群”。

Julia的集群管理器,是包括了管理单机集群和多机集群的一组函数和命令。前者就是我们已经认识的各种进程操作的汇总。后者也是一样的操作,但为了管理多机器而加入了一些特殊命令。

所谓的集群管理器的功能有三个方面:

  1. 在集群环境中启动Worker。
  2. 管理每个Worker的生命周期,比如发送中断信号。
  3. (可选)提供数据传输。

多机集群的进程间的连接基于内置的TCP/IP传输协议,连接过程的内部原理是这样的:

  • 首先,在主进程上调用带有ClusterManager对象的addprocs
  • addprocs调用某种合适的方法在合适的机器上启动所需数量的Worker。
  • 每个Worker会拨出一个空闲端口,把自己的host和端口信息写入stdout
  • 集群管理器读取每个Worker的stdout并传给主进程。
  • 主进程解析信息并设置Worker的TCP/IP连接。
  • 集群里的每个进程都会被告知其他进程的连接信息。
  • 每个进程都与PID更小的所有进程连接,照此方式组成一个两两联通的网络。

这些过程都是隐式的,用户真正要做的显式操作就是addprocs(参数),分为三种情况:

  • 假如参数为整数n,那么它会构建一个有n个Worker的单机集群。为空则默认n=Sys.CPU_THREADS,此时总进程数等于CPU逻辑线程数+1。
  • 假如参数为hostname::Array,即各机器的hostname的数组,那么它会构建一个多机集群。在官方的标准库文档里,又写作addprocs(machines),其中machines是一个由”机器参数“组成的向量,每个机器参数对应启动一台机器。机器参数的格式为:字符串machine_spec或元组(machine_spec,count)。具体地,字符串machine_spec=[user@]hostname[:port] [bind_addr[:port]],其中[]表示可省略。hostname是唯一必写的,user默认为当前用户,port默认为标准SSH端口,它们共同提供了主进程和目标Worker之间的连接信息。假如写了bind_addr[:port],那么其他Worker将可以通过bind_addr[:port]连接到这个Worker上,也就是说这是一个自定义的额外地址和端口。元组参数的第二个元素count是整数,表示要在该机器上创建几个Worker。如果令count=:auto(注意冒号),那么会创建等于该机器CPU逻辑线程总数的Worker。
  • 假如参数为manager::ClusterManager,那么会创建一个自定义的集群。这里的manager::ClusterManager是一个自定义的ClusterManager。官方文档中给的例子是:扩展包ClusterManagers.jl中通过一个自定义ClusterManager构造了一个所谓的“Beowulf集群”。具体怎么自定义恐怕要去翻翻源代码。

小贴士:可以在Windows或Linux的终端中输入hostname命令查看该机器的hostname。

最后,创建好集群后就可以按照前几节讲的命令操作各进程了,无论单机或多机。

还有个--machinefile命令,用于在启动Julia REPL时连接各机器。用法为在终端输入:

julia --machinefile 文件路径
# 或简写为
julia -m 文件路径

其中文件路径是指向一个自定义文件的路径。这个文件由自己创建,内容是每台机器的hostname或IP地址,一行只写一台机器。例如在julia可执行文件的目录里放一个名为machinefile的文件然后:

julia --m ~/machinefile

其中machinefile文件的内容是两台电脑的hostname:

host1
host2

现在我们有了两种方式创建多机集群:addprocs(machines)--machinefile。我读到一篇博客在吐槽后一种方式,观点大概是酱紫:

--machinefile在连接机器后自动在每台机器上创建等于CPU逻辑线程数的Worker。它允许额外添加一些自定义信息,但自由度不高,比如不能自定义网络拓扑和Julia可执行文件的位置。如果想完整地控制集群创建过程,应使用addprocs(machines)。具体做法是:

  1. 首先创建一个startupfile.jl文件,把addprocs(machines)写在文件里。
  2. 在终端中julia -L startupfile.jl。它会在Julia REPL启动时立即执行startupfile.jl

与每次手动 addprocs()或把addprocs()写在代码开头相比,这种方法在有多个程序要并行时显然方便得多。在startupfile.jl里我们还可以精细地设定集群参数。以下是详细的演示:

假定有一个集群包含4个服务器。除了主服务器外,另外3台远程服务器分别为:

  • host1,24核(实际上是逻辑线程,以下用“核”代指“逻辑线程”。)
  • host2,12核
  • host3,8核

machinefile里写的是:

host1
host2
host3

假定需要的Worker数量等于核数,那么直接julia -m ~/machinefile即可。如果你面对的是非常多服务器组成的超级计算机或超大集群,那么可设法生成一个machinefile文件。具体生成办法请咨询MPI用户。

但是,如果你想搭建一个具有:master_slave拓扑的集群,使得所有Worker只能与主进程通讯而不能互相通讯(在很多集群中经常要这么做),可以写一个startupfile.jl文件,内容为:

  • 先启动位于主服务器的Worker,比如addprocs(4)。注意这一步与下一步毫无关系,如果你不需要主服务器上有Worker,甚至可以省略这一步。(当然通常不希望主服务器闲着。)
  • 把3台远程服务器加入集群:
for host in ["host1","host2","host3"]
	addprocs(host;topology=:master_slave)  # 注意分号和冒号
end
# 或者写作
addprocs(["host1", "host2", "host3"]; topology=:master_slave)

这样每台服务器上会自动创建等于核数的Worker。也可以逐个规定Worker数量,只要把hostname改成(hostname,数量)

addprocs([("host1", 24), ("host2", 12), ("host3", 8)]; topology=:master_slave)

如果服务器很多,写这么多hostname会很麻烦。可以先把hostname都写在一个machinefile文件里,或用MPI用户的办法自动导出一个machinefile文件(如果导出的文件里只有hostname没有Worker数量,只能自己再补上。不补上就是默认等于核数。),然后逐行读取:

addprocs(collect(eachline("~/machinefile")); topology=:master_slave)

除此之外,addprocs()还有几个自定义参数,详见官方文档。

总之,用julia -L startupfile.jl方式能精细且方便地定义集群,值得推荐。

猜你喜欢

转载自blog.csdn.net/iamzhtr/article/details/91947784