2016-12-13 1 views
2

Je souhaite télécharger des fichiers volumineux à partir de Google Cloud Storage à l'aide de la bibliothèque Java fournie par google com.google.cloud.storage. J'ai le code de travail, mais j'ai toujours une question et une préoccupation majeure:Comment télécharger un gros fichier à partir de Google Cloud Storage à l'aide de Java avec contrôle de total de contrôle

Ma principale préoccupation est, quand est le contenu du fichier réellement téléchargé? Pendant (références au code ci-dessous) storage.get(blobId), pendant blob.reader() ou pendant reader.read(bytes)? Cela devient très important quand il s'agit de comment gérer une somme de contrôle invalide, que dois-je faire pour déclencher réellement que le fichier est récupéré sur le réseau à nouveau?

La question la plus simple est la suivante: y a-t-il une fonctionnalité intégrée permettant de vérifier md5 (ou crc32c) sur le fichier reçu dans la bibliothèque google? Peut-être que je n'ai pas besoin de l'implémenter tout seul.

Voici ma méthode tente de télécharger des fichiers volumineux de Google Cloud Storage:

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; 
} 

Répondre

2

La bibliothèque de stockage google-cloud-java ne valide pas elle-même les sommes de contrôle lors de la lecture de données au-delà de la vérification normale HTTPS/TCP. S'il comparait le MD5 des données reçues au MD5 connu, il aurait besoin de télécharger le fichier entier avant de pouvoir retourner les résultats de read(), ce qui serait impossible pour de très gros fichiers.

Ce que vous faites est une bonne idée si vous avez besoin de la protection supplémentaire de comparer les MD5. S'il s'agit d'une tâche unique, vous pouvez utiliser l'outil de ligne de commande gsutil, qui effectue ce même type de vérification supplémentaire.

+0

C'est logique! Si je détecte une somme de contrôle non valide, existe-t-il un moyen de forcer la bibliothèque com.google.cloud.storage à télécharger à nouveau le fichier et à ne pas le récupérer dans un cache? –

+0

Je ne voudrais pas déranger. Si vous obtenez une somme de contrôle invalide, l'erreur était très susceptible d'être un problème de réseau, et cela n'arrivera pas la deuxième fois. En outre, les lectures HTTPS faites avec des informations d'identification ne seront certainement pas mises en cache à moins que vous soyez derrière un pare-feu d'entreprise particulièrement bizarre. Néanmoins, vous pouvez certainement ignorer la plupart des caches en ajoutant simplement un paramètre d'URL non-sens supplémentaire, comme "& skipCaching = 12345". –

+0

Le problème n'est pas le cache HTTP, mais le cache dans la bibliothèque Java fourni par google (lien dans la question ci-dessus). JavaDoc for ReadChannel indique que "les implémentations de cette classe peuvent tamponner les données en interne pour réduire les appels distants". Voici une page qui parle de la validation de la somme de contrôle, mais qui ne répond pas à ma question pour autant que je puisse la voir: https://cloud.google.com/storage/docs/hashes-etags Je ne travaille donc pas directement avec les URLs malheureusement. –

0

Comme le JavaDoc ReadChannel dit:

Implémentations de cette classe peut tampon interne des données pour réduire les appels à distance .

Ainsi, la mise en œuvre que vous obtenez de blob.reader() pourrait mettre en cache le fichier entier, quelques octets ou rien et juste chercher octet par octet lorsque vous appelez read(). Vous ne saurez jamais et vous ne devriez pas vous en soucier.

Comme seulement read() jette un IOException et les autres méthodes que vous avez utilisées ne le font pas, je dirais que seul l'appel read() va effectivement télécharger des choses. Vous pouvez également voir cela dans the sources de la lib.

Btw. malgré l'exemple dans les JavaDocs de la bibliothèque, vous devriez vérifier >= 0, pas > 0. 0 signifie simplement que rien n'a été lu, pas que la fin du flux est atteinte. La fin du flux est signalée par le retour -1.

Pour réessayer après une vérification de somme de contrôle échouée, obtenez un nouveau lecteur à partir du blob. Si quelque chose met en cache les données téléchargées, alors le lecteur lui-même. Donc, si vous obtenez un nouveau lecteur à partir du blob, le fichier sera téléchargé depuis la télécommande.

+0

Oui, j'ai lu JavaDoc. Comme vous le dites, cela n'aide pas beaucoup. Puisque ce n'est pas une réponse, juste une déclaration que vous êtes aussi clueless que je suis, je voterai ceci. Le contrôle des octets devrait également être correct. La vérification est basée sur JavaDoc et le lecteur Blob ::, donc je suppose que le canal est en mode blocage. –

+0

Je ne suis pas aussi ignorant que vous. Comme je l'ai dit, la méthode de lecture fera la lecture du réseau. Vous pouvez le voir à partir du 'IOException' lancé.Obtenir des downvotes sur une réponse parfaitement valide n'incite pas les gens à vous donner d'autres réponses. Et trouver un exemple erroné ne signifie pas que vous devez répéter l'erreur dans votre propre code. Le code retour de «0» ne signifie tout simplement pas que la fin du flux est atteinte, c'est un fait qu'aucun exemple mal écrit ne peut changer. – Vampire

+0

Je lis juste JavaDoc à ReadableByteChannel, "Il est toutefois garanti que si un canal est en mode de blocage et qu'il reste au moins un octet dans le tampon, alors cette méthode bloquera jusqu'à ce qu'au moins un octet soit lu." –