Hintergrundverarbeitung mit Python

Viele Anwendungen müssen eine Hintergrundverarbeitung außerhalb des Kontexts einer Webanfrage ausführen. In dieser Anleitung wird eine Webanwendung erstellt, mit der Nutzer Text eingeben können, der übersetzt werden soll. Anschließend wird eine Liste früherer Übersetzungen angezeigt. Die Übersetzung erfolgt in einem Hintergrundprozess, um die Anfrage des Nutzers nicht zu blockieren.

Das folgende Diagramm veranschaulicht den Ablauf der Übersetzungsanfrage.

Diagramm der Architektur

Wie die in dieser Anleitung verwendete Anwendung funktioniert, sehen Sie an der Abfolge der Ereignisse:

  1. Die Webseite wird aufgerufen, um eine Liste früherer Übersetzungen anzuzeigen, die in Firestore gespeichert sind.
  2. Die Übersetzung eines Textes wird angefordert. Dazu wird ein HTML-Formular ausgefüllt.
  3. Die Übersetzungsanfrage wird in Pub/Sub veröffentlicht.
  4. Eine Cloud Functions-Funktion, die dieses Pub/Sub-Thema abonniert hat, wird ausgelöst.
  5. Die Cloud Functions-Funktion verwendet Cloud Translation, um den Text zu übersetzen.
  6. Die Cloud Functions-Funktion speichert das Ergebnis in Firestore.

Diese Anleitung richtet sich an alle, die mehr über die Hintergrundverarbeitung mit Google Cloud erfahren möchten. Es sind keine Vorkenntnisse in Pub/Sub, Firestore, App Engine oder Cloud Functions erforderlich. Etwas Erfahrung mit Go, JavaScript und HTML ist allerdings hilfreich, um den gesamten Code zu verstehen.

Lernziele

  • Cloud Functions-Funktion verstehen und bereitstellen
  • App Engine-Anwendung verstehen und bereitstellen
  • Anwendung testen

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten von Google Cloud:

Mit dem Preisrechner können Sie eine Kostenschätzung für Ihre voraussichtliche Nutzung vornehmen. Neuen Google Cloud-Nutzern steht möglicherweise eine kostenlose Testversion zur Verfügung.

Nach Abschluss der in diesem Dokument beschriebenen Aufgaben können Sie weitere Kosten vermeiden, indem Sie die erstellten Ressourcen löschen. Weitere Informationen finden Sie unter Bereinigen.

Hinweis

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Cloud Functions, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Firestore, Cloud Functions, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  8. Öffnen Sie die Anwendung in der Google Cloud Console in Cloud Shell.

    Zu Cloud Shell

    Cloud Shell bietet Ihnen direkt über den Browser Befehlszeilenzugriff auf Ihre Cloud-Ressourcen. Öffnen Sie Cloud Shell im Browser und klicken Sie auf Fortfahren, um den Beispielcode herunterzuladen und ins Anwendungsverzeichnis zu wechseln.

  9. Konfigurieren Sie in Cloud Shell das gcloud-Tool, um Ihr Google Cloud-Projekt zu verwenden:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Informationen zur Cloud Functions-Funktion

  • Zuerst werden verschiedene Abhängigkeiten wie Firestore und Translation importiert.
    import base64
    import hashlib
    import json
    
    from google.cloud import firestore
    from google.cloud import translate_v2 as translate
  • Die globalen Firestore- und Translation-Clients werden initialisiert, damit sie zwischen Funktionsaufrufen wiederverwendet werden können. So müssen Sie nicht für jeden Funktionsaufruf neue Clients initialisieren, was die Ausführung verlangsamen würde.
    # Get client objects once to reuse over multiple invocations.
    xlate = translate.Client()
    db = firestore.Client()
  • Die Translation API übersetzt den String in die von Ihnen ausgewählte Sprache.
    def translate_string(from_string, to_language):
        """ Translates a string to a specified language.
    
        from_string - the original string before translation
    
        to_language - the language to translate to, as a two-letter code (e.g.,
            'en' for english, 'de' for german)
    
        Returns the translated string and the code for original language
        """
        result = xlate.translate(from_string, target_language=to_language)
        return result['translatedText'], result['detectedSourceLanguage']
  • Die Cloud Functions-Funktion beginnt mit dem Parsen der Pub/Sub-Nachricht, um den zu übersetzenden Text und die gewünschte Zielsprache abzurufen.

    Die Cloud Functions-Funktion übersetzt dann den Text und speichert ihn in Firestore. Über eine Transaktion wird sichergestellt, dass es keine doppelten Übersetzungen gibt.

    def document_name(message):
        """ Messages are saved in a Firestore database with document IDs generated
            from the original string and destination language. If the exact same
            translation is requested a second time, the result will overwrite the
            prior result.
    
            message - a dictionary with fields named Language and Original, and
                optionally other fields with any names
    
            Returns a unique name that is an allowed Firestore document ID
        """
        key = '{}/{}'.format(message['Language'], message['Original'])
        hashed = hashlib.sha512(key.encode()).digest()
    
        # Note that document IDs should not contain the '/' character
        name = base64.b64encode(hashed, altchars=b'+-').decode('utf-8')
        return name
    
    @firestore.transactional
    def update_database(transaction, message):
        name = document_name(message)
        doc_ref = db.collection('translations').document(document_id=name)
    
        try:
            doc_ref.get(transaction=transaction)
        except firestore.NotFound:
            return  # Don't replace an existing translation
    
        transaction.set(doc_ref, message)
    
    def translate_message(event, context):
        """ Process a pubsub message requesting a translation
        """
        message_data = base64.b64decode(event['data']).decode('utf-8')
        message = json.loads(message_data)
    
        from_string = message['Original']
        to_language = message['Language']
    
        to_string, from_language = translate_string(from_string, to_language)
    
        message['Translated'] = to_string
        message['OriginalLanguage'] = from_language
    
        transaction = db.transaction()
        update_database(transaction, message)

