分布式编译环境中的负载均衡

分布式编译环境中的负载均衡

  
本文标题中的“分布式编译”是一种通过在局域网内的多个节点上运行编译进程来提高构建速度的途径。然而在我们的实践当中发现,单纯的向各个节点分发任务而不考虑负载均衡往往会产生性能上的瓶颈。本文主要介绍如何解决这个问题。

使用 distcc 分布式编译的特点与潜在问题

作为经典的分布式编译工具,distcc 在日常工作中常为我们使用来解决大型项目在单一工作站上编译较慢的问题。其主要用于对 C, Object C 以及 C++ 代码进行并行编译,将可以并行的编译任务分布于编译集群中的各个工作站,有效利用各机器资源,达到整体编译性能的成倍提升。

在类 Unix 系统上,distcc 使用 sendfile 系统调用在不同工作节点之间传送文件,尽管这种网络文件传输会占用一定的时间,他们对工作机的 CPU 资源占用却很小,而且这种分发任务的方式能够简化构建环境的配置,distcc 在这方面同早期的一些基于共享文件系统的分布编译环境 (dmake, pvmmake 等等 ) 相比几乎是 0 配置。

distcc 对各个编译节点的本地系统库及头文件基本没有要求,即使在不同的节点上这些组件的版本不同也不会影响到最终编译结果的正确性,实际情况是 distcc 会在本地 (client 机 ) 完成存在版本依赖的编译任务,这一点的实现原理简要说来式因为 distcc 借助了 C/C++ 编译驱动程序的以下特点:

  • cpp(C 预处理器 ) [cpp [arguments] .c 源文件输入 .i 中间文件输出 ]
  • ccl(C 编译器 ) [ccl .i 中间文件输入 .c 源文件输入 [arguments] –o .s 汇编文件输出 ]
  • as( 汇编器 ) [as [arguments] –o .o 目标文件输出 .s 汇编文件输入 ]

这个在本地做过预处理的 ASCII 源文件及其他命令行选项即可唯一确定一个目标文件,而与此任务在哪台机器上运行无关,通过分发这种任务到各个节点,即可消除对头文件的依赖。同理 distcc 通过在任务的分发节点做链接来消除对库文件的依赖。

然而,distcc 的缺点在于其负载均衡算法过于简单,distcc 的代理进程对各个工作机当前的负荷没有感知,分发预处理文件的唯一依据是主机出现在 DISTCC_HOST 环境变量中的次序,主机名越靠前,就会得到更多的编译任务,然而当编译场中某些机器性能过差,整体编译性能会显著下降,当阻塞 Make 运行的编译任务运行在这些机器上的时候,这种性能变化尤为明显。

假设这样一种极端的情况:集群中两台工作机中一台使用 4 个强劲的 CPU,而另一台性能较弱的机器只有 2 个较弱的 CPU。如果使用 distcc 启动并行数量为 6 的编译任务,这将导致较弱的 2 CPU 工作机负载过载而较强的 4CPU 工作机负载过轻,这样的非均匀负载分布将拖累整个编译任务。如图所示:


图 1:无负载均衡的并行编译任务演示
分布式编译环境中的负载均衡_第1张图片




一种实现负载均衡的解决方案:使用 DMUCS

毫无疑问,在上述编译集群中,有必要采用负载均衡来使编译系能得到最大的优化。这就需要在编译集群中增加监控各工作机工作量的监控程序,动态检测和平衡编译机的负载。

一个有效的方案是使用 DMUCS(Distributed Multi-User Compilation System)应用。DMUCS 是一个实现于 distcc 之上的动态平衡和任务分布程序。它可以:

  • 支持多用户同时编译,扩展性好,可以很好处理新增的负载。
  • 支持多种操作系统所组成的编译集群。
  • 可以使用具有多处理器(多核)编译主机的所有处理资源。
  • 可以充分使用具有不同处理速度的编译主机,使整体编译性能达到最优。
  • 可以保证参与编译的主机不会由于编译任务而产生超负载的情况。
  • 考虑到了编译主机上由非编译任务所引起的负载情况。
  • 支持从编译集群中动态的增加或者移除编译主机。

