Hintergrundverarbeitung mit PHP


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. Sie rufen die Webseite auf, um sich eine Liste früherer, in Firestone gespeicherter Übersetzungen anzeigen zu lassen.
  2. Sie fordern die Übersetzung eines Textes an. Dazu füllen Sie ein HTML-Formular aus.
  3. Die Übersetzungsanfrage wird in Pub/Sub veröffentlicht.
  4. Eine Cloud Run-Anwendung empfängt die Pub/Sub-Nachricht.
  5. Die Cloud Run-Anwendung übersetzt den Text mit Cloud Translation.
  6. Die Cloud Run-Anwendung 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 Run erforderlich. Etwas Erfahrung mit PHP, JavaScript und HTML ist allerdings hilfreich, um den gesamten Code zu verstehen.

Lernziele

  • Cloud Run-Dienste 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.

Hinweise

  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 Run, 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 Run, Pub/Sub, and Cloud Translation APIs.

    Enable the APIs

  8. Öffnen Sie die Anwendung in der Google Cloud Console über 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 zum Cloud Run-Back-End

Sie definieren eine einzelne PHP-Funktion translateString und konfigurieren den Cloud Run-Dienst so, dass er mit einem Aufruf dieser Funktion auf eine Pub/Sub-Nachricht reagiert.

use Google\Cloud\Firestore\FirestoreClient;
use Google\Cloud\Firestore\Transaction;
use Google\Cloud\Translate\TranslateClient;

/**
 * @param array $data {
 *     The PubSub message data containing text and target language.
 *
 *     @type string $text
 *           The full text to translate.
 *     @type string $language
 *           The target language for the translation.
 * }
 */
function translateString(array $data)
{
    if (empty($data['language']) || empty($data['text'])) {
        throw new Exception('Error parsing translation data');
    }

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();

    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);

    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }

            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

    echo "Done.";
}
  1. Die Funktion muss mehrere Abhängigkeiten importieren, um eine Verbindung zu Firestore und Translation herzustellen.

    use Google\Cloud\Firestore\FirestoreClient;
    use Google\Cloud\Firestore\Transaction;
    use Google\Cloud\Translate\TranslateClient;
    
  2. Cloud Run beginnt mit der Initialisierung der Firestore- und Pub/Sub-Clients. Anschließend parst Cloud Run die Pub/Sub-Nachrichtendaten, um den zu übersetzenden Text und die gewünschte Zielsprache abzurufen.

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();
    
    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];
  3. Mithilfe der Translation API wird der String in die gewünschte Sprache übersetzt.

    $result = $translate->translate($translation['original'], [
        'target' => $translation['lang'],
    ]);
  4. Die Funktion erstellt dann einen eindeutigen Namen für die Übersetzungsanfrage, um zu verhindern, dass doppelte Übersetzungen gespeichert werden. Anschließend wird die Datei in eine Firestore-Transaktion übersetzt, damit bei gleichzeitiger Ausführung nicht versehentlich dieselbe Übersetzung zweimal ausgeführt wird.

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);
    
    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }
    
            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

Cloud Run-Back-End erstellen und bereitstellen

  • Erstellen Sie die Cloud Run-Anwendung im Verzeichnis backend:

    gcloud builds submit backend/ \
      --tag gcr.io/PROJECT_ID/background-function
  • Stellen Sie die Cloud Run-Anwendung mithilfe des Image-Tags aus dem vorherigen Schritt bereit:

    gcloud run deploy background-processing-function --platform managed \
      --image gcr.io/PROJECT_ID/background-function --region REGION

    Dabei ist REGION eine Google Cloud-Region.

  • Wenn die Bereitstellung abgeschlossen ist, sehen Sie in der Befehlsausgabe eine URL für die bereitgestellte Anwendung. Beispiel:

    Service [background-processing-function] revision [default-00002-vav] has been deployed and is serving 100 percent of traffic at https://default-c457u4v2ma-uc.a.run.app

    Kopieren Sie diese URL für den nächsten Schritt.

Pub/Sub-Abo einrichten

Die Cloud Run-Anwendung empfängt Nachrichten von Pub/Sub, sobald eine Nachricht zum Thema translate veröffentlicht wird.

Durch eine integrierte Authentifizierungsprüfung wird gewährleistet, dass die Pub/Sub-Nachricht ein gültiges Autorisierungstoken aus einem Dienstkonto enthält, das zum Aufrufen Ihres Cloud Run-Back-Ends berechtigt ist.