Cloud-Funktion bereitstellen

  • Stellen Sie in Cloud Shell im Verzeichnis function die Cloud Functions-Funktion mit einem Pub/Sub-Trigger bereit:

    gcloud functions deploy Translate --runtime=python37 \
    --entry-point=translate_message --trigger-topic=translate \
    --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_GOOGLE_CLOUD_PROJECT
    

    Dabei ist YOUR_GOOGLE_CLOUD_PROJECT Ihre Google Cloud-Projekt-ID.

Informationen zur Anwendung

Zur Webanwendung gehören zwei Hauptkomponenten:

  • Ein Python-HTTP-Server für Webanfragen. Der Server hat die folgenden beiden Endpunkte:
    • /: Führt alle vorhandenen Übersetzungen auf und zeigt ein Formular an, das Nutzer senden können, um neue Übersetzungen anzufordern.
    • /request-translation: Formularübermittlungen werden an diesen Endpunkt gesendet, wodurch die Anfrage zur Übersetzung asynchron in Pub/Sub veröffentlicht wird.
  • Eine HTML-Vorlage, in die die vorhandenen Übersetzungen vom Python-Server eingetragen sind.

Der HTTP-Server

  • Im Verzeichnis app beginnt main.py damit, Abhängigkeiten zu importieren, eine Flask-Anwendung zu erstellen, Firestore- und Translation-Clients zu initialisieren und eine Liste der unterstützten Sprachen zu definieren:

    import json
    import os
    
    from flask import Flask, redirect, render_template, request
    from google.cloud import firestore
    from google.cloud import pubsub
    
    app = Flask(__name__)
    
    # Get client objects to reuse over multiple invocations
    db = firestore.Client()
    publisher = pubsub.PublisherClient()
    
    # Keep this list of supported languages up to date
    ACCEPTABLE_LANGUAGES = ("de", "en", "es", "fr", "ja", "sw")
  • Der Index-Handler (/) ruft alle vorhandenen Übersetzungen aus Firestore ab und füllt eine HTML-Vorlage mit der Liste aus:

    @app.route("/", methods=["GET"])
    def index():
        """The home page has a list of prior translations and a form to
        ask for a new translation.
        """
    
        doc_list = []
        docs = db.collection("translations").stream()
        for doc in docs:
            doc_list.append(doc.to_dict())
    
        return render_template("index.html", translations=doc_list)
    
    
  • Neue Übersetzungen werden durch Senden eines HTML-Formulars angefordert. Der Handler für Übersetzungsanfragen, der unter /request-translation registriert ist, parst das gesendete Formular, validiert die Anfrage und veröffentlicht eine Nachricht in Pub/Sub:

    @app.route("/request-translation", methods=["POST"])
    def translate():
        """Handle a request to translate a string (form field 'v') to a given
        language (form field 'lang'), by sending a PubSub message to a topic.
        """
        source_string = request.form.get("v", "")
        to_language = request.form.get("lang", "")
    
        if source_string == "":
            error_message = "Empty value"
            return error_message, 400
    
        if to_language not in ACCEPTABLE_LANGUAGES:
            error_message = "Unsupported language: {}".format(to_language)
            return error_message, 400
    
        message = {
            "Original": source_string,
            "Language": to_language,
            "Translated": "",
            "OriginalLanguage": "",
        }
    
        topic_name = "projects/{}/topics/{}".format(
            os.getenv("GOOGLE_CLOUD_PROJECT"), "translate"
        )
        publisher.publish(
            topic=topic_name, data=json.dumps(message).encode("utf8")
        )
        return redirect("/")
    
    