DMUCS 以下四个部分程序组成。

  • dmucs 主服务程序。

DMUCS 解决方案的核心服务程序。每个编译集群仅运行一个 dmucs 主服务程序,其运行于哪一台主机上没有限制。该程序从一个配置文件读出编译场里处理器数目和每个可能主机的“潜能”。然后从网络接收每个编译主机的平均负载信息,编译任务数量和监控程序得到的编译请求信息。dmucs 管理一个编译场里主机的数据库,并调度主机去编译任务,当有编译请求时给出可用的最快的主机 / 处理器。

  • Loadavg 监控程序

编译场的每个参与编译的主机上均需要启动这个程序。loadavg 定期发送编译主机的平均负载到 dmucs 服务器。这样当某个主机的平均负载太高时,dmucs 服务器会将不会优先给该主机分配编译任务。

  • gethost 编译命令

gethost 是具体进行编译的命令,其运行于 distcc 之上。该命令从运行 dmucs 主服务程序的主机获取编译集群中的机器信息来获得放进 DISTCC_HOSTS 环境变量里的主机,然后调用所分配的编译机进行编译。在编译结束后,gethost 释放所分配的主机到 dmucs 主服务器。用户使用“make CXX=gethost distcc”来启动编译。

  • Monitor 管理程序(非必须)

编译集群的管理员可以使用这个程序监控编译场的繁忙情况。

在上例中,采用 DMUCS 的负载均衡来配置编译集群,当其启动并行数量为 6 的编译任务时,理想状态下 DMUCS 会首先将任务优先分布到高性能的编译机进行编译。当高性能编译机上的负载饱满以后,余下的编译任务将被分布到低性能的编译机上进行编译。由此,每个编译机资源将得到最大限度的利用,编译性能得到最大提升。


图 2:带负载均衡的并行编译任务演示
分布式编译环境中的负载均衡_第2张图片




部署负载均衡的分布式编译环境

本节介绍如何架设 / 配置你的分布式编译环境来获得最好的性能,包括如下几点提示:

  • 使用 distcc 为你的工作节点指定角色;
  • 在此基础上配置 DMUCS 来实现性能优化;

本节中我们将主要讨论 distcc 和 DMUCS 的特性和配置,并针对上述两点做详细讨论。

distcc 的安装与配置

distcc 的基本安装,首先下载源代码到任意目录解压后依次运行

./configure, make,make install

即可完成安装,如果同时需要安装图形化任务监控前端,请根据主机图形库支持情况指定—with-gnome 或—with-gtk. distcc 程序组的默认安装位置是 /usr/local/bin,如果需要安装到其余位置,请在配置编译时修改 configure 的各个路径变量。

distcc 会安装如下可执行文件:

  • distcc: 整个编译任务通常由一台机器发起,在 distcc 编译环境下,这台机器被称为 client,client 必须使用 distcc 来替代原有的 GNU 编译器命令,由于 distcc 的后台编辑程序仍然是 GNU 编译器,distcc 与 gcc, g++, cc, c++ 等程序的编译参数兼容。distcc 必须与 Make 命令的 -j 参数协同使用,client 机通过指定此参数来定义并发编译的任务数。在默认情况下,一台编译机的 distcc 允许的并发任务的数量是 CPU 数量 +2。
  • distccd: distccd 是运行在编译场内各个节点上的 distcc 代理程序,distccd 的常用参数如下:
      • -j: 指定可以在本节点上运行的最大任务数;
      • -N: 如果编译节点上运行有其他重要任务,可以通过指定 -N 参数来调整编译进程的运行优先级;
      • -a: 指定 distccd 可以接受来自哪些节点的连接请求,-a 参数的值可以是一个网段,也可以是所有编辑节点主机名 (IP 地址 ) 的列表。
  • distccmon-text: 可以通过运行 distccmon-text 来通过一个字符界面监控整个编译任务,此命令唯一的参数是监控任务的刷新间隔 ( 秒 )。
  • distccmon-gnome: 是一个图形化的监控前端,下图是此程序的一个运行实例。其中,任务进度指示条颜色的意义分别为:绿色:compiling;紫色:preprocessing;蓝色:receiving;橙色:connecting;白色:idle;