Die nächsten Schritte führen Sie durch die Einrichtung des Pub/Sub-Themas, des Abos und des Dienstkontos für authentifizierte Aufrufe des Cloud Run-Back-Ends. Weitere Informationen zu dieser Einbindung finden Sie unter Dienst-zu-Dienst-Authentifizierung.

  1. Erstellen Sie das Thema translate zum Veröffentlichen neuer Übersetzungsanfragen:

    gcloud pubsub topics create translate
    
  2. Aktivieren Sie Ihr Projekt, um Pub/Sub-Authentifizierungstokens zu erstellen:

    gcloud projects add-iam-policy-binding PROJECT_ID \
         --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    Dabei ist PROJECT_NUMBER Ihre Google Cloud-Projektnummer, die durch Ausführen von gcloud projects describe PROJECT_ID | grep projectNumber ermittelt werden kann.

  3. Erstellen oder wählen Sie ein Dienstkonto, das die Pub/Sub-Abonnementidentität darstellen soll.

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
         --display-name "Cloud Run Pub/Sub Invoker"

    Hinweis: Sie können cloud-run-pubsub-invoker verwenden oder durch einen in Ihrem Google Cloud-Projekt eindeutigen Namen ersetzen.

  4. Gewähren Sie dem Aufrufer-Dienstkonto die Berechtigung zum Aufrufen Ihres background-processing-function-Dienstes:

    gcloud run services add-iam-policy-binding background-processing-function \
       --member=serviceAccount:cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker  --platform managed --region REGION

    Es kann einige Minuten dauern, bis Änderungen am Identity and Access Management wirksam werden. In der Zwischenzeit werden möglicherweise HTTP 403-Fehler in den Dienstlogs angezeigt.

  5. Erstellen Sie ein Pub/Sub-Abo mit dem Dienstkonto:

    gcloud pubsub subscriptions create run-translate-string --topic translate \
       --push-endpoint=CLOUD_RUN_URL \
       --push-auth-service-account=cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com

    Dabei ist CLOUD_RUN_URL die HTTPS-URL, die Sie nach der Erstellung und Bereitstellung des Back-Ends kopiert haben.

    Das Flag --push-account-service-account aktiviert die Pub/Sub-Push-Funktion für die Authentifizierung und Autorisierung.

    Ihre Cloud Run-Dienstdomain wird automatisch für die Verwendung mit Pub/Sub-Abos registriert.

Informationen zur Anwendung

Zur Webanwendung gehören zwei Hauptkomponenten:

  • Ein PHP-HTTP-Server für Webanfragen Der Server hat die folgenden beiden Endpunkte:
    • /: listet alle vorhandenen Übersetzungen auf und zeigt ein Formular an, das Nutzer senden können, um neue Übersetzungen anzufordern.
    • /request-translation: Formulare werden an diesen Endpunkt gesendet, der die Anfrage zur asynchronen Übersetzung in Pub/Sub veröffentlicht.
  • Eine HTML-Vorlage, die vom PHP-Server mit den vorhandenen Übersetzungen ausgefüllt wird.

Der HTTP-Server

  • Im Verzeichnis app beginnt index.php mit der Einrichtung der Lumen und Registrierung der HTTP-Handler:

    $app = new Laravel\Lumen\Application(__DIR__);
    $app->router->group([
    ], function ($router) {
        require __DIR__ . '/routes/web.php';
    });
    $app->run();
  • Der Index-Handler (/) ruft alle vorhandenen Übersetzungen aus Firestore ab und rendert eine Vorlage mit der Liste:

    /**
     * Homepage listing all requested translations and their results.
     */
    $router->get('/', function (Request $request) use ($projectId) {
        $firestore = new FirestoreClient([
            'projectId' => $projectId,
        ]);
        $translations = $firestore->collection('translations')->documents();
        return view('home', ['translations' => $translations]);
    });
  • Der Handler für Übersetzungsanfragen, der unter /request-translation registriert ist, parst das gesendete HTML-Formular, validiert die Anfrage und veröffentlicht eine Nachricht in Pub/Sub:

    /**
     * Endpoint which publishes a PubSub request for a new translation.
     */
    $router->post('/request-translation', function (Request $request) use ($projectId) {
        $acceptableLanguages = ['de', 'en', 'es', 'fr', 'ja', 'sw'];
        if (!in_array($lang = $request->get('lang'), $acceptableLanguages)) {
            throw new Exception('Unsupported Language: ' . $lang);
        }
        if (!$text = $request->get('v')) {
            throw new Exception('No text to translate');
        }
        $pubsub = new PubSubClient([
            'projectId' => $projectId,
        ]);
        $topic = $pubsub->topic('translate');
        $topic->publish(['data' => json_encode([
            'language' => $lang,
            'text' => $text,
        ])]);
    
        return '';
    });