HTML-Vorlage

Die HTML-Vorlage ist die Grundlage für die HTML-Seite, die dem Nutzer angezeigt wird, damit er frühere Übersetzungen sehen und neue anfordern kann. Die Vorlage wird vom HTTP-Server mit der Liste der vorhandenen Übersetzungen ausgefüllt.

  • Das <head>-Element der HTML-Vorlage enthält Metadaten, Stylesheets und JavaScript für die Seite:
    <html>
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Translations</title>
    
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                $("#translate-form").submit(function(e) {
                    e.preventDefault();
                    // Get value, make sure it's not empty.
                    if ($("#v").val() == "") {
                        return;
                    }
                    $.ajax({
                        type: "POST",
                        url: "/request-translation",
                        data: $(this).serialize(),
                        success: function(data) {
                            // Show snackbar.
                            console.log(data);
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--red-100");
                            $("#snackbar").addClass("mdl-color--green-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation requested'
                            });
                        },
                        error: function(data) {
                            // Show snackbar.
                            console.log("Error requesting translation");
                            var notification = document.querySelector('.mdl-js-snackbar');
                            $("#snackbar").removeClass("mdl-color--green-100");
                            $("#snackbar").addClass("mdl-color--red-100");
                            notification.MaterialSnackbar.showSnackbar({
                                message: 'Translation request failed'
                            });
                        }
                    });
                });
            });
        </script>
        <style>
            .lang {
                width: 50px;
            }
            .translate-form {
                display: inline;
            }
        </style>
    </head>

    Die Seite bindet CSS von Material Design Lite (MDL) und JavaScript-Assets ein. Mit MDL können Sie Ihren Websites einen Material Design-Look verleihen.

    Mithilfe von JQuery wartet die Seite, bis das Dokument fertig geladen ist, und legt dann einen Handler für gesendete Formulare fest. Wenn das Formular zur Übersetzungsanfrage gesendet wird, bestätigt die Seite mithilfe einer minimalen Formularvalidierung, dass der Wert nicht leer ist, und sendet dann eine asynchrone Anfrage an den Endpunkt /request-translation.

    Nun wird in einer MDL-Snackbar angezeigt, ob die Anfrage erfolgreich war oder ein Fehler aufgetreten ist.

  • Der HTML-Text der Seite verwendet ein MDL-Layout und mehrere MDL-Komponenten, um eine Liste der Übersetzungen und ein Formular zum Anfordern zusätzlicher Übersetzungen anzuzeigen:
    <body>
        <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
            <header class="mdl-layout__header">
                <div class="mdl-layout__header-row">
                    <!-- Title -->
                    <span class="mdl-layout-title">Translate with Background Processing</span>
                </div>
            </header>
            <main class="mdl-layout__content">
                <div class="page-content">
                    <div class="mdl-grid">
                    <div class="mdl-cell mdl-cell--1-col"></div>
                        <div class="mdl-cell mdl-cell--3-col">
                            <form id="translate-form" class="translate-form">
                                <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                                </div>
                                <select class="mdl-textfield__input lang" name="lang">
                                    <option value="de">de</option>
                                    <option value="en">en</option>
                                    <option value="es">es</option>
                                    <option value="fr">fr</option>
                                    <option value="ja">ja</option>
                                    <option value="sw">sw</option>
                                </select>
                                <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                                    name="submit">Submit</button>
                            </form>
                        </div>
                        <div class="mdl-cell mdl-cell--8-col">
                            <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                                <thead>
                                    <tr>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                                        <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                                    </tr>
                                </thead>
                                <tbody>
                                {% for translation in translations %}
                                    <tr>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--primary">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ translation['OriginalLanguage'] }} </span>
                                            </span>
                                        {{ translation['Original'] }}
                                        </td>
                                        <td class="mdl-data-table__cell--non-numeric">
                                            <span class="mdl-chip mdl-color--accent">
                                                <span class="mdl-chip__text mdl-color-text--white">{{ translation['Language'] }} </span>
                                            </span>
                                            {{ translation['Translated'] }}
                                        </td>
                                    </tr>
                                {% endfor %}
                                </tbody>
                            </table>
                            <br/>
                            <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">
                                Refresh
                            </button>
                        </div>
                    </div>
                </div>
                <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
                    <div class="mdl-snackbar__text mdl-color-text--black"></div>
                    <button type="button" class="mdl-snackbar__action"></button>
                </div>
            </main>
        </div>
    </body>
    
    </html>