图 3:distccmon-gnome: 监控前端
分布式编译环境中的负载均衡_第3张图片

Distcc client 通过配置环境变量 DISTCC_HOSTS 来指定编译场中的各个节点,具体命令如::

export DISTCC_HOSTS="192.168.1.1, 192.168.1.2"

此处需注意在此环境变量中位置靠前的主机会得到更多的编译任务,distcc 工具通过这种机制来实现简单的负载均衡,虽然我们认为这种机制是不完备的。

DMUCS 的安装与配置

从 http://prdownloads.sourceforge.net/dmucs/dmucs-0.6.tar.bz2?download 下载 dmucs 的最新源码包。Dmucs 的安装使用的是标准的 configure,make,make install 的方式。

首先在解压开的源码包的根目录下运行 ./configure,默认是设置 /usr/local 为安装目录,你也可以使用—prefix 参数来指定其他的安装目录,这同时也改变了 dmucs 启动时所需要的 hosts-info 文件(下文会详细介绍该文件的内容)所在的路径。默认情况下这个文件的所在路径是 /usr/local/share/dmucs/hosts-info。

第二步就是执行 make 命令来编译 dmucs,这里要注意的是需要使用一个 make 选项 CPPFLAGS=-DSERVER_MACH_NAME=\\\”<DSERVER-IP>\\\”,这里的 <DSERVER-IP> 是你选择运行 dmucs 服务器的 IP 地址。

第三步是运行 make install 将编译好的可执行文件安装到指定的安装目录下。

以上的安装步骤在每台机器上(包括 dmucs 主机和实际执行编译任务的 host 主机)都要执行一遍。

在运行 dmucs 之前要确保完成以下配置:

  1. 确保每个实际参与编译任务的主机都安装了 gcc 编译器和 distcc(至少需要 distcc 和 distccd)。
  2. 确保 loadavg 在每个参与编译任务的主机上都可以被执行。
  3. 确保 dmucs 程序在被你选作 dmucs sever 的主机上可以被执行。
  4. 确保 hosts-info 文件已被创建。该文件的默认路径是 /usr/local/share/dmucs/hosts-info,如果你在之前的安装步骤中指定过其他的安装目录(使用过—prefix 参数)那么这个文件所在路径是 <prefix>/share/dmucs/hosts-info。这里简单介绍一下这个文件的格式:

清单 1:hosts-info 配置
#Format:host-name number-of-cpus power-index
 9.123.247.65 2 3
 9.123.245.91 2 3
 9.47.98.59 1 1
 9.47.98.90 8 15

以上文件格式比较简单,每一行代表一台实际参与编译的主机的信息:主机名或 IP 地址,cpu(或者核)的个数和“性能指数。前两个属性的含义都是一目了然的,第三个属性“性能指数”是个相对数值(大于等于 1),如果你确定一台主机的性能强过另一台主机,这台主机的“性能指数就要相对大些。一种确定“性能指数”的方式是,让几台主机都单独执行一次同样的编译任务,分别记录所需的时间。然后指定所花时间最长的那台主机的性能参数(比如说 2),那么其他主机的性能指数的设定按照其所花编译时间与最慢的那台主机所花时间的比例来设置。比如说,如果有一台主机只花了最慢主机一半的时间,那么它的性能指数就应被设置为 4。其他的主机以此类推。

