Müheloser Dateiimport aus OpenStack Swift in Java
OpenStack Swift ist ein hochskalierbares Objektspeichersystem, mit dem Sie Dateien effizient in der Cloud speichern und abrufen können. In diesem Leitfaden zeigen wir, wie Sie Dateien mithilfe von Open-Source-Bibliotheken wie OpenStack4j aus OpenStack Swift in Ihre Java-Anwendungen importieren, und wir stellen bewährte Verfahren für eine nahtlose Cloud-Storage-Integration vor.
Ihre Java-Umgebung einrichten
Bevor Sie beginnen, stellen Sie sicher, dass die folgenden Voraussetzungen erfüllt sind:
- Java Development Kit (JDK) 11 oder neuer
- Maven oder Gradle für die Verwaltung von Abhängigkeiten
- OpenStack-Swift-Zugangsdaten (Authentifizierungs-URL, Benutzername, Passwort sowie Domainname und Projektname)
Fügen Sie Ihrem Projekt die OpenStack4j-Abhängigkeit hinzu. Für Maven ergänzen Sie Folgendes in Ihrer pom.xml:
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
Für Gradle ergänzen Sie in Ihrer build.gradle:
implementation 'com.github.openstack4j.core:openstack4j:3.12'
Verbindung zu OpenStack Swift herstellen
Stellen Sie mithilfe von OpenStack4j eine Verbindung zu Ihrer OpenStack-Swift-Instanz her. Das folgende Beispiel zeigt eine projektbezogene Authentifizierung. Geben Sie sowohl einen Domain- als auch einen Projektnamen an, um Autorisierungsprobleme zu vermeiden:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.openstack.OSFactory;
public class SwiftConnector {
private final OSClientV3 os;
public SwiftConnector(String authUrl, String username, String password,
String domainName, String projectName) {
this.os = OSFactory.builderV3()
.endpoint(authUrl)
.credentials(username, password, Identifier.byName(domainName))
.scopeToProject(Identifier.byName(projectName), Identifier.byName(domainName))
.authenticate();
}
public OSClientV3 getClient() {
return os;
}
}
Dateien aus Swift importieren
Das folgende Beispiel zeigt, wie Sie eine Datei aus einem Swift-Container in Ihre Java-Anwendung
importieren. Der Download erfolgt in eine temporäre Datei neben dem Ziel, und diese Datei wird erst
dann an ihren endgültigen Platz verschoben, wenn die Übertragung abgeschlossen ist und die Anzahl
der Bytes mit der von Swift übereinstimmt. Würden Sie direkt in localPath
schreiben, bliebe bei jedem Verbindungsabbruch eine abgeschnittene Datei zurück, die vollständig
aussieht.
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.storage.object.SwiftObject;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class SwiftFileImporter {
private final OSClientV3 os;
public SwiftFileImporter(OSClientV3 os) {
this.os = os;
}
public void importFile(String containerName, String objectName, String localPath)
throws IOException {
SwiftObject obj = os.objectStorage()
.objects()
.get(containerName, objectName);
if (obj == null) {
// NoSuchFileException's single-argument form takes a file, not a sentence.
throw new NoSuchFileException(
containerName + "/" + objectName, null, "Object not found");
}
Path destination = Paths.get(localPath).toAbsolutePath();
// The temporary file has to share a directory with the destination, otherwise the move
// below crosses filesystems and stops being atomic.
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
try (InputStream is = obj.download().getInputStream()) {
written = Files.copy(is, partial, StandardCopyOption.REPLACE_EXISTING);
}
// getSizeInBytes() is a primitive long: it is 0 both for an empty object and for a
// listing that carried no size, so only a positive value is worth checking against.
long expected = obj.getSizeInBytes();
if (expected > 0 && written != expected) {
throw new IOException(
"Truncated download of " + objectName + ": expected " + expected
+ " bytes but wrote " + written);
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(partial);
}
}
}
Große Dateien effizient verarbeiten
Implementieren Sie für große Dateien effizientes Streaming mit Fortschrittsanzeige, damit die Vorgänge reibungslos ablaufen, ohne den Arbeitsspeicher zu erschöpfen:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.model.storage.object.SwiftObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class LargeFileImporter {
private static final int BUFFER_SIZE = 1024 * 1024; // 1MB buffer
private static final long PROGRESS_INTERVAL = 32L * BUFFER_SIZE;
private final OSClientV3 os;
public LargeFileImporter(OSClientV3 os) {
this.os = os;
}
public void importLargeFile(String containerName, String objectName, String localPath)
throws IOException {
SwiftObject obj = os.objectStorage()
.objects()
.get(containerName, objectName);
if (obj == null) {
// NoSuchFileException's single-argument form takes a file, not a sentence.
throw new NoSuchFileException(
containerName + "/" + objectName, null, "Object not found");
}
long contentLength = obj.getSizeInBytes();
Path destination = Paths.get(localPath).toAbsolutePath();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long totalBytesRead = 0;
try (InputStream is = obj.download().getInputStream();
OutputStream out = Files.newOutputStream(partial)) {
byte[] buffer = new byte[BUFFER_SIZE];
long nextReport = PROGRESS_INTERVAL;
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// A size of 0 means Swift reported none, and a per-buffer report on a
// multi-GB object is thousands of log lines.
if (contentLength > 0 && totalBytesRead >= nextReport) {
nextReport += PROGRESS_INTERVAL;
System.out.printf("Download progress: %.2f%%%n",
(double) totalBytesRead / contentLength * 100);
}
}
}
if (contentLength > 0 && totalBytesRead != contentLength) {
throw new IOException(
"Truncated download of " + objectName + ": expected " + contentLength
+ " bytes but wrote " + totalBytesRead);
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} finally {
Files.deleteIfExists(partial);
}
}
}
Fehlerbehandlung und Retries implementieren
Eine robuste Fehlerbehandlung ist unerlässlich, insbesondere bei Netzwerkproblemen oder vorübergehenden Fehlern. Das folgende Beispiel wiederholt den Download bis zu dreimal, bevor es endgültig fehlschlägt. Ein fehlendes Objekt ist kein vorübergehender Zustand, daher schlägt der Vorgang sofort fehl, statt drei Versuche und sechs Sekunden für einen Namen zu verbrauchen, der sich nie auflösen lässt. Verbindungsfehler und wiederholbare HTTP-Antworten sind ungeprüfte SDK-Ausnahmen und damit von lokalen I/O-Fehlern zu unterscheiden. Bei Authentifizierungsfehlern und anderen dauerhaften HTTP-Fehlern erfolgt kein erneuter Versuch:
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ConnectionException;
import org.openstack4j.api.exceptions.ResponseException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Set;
public class RetryableSwiftImporter {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 2000;
private static final Set<Integer> RETRYABLE_STATUS = Set.of(408, 429, 500, 502, 503, 504);
private final SwiftFileImporter importer;
public RetryableSwiftImporter(OSClientV3 os) {
this.importer = new SwiftFileImporter(os);
}
public void importWithRetry(String containerName, String objectName, String localPath)
throws IOException, InterruptedException {
Exception lastException = null;
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
// importFile leaves the destination untouched until a whole, size-checked copy has
// landed, so retrying can never observe a half-written file.
importer.importFile(containerName, objectName, localPath);
return;
} catch (NoSuchFileException e) {
throw e;
} catch (ResponseException e) {
if (!(e instanceof ConnectionException) && !RETRYABLE_STATUS.contains(e.getStatus())) {
throw e;
}
lastException = e;
} catch (IOException e) {
lastException = e;
}
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS * attempt);
}
}
throw new IOException("Failed to import after " + MAX_RETRIES + " attempts", lastException);
}
}
Hinweise zur Fehlerbehandlung
Berücksichtigen Sie bei der Arbeit mit OpenStack Swift die folgenden häufigen Fehlerszenarien und passen Sie Ihre Fehlerbehandlung entsprechend an:
- Container nicht gefunden: Stellen Sie sicher, dass der angegebene Container in Ihrer Swift-Instanz vorhanden ist.
- Objekt nicht gefunden: Überprüfen Sie den Objektnamen und dessen Pfad.
- Authentifizierungsfehler: Prüfen Sie Ihre Zugangsdaten, den Domainnamen und den Projektnamen auf einen korrekten Geltungsbereich.
- Kontingent überschritten: Überwachen Sie Ihre Speicherlimits und Ihre Nutzung, um die zugewiesenen Kontingente nicht zu überschreiten.
Detaillierte Fehlermeldungen und eine explizite Behandlung dieser Fälle können die Fehlersuche und die Systemzuverlässigkeit erheblich verbessern.
Tipps zur Performance-Optimierung
Um Ihren Workflow für den Dateiimport aus Swift zu optimieren, beachten Sie die folgenden Verfahren:
-
Verbindungsverwaltung:
- Verwenden Sie einen
OSClientV3innerhalb des Threads wieder, der ihn authentifiziert hat.SwiftObject.download()ruftApis.get(...)auf und schlägt damit die Sitzung nach, die OpenStack4j in einemThreadLocalspeichert. Wenn Sie den Client oder einSwiftObjectan einen Worker-Thread übergeben, verfügt dieser Worker daher über gar keine Sitzung - Authentifizieren Sie sich bei parallelen Importen einmal pro Worker-Thread oder leiten Sie im Worker aus dem bereits vorhandenen Token erneut einen Client ab, statt einen gemeinsam genutzten Client zu poolen
- Legen Sie je nach Anwendungsfall passende Timeouts fest
- Verwenden Sie einen
-
Optimierung der Puffergröße:
- Verwenden Sie größere Puffer (1 MB oder mehr), um große Dateien zu übertragen
- Passen Sie die Puffergröße an den verfügbaren Arbeitsspeicher und die Dateigrößen an
- Überwachen Sie den Speicherverbrauch während der Übertragungen
-
Ressourcenverwaltung:
- Verwenden Sie stets try-with-resources, damit Streams und Verbindungen ordnungsgemäß freigegeben werden
- Laden Sie in eine temporäre Datei im Zielverzeichnis herunter und verschieben Sie sie erst dann an ihren endgültigen Platz, wenn die Größenprüfung bestanden ist, damit eine fehlgeschlagene Übertragung niemals eine plausibel wirkende unvollständige Datei hinterlässt
- Protokollieren Sie Ausnahmen und geben Sie Ressourcen frei, um Speicherlecks zu vermeiden
-
Bewährte Verfahren für die Sicherheit:
- Speichern Sie Zugangsdaten sicher mithilfe von Umgebungsvariablen oder sicheren Vaults
- Verwenden Sie für alle Verbindungen HTTPS-Endpunkte, um Daten bei der Übertragung zu schützen
- Implementieren Sie angemessene Zugriffskontrollen und ein Audit-Logging
-
Massenoperationen für Dateien:
- Nutzen Sie beim Import zahlreicher Dateien nach Möglichkeit die nativen Massenoperationen von Swift oder verarbeiten Sie die Dateien sequenziell, um den Arbeitsspeicher effektiver zu verwalten
Fazit
Mit OpenStack4j können Java-Entwickler OpenStack Swift effizient in ihre Anwendungen integrieren und so Dateiimport und Cloud-Storage-Verwaltung nahtlos abwickeln. Die hier vorgestellten Beispiele und bewährten Verfahren bilden einen soliden Ausgangspunkt für effiziente Lösungen zum Dateiimport.
Für weiterführende Funktionen zur Dateiverarbeitung und automatisierte Verarbeitungsworkflows lohnt sich ein Blick auf Transloadit.