Webanwendung bereitstellen

Sie können die App Engine-Standardumgebung verwenden, um eine Anwendung zu erstellen und bereitzustellen, die zuverlässig unter hoher Last und mit großen Datenmengen ausgeführt wird.

In dieser Anleitung wird das HTTP-Front-End in der App Engine-Standardumgebung bereitgestellt.

Die Datei app.yaml konfiguriert die App Engine-Anwendung:

runtime: python37
  • Stellen Sie Ihre Anwendung in demselben Verzeichnis wie die Datei app.yaml in der App Engine-Standardumgebung bereit:
    gcloud app deploy

Anwendung testen

Nachdem Sie die Cloud Functions-Funktion und die App Engine-Anwendung bereitgestellt haben, können Sie eine Übersetzung anfordern.

  1. Geben Sie die folgende URL ein,um die Anwendung in Ihrem Browser aufzurufen:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Ersetzen Sie Folgendes:

    Es gibt eine Seite mit einer leeren Liste von Übersetzungen und einem Formular, über das Sie neue Übersetzungen anfordern können.

  2. Geben Sie in das Feld Text to Translate einen zu übersetzenden Text ein, z. B. Hello, World.
  3. Wählen Sie in der Drop-down-Liste eine Sprache aus, in die der Text übersetzt werden soll.
  4. Klicken Sie auf Senden.
  5. Klicken Sie zum Aktualisieren der Seite auf Aktualisieren . Die Übersetzungsliste enthält jetzt eine neue Zeile. Wenn Sie keine Übersetzung sehen, warten Sie einige Sekunden und versuchen es dann noch einmal. Ist immer noch keine Übersetzung zu sehen, lesen Sie den nächsten Abschnitt zum Debugging der Anwendung.

Fehler in der Anwendung beheben

Wenn Sie keine Verbindung zu Ihrer App Engine-Anwendung herstellen können oder keine neuen Übersetzungen sehen, prüfen Sie Folgendes:

  1. Prüfen Sie, ob die gcloud-Bereitstellungsbefehle erfolgreich ausgeführt wurden und keine Fehler ausgegeben haben. Wenn Fehler aufgetreten sind, beheben Sie sie, bevor Sie die Cloud Functions-Funktion und die App Engine-Anwendung noch einmal bereitstellen.
  2. Rufen Sie in der Google Cloud Console die Seite „Loganzeige“ auf.

    Zur Seite „Loganzeige“
    1. Klicken Sie in der Drop-down-Liste Kürzlich ausgewählte Ressourcen auf GAE-Anwendung und dann auf Alle Werte für module_id. Sie sehen eine Liste der Anfragen, die Sie beim Besuch Ihrer Anwendung gestellt haben. Wenn keine Anfragenliste angezeigt wird, bestätigen Sie, dass Sie Alle Werte für module_id aus der Drop-down-Liste ausgewählt haben. Wenn Fehlermeldungen in der Cloud Console angezeigt werden, prüfen Sie, ob der Code Ihrer App dem Code im Abschnitt zur Erläuterung der Anwendung entspricht.
    2. Klicken Sie in der Drop-down-Liste Kürzlich ausgewählte Ressourcen auf Cloud Functions-Funktion und dann auf Alle Funktionsnamen. Daraufhin wird für jede angeforderte Übersetzung eine Funktion angezeigt. Wenn nicht, prüfen Sie, ob die Cloud Functions-Funktion und die App Engine-Anwendung dasselbe Pub/Sub-Thema verwenden:

Bereinigen

Damit Ihrem Google Cloud-Konto die in dieser Anleitung verwendeten Ressourcen nicht in Rechnung gestellt werden, löschen Sie entweder das Projekt, das die Ressourcen enthält, oder Sie behalten das Projekt und löschen die einzelnen Ressourcen.

Cloud-Projekt löschen

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

App Engine-Instanz löschen

  1. In the Google Cloud console, go to the Versions page for App Engine.

    Go to Versions

  2. Select the checkbox for the non-default app version that you want to delete.
  3. Klicken Sie zum Löschen der Anwendungsversion auf Löschen.

Cloud Functions-Funktion löschen

  • Löschen Sie die Cloud Functions-Funktion, die Sie in dieser Anleitung erstellt haben:
    gcloud functions delete Translate

Weitere Informationen