完整启动 DMUCS 程序由以下步骤组成:

  1. 在 dmucs server 上启动 dmucs 程序。
  2. 在每台实际参与编译的主机上运行 distccd 守护进程。
  3. 在每台实际参与编译的主机上执行 loadavg 程序,该程序默认向编译时所指定的 <DSERVER-IP> 的 dmucs server 发送该台主机的 IP 地址和负载信息。你也可以通过 -s 参数指定 dmucs server 的地址信息。

这里有个读者特别需要注意的地方,loadavg 程序是先获得主机的 hostname,之后再通过 hostname 去获取主机 IP 的。这就有个问题就是,假设某台主机的 hostname 是 host1,如果在这台机器上的 /etc/hosts 文件里的信息如下所示:


清单 2:hosts 文件示例
…………….
host1 127.0.0.1
host1 9.123.245.91
……………..

也就是说回环地址的记录在实际的 IP 地址记录之前,那么 loadavg 向 dmucs server 所发送的 IP 信息永远是 127.0.0.1!这样就会导致 dmucs 程序不能正确识别这台编译主机。解决的方法就是将 /etc/hosts/ 文件里的 host 主机的实际 IP 信息记录写在最前面。

下图是 dmucs 程序启动后的情况,读者可以从示例当中看到有两台主机加入到了编译集群:


图 4 dmucs 程序运行示例

最后用户就可以使用“make CXX=gethost distcc”来启动编译任务了。





编译测试与性能分析

我们使用两台不同性能的工作机搭建分布式编译的编译集群,这两台工作机的配置参数如下表所示。


表 1:测试机配置表
  工作机 1 工作机 2
CPU 数量 8 2
CPU 性能 Intel(R) Xeon(R) CPU E5410 2.33GHz Intel(R) Pentium(R) III CPU family 1266MHz
内存 16G 1G
IP 地址 9.47.98.90 9.47.98.59

每个主机上均运行 distccd 程序“distccd -a 9.47.0.0/16”。其中 DMUCS 服务程序运行在 8 核主机上,其 hosts-info 配置如下所示:


清单 3:hosts-info 配置
#Format:host-name number-of-cpus power-index
 9.47.98.59 2 2
 9.47.98.90 8 4

我们使用的测试项目包含个需要编译 803 个 CPP 文件,通常情况在单个普通双核的工作机上编译需要一小时以上。运用此测试样例,我们分别在单独使用 distcc 和用 dmucs 进行负载均衡优化的情况下,进行分布式编译。测试环境的配置与上节内容相同。通过设置不同的并行任务数量,编译测试依次进行并行任务数量为 2,4,6,8,10,12 和 14 的共 7 组测试。

我们得到了详细的测试结果,请参见表 2。


表 2:测试结果
并行任务数 Distcc ( 分钟 ) distcc+dmucs ( 分钟 )
2 26.3 17.8
4 14.5 10.0
6 11.2 7.5
8 9.5 6.2
10 9.3 6.3
12 9.5 6.1
14 9.2 6.3

测试结果的数据对比请参加图 5


图 5:测试结果
分布式编译环境中的负载均衡_第4张图片

通过对比,我们可以发现使用 dmucs 配合 distcc 在分布式编译中进行负载均衡,使性能强大的节点承担较重的负载,可以大大提高编译的效率,从而很好的解决了 distcc 平均分配所带来的性能瓶颈问题。从在本次测试中可以看出,其编译时间平均缩短最多至 1/3 左右。

同时,我们可以发现当并行任务到 10 以后,编译性能已无明显提升。这也反应了在分布式编译中,并行并行编译任务的数量应该根据实际的编译集群能力设定。单纯提高并行编译任务的数量而忽略编译集群的实际能力将并不一定给编译性能带来好处。(责任编辑:A6)

你可能感兴趣的:(分布式编译环境中的负载均衡)