Die 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:

    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.

    Schließlich wird eine MDL-Snackbar angezeigt, die anzeigt, ob die Anfrage erfolgreich war oder ob ein Fehler aufgetreten ist.

  • Der HTML-Text der Seite verwendet ein MDL-Layout und mehrere MDL-Komponenten, um eine Liste von Ü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>
                  <?php foreach ($translations as $translation): ?>
                    <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['originalLang'] ?></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['lang'] ?></span>
                        </span>
                        <?= $translation['translated'] ?>
                      </td>
                    </tr>
                  <?php endforeach ?>
                  </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>
    

Anwendung in Cloud Shell ausführen

Bevor Sie die Webanwendung bereitstellen, installieren Sie die Abhängigkeiten und führen Sie sie lokal aus.

  1. Installieren Sie zuerst die Abhängigkeiten mit Composer. Die in Cloud Shell vorinstallierte gRPC-Erweiterung für PHP ist erforderlich.

    composer install -d app
    
  2. Führen Sie als Nächstes den in PHP integrierten Webserver aus, um Ihre Anwendung bereitzustellen:

    APP_DEBUG=true php -S localhost:8080 -t app
    

    Das Flag APP_DEBUG=true zeigt alle auftretenden Ausnahmen an.

  3. Klicken Sie in Cloud Shell auf Webvorschau und wählen Sie dann Vorschau auf Port 8080 aus. Ein neues Fenster mit der ausgeführten Anwendung wird geöffnet.

Webanwendung bereitstellen

Mit der App Engine-Standardumgebung können Sie eine Anwendung erstellen und bereitstellen, 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: php73

env_variables:
  APP_DEBUG: true
  LOG_CHANNEL: stderr
  APP_STORAGE: /tmp
  • Stellen Sie Ihre Anwendung aus demselben Verzeichnis wie die Datei app.yaml in der App Engine-Standardumgebung bereit:
    gcloud app deploy

Anwendung testen

Fordern Sie nach dem Bereitstellen der Cloud Functions-Funktion und der App Engine-Anwendung eine Übersetzung an.

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

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

    Ersetzen Sie Folgendes:

    Sie sehen eine Seite mit einer leeren Liste von Übersetzungen und einem Formular zum Anfordern neuer Übersetzungen.

  2. Geben Sie im Feld Zu übersetzender Text einen zu übersetzenden Text ein, z. B. Hello, World.
  3. Wählen Sie in der Drop-down-Liste eine Sprache aus, in die Sie den Text übersetzen möchten.
  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 zur App Engine-Anwendung herstellen können oder keine neuen Übersetzungen sehen, gehen Sie so vor:

  1. Überprüfen Sie, ob die gcloud-Bereitstellungsbefehle erfolgreich ausgeführt wurden und keine Fehler ausgegeben haben. Wenn Fehler aufgetreten sind, (etwa message=Build failed), beheben Sie sie und wiederholen Sie die Erstellung und Bereitstellung der Cloud Run-Anwendung und Bereitstellung der App Engine-Anwendung.
  2. Rufen Sie in der Google Cloud Console die Seite Logs-Explorer auf.

    Zur Seite „Log-Explorer“

    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. Es wird eine Liste der Anfragen abgerufen, 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 Google Cloud Console angezeigt werden, prüfen Sie, ob der Code Ihrer Anwendung mit dem Code im Abschnitt zum Verständnis der Webanwendung übereinstimmt.
    2. Klicken Sie in der Dropdown-Liste Zuletzt ausgewählte Ressourcen auf Cloud Run-Überarbeitung und anschließend auf Alle Logs. Es sollte eine POST-Anfrage an die URL Ihrer bereitgestellten Anwendung sichtbar sein. Ist dies nicht der Fall, prüfen Sie, ob die Cloud Run-Anwendung und die App Engine-Anwendung dasselbe Pub/Sub-Thema verwenden und ein Pub/Sub-Abo zum Senden an den Cloud Run-Endpunkt vorhanden ist.

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.

Google 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.

Anleitungsressourcen löschen

  1. Löschen Sie die App Engine-Anwendung, die Sie in dieser Anleitung erstellt haben:

    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.

  2. Löschen Sie den Cloud Run-Dienst, den Sie in dieser Anleitung bereitgestellt haben:

    gcloud run services delete background-processing-function

    Sie können Cloud Run-Dienste auch über die Google Cloud Console löschen.

  3. Löschen Sie sonstige Google Cloud-Ressourcen, die in dieser Anleitung erstellt wurden:

Nächste Schritte