J'ai assisté à quelques discussions à ce sujet mais je ne comprenais pas vraiment la bonne solution: Je veux charger quelques centaines de fichiers de S3 dans un RDD. Voici comment je le fais maintenant:
ObjectListing objectListing = s3.listObjects(new ListObjectsRequest().
withBucketName(...).
withPrefix(...));
List<String> keys = new LinkedList<>();
objectListing.getObjectSummaries().forEach(summery -> keys.add(summery.getKey())); // repeat while objectListing.isTruncated()
JavaRDD<String> events = sc.parallelize(keys).flatMap(new ReadFromS3Function(clusterProps));
Le ReadFromS3Function
effectue la lecture réelle à l'aide du client AmazonS3
:
public Iterator<String> call(String s) throws Exception {
AmazonS3 s3Client = getAmazonS3Client(properties);
S3Object object = s3Client.getObject(new GetObjectRequest(...));
InputStream is = object.getObjectContent();
List<String> lines = new LinkedList<>();
String str;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
if (is != null) {
while ((str = reader.readLine()) != null) {
lines.add(str);
}
} else {
...
}
} finally {
...
}
return lines.iterator();
J'ai en quelque sorte "traduit" cela à partir des réponses que j'ai vues pour la même question à Scala. Je pense qu'il est également possible de passer toute la liste des chemins à sc.textFile(...)
, mais je ne suis pas sûr de savoir quelle est la meilleure méthode.
le problème sous-jacent est que la liste des objets dans s3 est très lente et que sa structure ressemble à une arborescence de répertoires tue les performances chaque fois que quelque chose se passe dans une promenade dans les arbres (comme le fait le traitement générique des chemins).
Le code de l'article contient la liste de tous les enfants qui offre de bien meilleures performances. Il s'agit essentiellement de ce qui est livré avec Hadoop 2.8 et s3a listFiles (chemin, récursif) voir HADOOP-13208 .
Après avoir obtenu cette liste, vous avez des chaînes dans les chemins des objets que vous pouvez ensuite mapper vers les chemins s3a/s3n et que spark doit gérer en tant qu’entrées de fichier texte, et auxquels vous pouvez ensuite appliquer un travail.
val files = keys.map(key -> s"s3a://$bucket/$key").mkString(",")
sc.textFile(files).map(...)
Et comme demandé, voici le code Java utilisé.
String prefix = "s3a://" + properties.get("s3.source.bucket") + "/";
objectListing.getObjectSummaries().forEach(summary -> keys.add(prefix+summary.getKey()));
// repeat while objectListing truncated
JavaRDD<String> events = sc.textFile(String.join(",", keys))
Notez que je suis passé de s3n à s3a car, à condition que vous disposiez des fichiers JAR hadoop-aws
et Amazon-sdk
sur votre CP, le connecteur s3a est celui que vous devriez utiliser. C’est mieux, et c’est celui qui est entretenu et mis à l’essai par des personnes (moi) contre les charges d’étincelles. Voir L'histoire des connecteurs S3 de Hadoop .
Vous pouvez utiliser sc.textFile
pour lire plusieurs fichiers.
Vous pouvez passer multiple file url
avec comme argument.
Vous pouvez spécifier directories
entier, utiliser wildcards
et même CSV de répertoires et de caractères génériques.
Ex:
sc.textFile("/my/dir1,/my/paths/part-00[0-5]*,/another/dir,/a/specific/file")
Je suppose que si vous essayez de mettre en parallèle tout en lisant les aws utiliseront l'exécuteur et amélioreront définitivement les performances
val bucketName=xxx
val keyname=xxx
val df=sc.parallelize(new AmazonS3Client(new BasicAWSCredentials("awsccessKeyId", "SecretKey")).listObjects(request).getObjectSummaries.map(_.getKey).toList)
.flatMap { key => Source.fromInputStream(s3.getObject(bucketName, keyname).getObjectContent: InputStream).getLines }