fabric并行任务

默认情况下,Fabric 会默认 顺序 执行所有任务.这篇文档将介绍 Fabric 如何在多个主机上 并行 执行任务,包括 Fabric 参数设置,以及命令行全局控制。


Fabric 并行是如何运转的

由于 Fabric 1.x 并不是完全线程安全(以及为了更加通用,任务函数之间并不会产生交互),该功能的实现是基于 Python multiprocessing 模块,它会为每一个主机和任务组合创建一个线程,同时提供了一个(可选的)弹窗用于阻止创建过多的进程。

举个例子,假设你正打算更新数台服务器上的 Web 应用代码,所有服务的代码都更新后开始重启服务器。你可能会写出下面这样的代码:

from fabric.api import *

def update():
    with cd("/root"):
        run("git pull")

def reload():
    sudo("service apache2 reload")

在三台服务器上并行执行,就像这样:

$ fab -H web1,web2,web3 update reload

常见的情况是没有启动任何并行执行参数,Fabric 将会按顺序在服务器上执行:

  1. 在 web1 上 更新

  2. 在 web2 上 更新

  3. 在 web3 上 更新

  4. 在 web1 上 重新加载配置

  5. 在 web2 上 重新加载配置

  6. 在 web3 上 重新加载配置

如果激活并行执行(通过 -P )它将变成这样,-P后面会介绍:

  1. 在 web1、web3 和 web3 上 更新

  2. 在 web1、web2 和 web3 上 重新加载配置。

这样做的好处非常明显:如果 update 花费 5 秒 reload 花费 2 秒,顺序执行总共会花费 (5+2)*3 = 21 秒,而并行执行只需要它的 1/3,也就是 (5+2) = 7 秒。


如何使用

由于并行执行影响的最小单位是任务,也就是函数,所以功能的启用或禁用也是以任务为单位使用 parallel 或 serial 注解来实现的。

parallel表示并行,serial表示串行。

serial 注解可以省略,省略的情况下,默认就是串行执行。

以下面这个 fabfile 为例:

from fabric.api import *

@parallel
def runs_in_parallel():
    print("执行runs_in_parallel")
    pass

def runs_serially():
    print("执行runs_serially")
    pass

如果运行下面的命令:

$ fab -H host1,host2,host3 runs_in_parallel runs_serially

将会按照下面的流程执行:

  1. runs_in_parallel 并行运行在 host1、host2 和 host3 上

  2. runs_serially 运行在 host1 上

  3. runs_serially 运行在 host2 上

  4. runs_serially 运行在 host3 上

代码中的@parallel,表示这个任务可以在多台机器上并行执行。也就是fab同时向各台机器发送执行命令。这里的host1,host2,host3,可以是ip或者域名。-H表示后面跟的主机参数。


命令行参数

你也可以使用命令行选项 -P 或者环境变量 env.parallel 强制所有任务并行执行。不过被注解 ~fabric.decorators.serial 的任务会忽略该设置,仍旧保持顺序执行。

例如,下面的 fabfile 会产生和上面同样的执行顺序:

from fabric.api import *

def runs_in_parallel():
    pass

@serial
def runs_serially():
    pass

在这样调用时:

$ fab -H host1,host2,host3 -P runs_in_parallel runs_serially

和上面一样,runs_in_parallel 将会并行执行,因为有一个 -P 命令选项。 runs_serially 顺序执行。


bubble 大小

主机列表很大时,用户的机器可能会因为并发运行了太多的 Fabric 进程而被压垮,因此,你可能会选择 moving bubble 方法来限制 Fabric 并发执行的活跃进程数。

bubble 是气泡的意思,这里表示每次运行多少个主机并发执行。

默认情况下没有使用 bubble 限制,所有主机都运行在并发池中。你可以在任务级别指定 parallel 的关键字参数 pool_size 来覆盖该设置,或者使用选项 -z 全局设置。

例如同时在 5 个主机上运行:

from fabric.api import *

@parallel(pool_size=5) def heavy_task(): # lots of heavy local lifting or lots of IO here

或者不使用关键字参数 pool_size:

$ fab -P -z 5 heavy_task


行级输出 vs 比特级输出

为了支持 与远程程序集成 特性,Fabric 默认会一字节一字节地讲数据输出到终端。并行情况下,这样的输出结果会非常糟糕,因为多个进程的同时输出结果可能会混在终端的标准输出流中。这样就会非常混乱,输出的单词由于来自不同的机器,所以是不可读的。

为了消除该问题,在并行执行时 Fabric 会自动启用行级输出,这会导致上面链接中提到的远程交互特性大部分失效,不过这是一个合理的折中。

行级输出混淆的情况在多进程的情况下是无法避免的,但是你可以设置主机地址作为前缀来区分。

未来版本会增加增强的日志支持来简化并行运行情况下的问题追踪。