子目录的工作原理

概览

本部分详细介绍了子目录在 gsutil 中的工作原理。大多数用户可能不需要知道这些详细信息,只需使用用于处理子目录的命令(如 cp -r)即可。我们另外提供本文档,以帮助用户了解 gsutil 对子目录的处理方式与大多数基于 GUI/基于 Web 的工具有何不同(例如,为什么其他工具创建“dir_$folder$”对象),并为对此类详细信息感兴趣的用户说明 gsutil 方法对费用和性能的影响。

gsutil 形成了一种在 Cloud Storage 服务支持的“平面”命名空间之上有一个分层文件树的错觉。对于该服务,对象 gs://your-bucket/abc/def.txt 只是其名称中恰好包含“/”字符的对象。没有“abc”目录;只有一个具有指定名称的对象。下图:

https://cloud.google.com/storage/images/gsutil-subdirectories.svg

说明了 gsutil 如何分层显示存储分区中的对象。

gsutil 通过应用各种规则来实现分层文件树错觉,从而尽量使命名按照用户预期方式执行。例如,如需确定将目标网址视为对象名称,还是对象应复制到的根目录,gsutil 使用以下规则:

  1. 如果目标对象以“/”结尾,则 gsutil 将其视为目录。例如,如果您运行以下命令:

    gsutil cp your-file gs://your-bucket/abc/
    

    gsutil 将创建对象 gs://your-bucket/abc/your-file。

  2. 如果目标对象为 XYZ,且存在一个名为 XYZ_$folder$ 的对象,则 gsutil 将 XYZ 视为目录。例如,如果您运行以下命令:

    gsutil cp your-file gs://your-bucket/abc
    

    并且存在一个名为 abc_$folder$ 的对象,则 gsutil 将创建对象 gs://your-bucket/abc/your-file。

  3. 如果您尝试将多个源文件复制到目标网址,则 gsutil 将目标网址视为目录。例如,如果您运行以下命令:

    gsutil cp -r your-dir gs://your-bucket/abc
    

    gsutil 会创建诸如 gs://your-bucket/abc/your-dir/file1 等对象(假设 file1 是源目录 your-dir 下的文件)。

  4. 如果上述规则均不适用,gsutil 会执行存储分区列出操作,以确定操作的目标是否是一个与指定的字符串匹配的前缀。例如,如果您运行以下命令:

    gsutil cp your-file gs://your-bucket/abc
    

    则 gsutil 将针对命名的存储分区发出存储分区列出请求,并使用分隔符“/”和前缀“abc”。然后,gsutil 会检查存储分区列出结果,并确定存储分区中是否有路径以 gs://your-bucket/abc/ 开头的对象,从而确定将目标视为对象名称还是目录名称。这又会进一步影响您创建的对象的名称:如果上述检查表明存在“abc”目录,则您将最终获得对象 gs://your-bucket/abc/your-file;否则,您将最终获得对象 gs://your-bucket/abc。(如需了解详情,请参阅 gsutil help cp 下的“如何构建名称”。)

这种基于规则的方法与许多工具的工作方式不同,后者会创建对象来标记存在的文件夹(例如“dir_$folder$”)。gsutil 可以理解此类工具采用的多种惯例,但不要求此类标记对象实现与 UNIX 命令一致的命名行为。

gsutil 子目录命名方法的缺点是,它需要额外的存储分区列出操作才能执行所需的 cp 或 mv 命令。但是,这些列出操作相对经济实惠,因为它们使用分隔符和前缀参数来限制结果数据。此外,gsutil 仅针对每个 cp/mv 命令发出一个存储分区列出请求,从而在所有传输的对象中分摊存储分区列出费用(例如,在将目录以递归方式复制到云端时)。

可能会发生意外的目标目录命名

上述基于规则的用于确定目标路径构建方式的方法可能会带来以下意外情况:假设您首先尝试将本地目录下的所有内容上传到尚不存在的存储分区“子目录”:

gsutil cp -r ./your-dir/* gs://your-bucket/new

其中,在 your-dir 下包含目录(假定 dir1 和 dir2)。第一次运行此命令时,它会创建以下对象:

gs://your-bucket/new/dir1/abc
gs://your-bucket/new/dir2/abc

因为 gs://your-bucket/new 尚不存在。如果您再次运行同一命令,则由于 gs://your-bucket/new 现已确实存在,因此该命令将创建其他对象:

gs://your-bucket/new/your-dir/dir1/abc
gs://your-bucket/new/your-dir/dir2/abc

除了此命名行为会让用户感到意外之外,您还应注意一个特殊情况,即您是否使用重试循环为 gsutil 上传编写脚本。如果您执行此操作,并且第一次尝试复制部分(而非全部)文件,则第二次尝试将会遇到已有的源子目录,并会导致上述命名问题。

您可以通过以下几种方法来避免此问题:

1. 使用 gsutil rsync。由于 rsync 不使用 Unix cp 定义的目录命名规则,因此无论目标子目录是否存在,它都会以一致的模式运行。

2. 如果使用 rsync 不起作用,则可以通过运行以下命令先创建一个“占位符”对象,以确定目标为子目录:

gsutil cp some-file gs://your-bucket/new/placeholder

此时,运行上述 gsutil cp -r 命令时,系统会始终将 gs://your-bucket/new 视为子目录。当该子目录下至少有一个对象后,您可以删除该占位符对象,并且对该子目录的后续上传仍会按照预期的方式处理命名。