如何使用带有校验和控制的Java从Google Cloud Storage下载大文件



我想使用Google提供的Java Library com.google.cloud.storage从Google Cloud Storage下载大文件。我有工作代码,但我仍然有一个问题和一个主要问题:

我的主要担心是,文件内容实际下载了什么时候?在(引用下面的代码)storage.get(blobId)期间,在blob.reader()期间还是在reader.read(bytes)期间?当涉及如何处理无效的校验和时,这变得非常重要,我需要做什么才能实际触发该文件再次通过网络获取?

更简单的问题是:是否内置功能可以进行MD5(或CRC32C)检查Google库中收到的文件?也许我不需要自己实施。

这是我尝试从Google云存储下载大文件的方法:

private static final int MAX_NUMBER_OF_TRIES = 3;
public Path downloadFile(String storageFileName, String bucketName) throws IOException {
    // In my real code, this is a field populated in the constructor.
    Storage storage = Objects.requireNonNull(StorageOptions.getDefaultInstance().getService());
    BlobId blobId = BlobId.of(bucketName, storageFileName);
    Path outputFile = Paths.get(storageFileName.replaceAll("/", "-"));
    int retryCounter = 1;
    Blob blob;
    boolean checksumOk;
    MessageDigest messageDigest;
    try {
        messageDigest = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException ex) {
        throw new RuntimeException(ex);
    }
    do {
        LOGGER.debug("Start download file {} from bucket {} to Content Store (try {})", storageFileName, bucketName, retryCounter);
        blob = storage.get(blobId);
        if (null == blob) {
            throw new CloudStorageCommunicationException("Failed to download file after " + retryCounter + " tries.");
        }
        if (Files.exists(outputFile)) {
            Files.delete(outputFile);
        }
        try (ReadChannel reader = blob.reader();
             FileChannel channel = new FileOutputStream(outputFile.toFile(), true).getChannel()) {
            ByteBuffer bytes = ByteBuffer.allocate(128 * 1024);
            int bytesRead = reader.read(bytes);
            while (bytesRead > 0) {
                bytes.flip();
                messageDigest.update(bytes.array(), 0, bytesRead);
                channel.write(bytes);
                bytes.clear();
                bytesRead = reader.read(bytes);
            }
        }
        String checksum = Base64.encodeBase64String(messageDigest.digest());
        checksumOk = checksum.equals(blob.getMd5());
        if (!checksumOk) {
            Files.delete(outputFile);
            messageDigest.reset();
        }
    } while (++retryCounter <= MAX_NUMBER_OF_TRIES && !checksumOk);
    if (!checksumOk) {
        throw new CloudStorageCommunicationException("Failed to download file after " + MAX_NUMBER_OF_TRIES + " tries.");
    }
    return outputFile;
}

Google-cloud-Java存储库在读取超过正常HTTPS/TCP正确性检查的数据时不会单独验证校验和。如果它将接收到的数据的MD5与已知的MD5进行了比较,则需要下载整个文件,然后才能返回read()的任何结果,对于非常大的文件而言,这是不可行的。

如果您需要对MD5进行比较的额外保护,那么您正在做的是一个好主意。如果这是一次性任务,则可以使用gsutil命令行工具,该工具执行相同的其他检查。

正如 ReadChannel的javadoc所说:

该类的实现可能会在内部缓冲数据以减少远程调用。

因此,您从blob.reader()获得的实现可以缓存整个文件,某些字节或什么都没有,然后在您调用read()时获取字节。你永远不会知道,你不应该在乎。

只有read()抛出IOException,而您使用的其他方法没有,我会说只有调用read()实际上会下载内容。您还可以在Lib的来源中看到这一点。

顺便说一句。尽管在库的Javadocs中有一个示例,但您应该检查>= 0而不是> 00只是意味着什么都没有阅读,而不是到达流的末端。流的结尾是通过返回-1的信号。

要在检查失败检查后重试,请从Blob中获取新读者。如果某些东西可以缓存下载的数据,则可以读者本身。因此,如果您从Blob中获得新读者,则文件将从远程重新下载。

最新更新