使用 Amazon Simple Storage Service (S3) 和 Google Cloud Storage (GCS) 等云存储系统处理数据是一项非常常见的任务。正因如此,Arrow C++ 库提供了一套工具包,旨在让云存储的操作变得像本地文件系统一样简单。
为了实现这一点,Arrow C++ 库包含一个通用的文件系统接口,而 arrow 包将此接口公开给 R 用户。例如,如果您愿意,可以创建一个 LocalFileSystem 对象,以常规方式与本地文件系统交互:复制、移动和删除文件,获取文件和文件夹的信息等(详细信息请参阅 help("FileSystem", package = "arrow"))。通常情况下,您可能不需要此功能,因为您已经有了处理本地文件系统的工具,但在远程文件系统的背景下,此接口将变得更加有用。目前,S3FileSystem 类专门针对 Amazon S3 提供了实现,而 GcsFileSystem 则针对 Google Cloud Storage 提供了相应的实现。
本文概述了如何使用 Arrow 工具包处理 S3 和 GCS 数据。
S3 和 GCS 支持
在开始之前,请确保您的 arrow 安装已启用 S3 和/或 GCS 支持。您可以通过辅助函数检查是否已启用支持
如果这些函数返回 TRUE,则表示已启用相关支持。
CRAN 构建的 arrow 包含 S3 支持,但不包含 GCS 支持。如果您需要 GCS 支持,可以使用以下方法之一安装功能完整的 arrow
# Option 1: Install from R-universe
install.packages("arrow", repos = "https://apache.r-universe.dev")# Option 2: Reinstall from source with full features
Sys.setenv("NOT_CRAN" = "true")
install.packages("arrow", type = "source")在 Linux 上,从源码安装时 S3 和 GCS 支持并不总是默认启用的,并且涉及额外的系统要求。详情请参阅安装指南。
连接到云存储
使用文件系统的一种方法是创建 ?FileSystem 对象。?S3FileSystem 对象可以通过 s3_bucket() 函数创建,该函数会自动检测存储桶的 AWS 区域。同样,?GcsFileSystem 对象可以使用 gs_bucket() 函数创建。生成的 FileSystem 将把路径视为相对于存储桶的路径(因此,例如,在列出目录时不需要加上存储桶路径前缀)。
有了 FileSystem 对象,您可以使用 $path() 方法指向其中的特定文件,并将结果传递给文件读取器和写入器(read_parquet(), write_feather() 等)。
通常用户在实际分析中处理云存储的原因是为了访问大型数据集。有关此内容的示例请参阅数据集指南,但新用户在学习 arrow 云存储接口的工作原理时,可能更倾向于使用较小的数据集。为此,本文中的示例依赖于一个多文件 Parquet 数据集,其中存储了通过 ggplot2 包提供的 diamonds 数据副本,该数据集在 help("diamonds", package = "ggplot2") 中有详细说明。该数据集的云存储版本由 5 个 Parquet 文件组成,总大小不到 1MB。
diamonds 数据集托管在 S3 和 GCS 上,存储桶名称为 arrow-datasets。要创建指向该存储桶的 S3FileSystem 对象,请使用以下命令
bucket <- s3_bucket("arrow-datasets")对于 GCS 版本的数据,命令如下
bucket <- gs_bucket("arrow-datasets", anonymous = TRUE)请注意,如果尚未配置凭据,GCS 需要 anonymous = TRUE。
在此存储桶中有一个名为 diamonds 的文件夹。我们可以调用 bucket$ls("diamonds") 来列出存储在此文件夹中的文件,或调用 bucket$ls("diamonds", recursive = TRUE) 来递归搜索子文件夹。请注意,在 GCS 上,您应该始终设置 recursive = TRUE,因为目录通常不会出现在结果中。
以下是我们列出存储在 GCS 存储桶中的文件时得到的结果
bucket$ls("diamonds", recursive = TRUE)## [1] "diamonds/cut=Fair/part-0.parquet"
## [2] "diamonds/cut=Good/part-0.parquet"
## [3] "diamonds/cut=Ideal/part-0.parquet"
## [4] "diamonds/cut=Premium/part-0.parquet"
## [5] "diamonds/cut=Very Good/part-0.parquet"这里有 5 个 Parquet 文件,每个文件对应 diamonds 数据集中的一个“切割(cut)”类别。我们可以通过调用 bucket$path() 来指定特定文件的路径
parquet_good <- bucket$path("diamonds/cut=Good/part-0.parquet")我们可以使用 read_parquet() 从该路径直接读取数据到 R 中
diamonds_good <- read_parquet(parquet_good)
diamonds_good## # A tibble: 4,906 × 9
## carat color clarity depth table price x y z
## <dbl> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 E VS1 56.9 65 327 4.05 4.07 2.31
## 2 0.31 J SI2 63.3 58 335 4.34 4.35 2.75
## 3 0.3 J SI1 64 55 339 4.25 4.28 2.73
## 4 0.3 J SI1 63.4 54 351 4.23 4.29 2.7
## 5 0.3 J SI1 63.8 56 351 4.23 4.26 2.71
## 6 0.3 I SI2 63.3 56 351 4.26 4.3 2.71
## 7 0.23 F VS1 58.2 59 402 4.06 4.08 2.37
## 8 0.23 E VS1 64.1 59 402 3.83 3.85 2.46
## 9 0.31 H SI1 64 54 402 4.29 4.31 2.75
## 10 0.26 D VS2 65.2 56 403 3.99 4.02 2.61
## # … with 4,896 more rows
## # ℹ Use `print(n = ...)` to see more rows请注意,其读取速度将比本地文件慢。
使用 URI 直接连接
在大多数用例中,在 arrow 中连接云存储最简单、最自然的方法是使用 s3_bucket() 和 gs_bucket() 返回的 FileSystem 对象,尤其是在需要进行多次文件操作时。但在某些情况下,您可能希望通过指定 URI 直接下载文件。Arrow 允许这样做,并且像 read_parquet(), write_feather(), open_dataset() 等函数都将接受托管在 S3 或 GCS 上的云资源的 URI。S3 URI 的格式如下
s3://[access_key:secret_key@]bucket/path[?region=]
对于 GCS,URI 格式如下
gs://[access_key:secret_key@]bucket/path
gs://anonymous@bucket/path
例如,我们在文章前面下载的存储“优质切割(good cut)”钻石的 Parquet 文件在 S3 和 GCS 上均可用。相关 URI 如下
uri <- "s3://arrow-datasets/diamonds/cut=Good/part-0.parquet"
uri <- "gs://anonymous@arrow-datasets/diamonds/cut=Good/part-0.parquet"请注意,对于公共存储桶,GCS 需要“anonymous”。无论使用哪个版本,您都可以将此 URI 传递给 read_parquet(),就像文件存储在本地一样
df <- read_parquet(uri)URI 在查询参数(? 后面的部分)中接受额外的选项,这些选项会被传递下去以配置底层文件系统。它们用 & 分隔。例如,
s3://arrow-datasets/?endpoint_override=https%3A%2F%2Fstorage.googleapis.com&allow_bucket_creation=true
等同于
bucket <- S3FileSystem$create(
endpoint_override="https://storage.googleapis.com",
allow_bucket_creation=TRUE
)
bucket$path("arrow-datasets/")两者都告诉 S3FileSystem 对象应该允许创建新存储桶,并与 Google Storage 而非 S3 通信。后者有效是因为 GCS 实现了 S3 兼容 API —— 参见下方的 模拟 S3 的文件系统 —— 但如果您想要更好的 GCS 支持,应该参考 GcsFileSystem,但请使用以 gs:// 开头的 URI。
还要注意,URI 中的参数需要进行 百分号编码(percent encoded),这就是为什么 :// 被写成 %3A%2F%2F 的原因。
对于 S3,只有以下选项可以包含在作为查询参数的 URI 中:region, scheme, endpoint_override, access_key, secret_key, allow_bucket_creation, allow_bucket_deletion 和 check_directory_existence_before_creation。对于 GCS,支持的参数是 scheme, endpoint_override 和 retry_limit_seconds。
在 GCS 中,一个有用的选项是 retry_limit_seconds,它设置请求在返回错误之前可以重试的秒数。当前的默认值是 15 分钟,因此在许多交互式环境中,设置一个较小的值会更好
gs://anonymous@arrow-datasets/diamonds/?retry_limit_seconds=10
身份验证
S3 身份验证
要访问私有 S3 存储桶,通常需要两个秘密参数:access_key(类似于用户 ID)和 secret_key(类似于令牌或密码)。有几种传递这些凭据的选项
将它们包含在 URI 中,例如
s3://access_key:secret_key@bucket-name/path/to/file。如果您的秘密包含特殊字符(如“/”),请务必对其进行 URL 编码(例如URLencode("123/456", reserved = TRUE))。将它们作为
access_key和secret_key传递给S3FileSystem$create()或s3_bucket()将它们设置为名为
AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY的环境变量。按照 AWS 文档,在
~/.aws/credentials文件中定义它们。使用 AccessRole 进行临时访问,方法是将
role_arn标识符传递给S3FileSystem$create()或s3_bucket()。
GCS 身份验证
对 GCS 进行身份验证的最简单方法是运行 gcloud 命令来设置应用程序默认凭据
gcloud auth application-default login
要手动配置凭据,您可以传递 access_token 和 expiration(用于使用在其他地方生成的临时令牌),或者传递 json_credentials(用于引用已下载的凭据文件)。
如果您尚未配置凭据,则要访问公共存储桶,您必须在 URI 中传递 anonymous = TRUE 或将 anonymous 作为用户
bucket <- gs_bucket("arrow-datasets", anonymous = TRUE)
fs <- GcsFileSystem$create(anonymous = TRUE)
df <- read_parquet("gs://anonymous@arrow-datasets/diamonds/cut=Good/part-0.parquet")使用代理服务器
如果您需要使用代理服务器连接到 S3 存储桶,可以向 proxy_options 提供 http://user:password@host:port 格式的 URI。例如,在端口 1316 上运行的本地代理服务器可以这样使用
bucket <- s3_bucket(
bucket = "arrow-datasets",
proxy_options = "https://:1316"
)模拟 S3 的文件系统
S3FileSystem 机制使您能够使用任何提供 S3 兼容接口的文件系统。例如,MinIO 是一个模拟 S3 API 的对象存储服务器。如果您在本地以默认设置运行 minio server,则可以使用 S3FileSystem 通过以下方式连接到它
minio <- S3FileSystem$create(
access_key = "minioadmin",
secret_key = "minioadmin",
scheme = "http",
endpoint_override = "localhost:9000"
)或者作为 URI,它将是
s3://minioadmin:minioadmin@?scheme=http&endpoint_override=localhost%3A9000
(注意 endpoint_override 中 : 的 URL 转义)。
在其他应用中,这对于在远程 S3 存储桶上运行之前在本地测试代码非常有用。
禁用环境变量
如上所述,可以使用环境变量来配置访问。但是,如果您希望通过 URI 或其他方法传入连接详细信息,但同时又定义了现有的 AWS 环境变量,这些变量可能会干扰您的会话。例如,您可能会看到类似以下的错误消息
Error: IOError: When resolving region for bucket 'analysis': AWS Error [code 99]: curlCode: 6, Couldn't resolve host name 您可以使用 Sys.unsetenv() 取消设置这些环境变量,例如
Sys.unsetenv("AWS_DEFAULT_REGION")
Sys.unsetenv("AWS_S3_ENDPOINT")默认情况下,AWS SDK 会尝试检索有关用户配置的元数据,这在通过 URI 传入连接详细信息时可能会导致冲突(例如在访问 MINIO 存储桶时)。要禁用 AWS 环境变量的使用,您可以将环境变量 AWS_EC2_METADATA_DISABLED 设置为 TRUE。
Sys.setenv(AWS_EC2_METADATA_DISABLED = TRUE)进一步阅读
- 要了解有关
FileSystem类(包括S3FileSystem和GcsFileSystem)的更多信息,请参阅help("FileSystem", package = "arrow")。 - 要查看依赖于托管在云存储上的数据的分析示例,请参阅数据集指南。