עיבוד ברקע באמצעות Python

הרבה אפליקציות צריכות לבצע עיבוד ברקע מחוץ להקשר של בקשה לאחזור מהרשת. במדריך הזה ניצור אפליקציית אינטרנט שמאפשרת למשתמשים להזין טקסט לתרגום, ואז מציגה רשימה של תרגומים קודמים. התרגום מתבצע בתהליך ברקע כדי למנוע חסימה של בקשת המשתמש.

התרשים הבא מדגים את תהליך בקשת התרגום.

דיאגרמה של הארכיטקטורה.

כך פועלת אפליקציית המדריך:

  1. עוברים לדף האינטרנט כדי לראות רשימה של תרגומים קודמים, שמאוחסנים ב-Firestore.
  2. שליחת בקשה לתרגום טקסט באמצעות הזנת טופס HTML.
  3. בקשת התרגום מתפרסמת ב-Pub/Sub.
  4. מופעלת פונקציית Cloud Run שרשומה לנושא Pub/Sub.
  5. הפונקציה של Cloud Run משתמשת ב-Cloud Translation כדי לתרגם את הטקסט.
  6. פונקציית Cloud Run מאחסנת את התוצאה ב-Firestore.

המדריך הזה מיועד לכל מי שרוצה ללמוד על עיבוד ברקע באמצעות Google Cloud. אין צורך בניסיון קודם עם Pub/Sub,‏ Firestore,‏ App Engine או פונקציות של Cloud Run. עם זאת, כדי להבין את כל הקוד, כדאי שתהיה לכם קצת ניסיון ב-Python, ב-JavaScript וב-HTML.

מטרות

  • להבין ולפרוס פונקציית Cloud Run.
  • הבנה ופריסה של אפליקציית App Engine.
  • כדאי לנסות את האפליקציה.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

כדי להעריך את ההוצאות בהתאם לתחזית השימוש שלכם, אתם יכולים להיעזר במחשבון העלויות.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

כשמסיימים את המשימות שמתוארות במסמך הזה אפשר למחוק את המשאבים שיצרתם כדי להימנע מחיובים נוספים. מידע נוסף זמין בקטע הסרת המשאבים.

לפני שמתחילים

  1. נכנסים לחשבון Google Cloud . אם אתם משתמשים חדשים ב- Google Cloud, צרו חשבון כדי שתוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

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

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

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

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. במסוף Google Cloud , פותחים את האפליקציה ב-Cloud Shell.

    כניסה ל-Cloud Shell

    ‫Cloud Shell נותנת גישה למשאבים בענן בממשק שורת פקודה ישירות מהדפדפן. פותחים את Cloud Shell בדפדפן ולוחצים על Proceed כדי להוריד את קוד הדוגמה ולעבור לספריית האפליקציה.

  9. ב-Cloud Shell, מגדירים את הכלי gcloud כך שישתמש בפרויקט Google Cloud :
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

הסבר על פונקציית Cloud Run

  • הפונקציה מתחילה בייבוא של כמה תלויות כמו Firestore ו-Translation.
    import base64
    import hashlib
    import json
    
    from google.cloud import firestore
    from google.cloud import translate_v2 as translate
  • הלקוחות הגלובליים של Firestore ושל Translation עוברים אתחול כדי שאפשר יהיה להשתמש בהם מחדש בין קריאות לפונקציות. כך לא צריך לאתחל לקוחות חדשים לכל הפעלה של פונקציה, מה שיאט את הביצוע.
    # Get client objects once to reuse over multiple invocations.
    xlate = translate.Client()
    db = firestore.Client()
  • ‫Translation API מתרגם את המחרוזת לשפה שבחרתם.
    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']
  • פונקציית Cloud Run מתחילה בניתוח של הודעת Pub/Sub כדי לקבל את הטקסט לתרגום ואת שפת היעד הרצויה.

    לאחר מכן, פונקציית Cloud Run מתרגמת את הטקסט ומאחסנת אותו ב-Firestore באמצעות טרנזקציה, כדי לוודא שאין תרגומים כפולים.

    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 Run

  • ב-Cloud Shell, בספרייה function, פורסים את הפונקציה ב-Cloud Run עם טריגר Pub/Sub:

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

    כאשר YOUR_GOOGLE_CLOUD_PROJECT הוא מזהה הפרויקט. Google Cloud

הסבר על האפליקציה

יש שני רכיבים עיקריים לאפליקציית האינטרנט:

  • שרת HTTP של Python לטיפול בבקשות לאחזור מהרשת. לשרת יש את שתי נקודות הקצה הבאות:
    • /: מציג רשימה של כל התרגומים הקיימים וטופס שמשתמשים יכולים לשלוח כדי לבקש תרגומים חדשים.
    • /request-translation: שליחות של טפסים נשלחות לנקודת הקצה הזו, שמפרסמת את הבקשה ב-Pub/Sub כדי לתרגם אותה באופן אסינכרוני.
  • תבנית HTML שממולאת בתרגומים הקיימים על ידי שרת Python.

שרת ה-HTTP

  • בספרייה app, מתבצע ייבוא של תלות, נוצרת אפליקציית Flask, מתבצעת אתחול של לקוחות Firestore ו-Translation, ומוגדרת רשימה של שפות נתמכות:main.py

    import json
    import os
    
    from flask import Flask, redirect, render_template, request
    from google.cloud import firestore, pubsub
    from markupsafe import escape
    
    
    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")
  • הפונקציה לטיפול באינדקס (/) מקבלת את כל התרגומים הקיימים מ-Firestore וממלאת תבנית HTML ברשימה:

    @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)
    
    
  • כדי לבקש תרגומים חדשים, צריך לשלוח טופס HTML. ה-handler של תרגום הבקשה, שרשום ב-/request-translation, מנתח את הטופס שנשלח, מאמת את הבקשה ומפרסם הודעה ב-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 = escape(request.form.get("lang", ""))
    
        if source_string == "":
            return "Invalid request, you must provide a value.", 400
    
        if to_language not in ACCEPTABLE_LANGUAGES:
            return f"Unsupported language: {to_language}", 400
    
        message = {
            "Original": source_string,
            "Language": to_language,
            "Translated": "",
            "OriginalLanguage": "",
        }
    
        topic_name = (
            f"projects/{os.getenv('GOOGLE_CLOUD_PROJECT')}/topics/translate"
        )
        publisher.publish(
            topic=topic_name, data=json.dumps(message).encode("utf-8")
        )
        return redirect("/")
    
    

תבנית ה-HTML

תבנית ה-HTML היא הבסיס לדף ה-HTML שמוצג למשתמש, כדי שיוכל לראות תרגומים קודמים ולבקש תרגומים חדשים. התבנית מאוכלסת על ידי שרת ה-HTTP עם רשימת התרגומים הקיימים.

  • האלמנט <head> בתבנית ה-HTML כולל מטא-נתונים, גיליונות סגנונות ו-JavaScript לדף:
    <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>

    הדף מאחזר נכסי CSS ו-JavaScript של Material Design Lite (MDL). ‫MDL מאפשר לכם להוסיף לאתרים שלכם מראה ותחושה של Material Design.

    הדף משתמש ב-JQuery כדי להמתין לסיום הטעינה של המסמך ולהגדיר handler לשליחת טופס. בכל פעם ששולחים את טופס בקשת התרגום, הדף מבצע אימות מינימלי של הטופס כדי לוודא שהערך לא ריק, ואז שולח בקשה אסינכרונית לנקודת הקצה /request-translation.

    לבסוף, מופיע סרגל אינטראקטיבי של MDL כדי לציין אם הבקשה הצליחה או אם נתקלה בשגיאה.

  • הגוף של דף ה-HTML משתמש בפריסת MDL ובכמה רכיבי MDL כדי להציג רשימה של תרגומים וטופס לבקשת תרגומים נוספים:
    <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>

פריסת אפליקציית האינטרנט

אתם יכולים להשתמש בסביבה רגילה של App Engine כדי ליצור ולפרוס אפליקציה שפועלת בצורה מהימנה בעומס כבד ועם כמויות גדולות של נתונים.

במדריך הזה משתמשים בסביבה הרגילה של App Engine כדי לפרוס את חזית ה-HTTP.

app.yaml מגדיר את אפליקציית App Engine:

runtime: python312
  • באותה ספרייה שבה נמצא הקובץ app.yaml, פורסים את האפליקציה בסביבה הרגילה של App Engine:
    gcloud app deploy

בדיקת האפליקציה

אחרי שפורסים את פונקציית Cloud Run ואת אפליקציית App Engine, מנסים לשלוח בקשה לתרגום.

  1. כדי להציג את האפליקציה בדפדפן,מזינים את כתובת ה-URL הבאה:

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

    מחליפים את מה שכתוב בשדות הבאים:

    מוצג דף עם רשימה ריקה של תרגומים וטופס לבקשת תרגומים חדשים.

  2. בשדה Text to translate (טקסט לתרגום), מזינים טקסט לתרגום, לדוגמה, Hello, World.
  3. בוחרים שפה מהרשימה הנפתחת שאליה רוצים לתרגם את הטקסט.
  4. לוחצים על שליחה.
  5. כדי לרענן את הדף, לוחצים על רענון . נוספה שורה חדשה לרשימת התרגומים. אם לא רואים תרגום, מחכים עוד כמה שניות ומנסים שוב. אם עדיין לא מופיע תרגום, אפשר לעבור לקטע הבא בנושא ניפוי באגים באפליקציה.

ניפוי באגים באפליקציה

אם אתם לא מצליחים להתחבר לאפליקציית App Engine או לא רואים תרגומים חדשים, כדאי לבדוק את הדברים הבאים:

  1. בודקים שהפקודות gcloud deploy הושלמו בהצלחה ולא הוחזרו שגיאות. אם היו שגיאות, צריך לתקן אותן ולנסות שוב לפרוס את הפונקציה של Cloud Run ואת האפליקציה של App Engine.
  2. נכנסים לדף Logs Viewer במסוף Google Cloud .

    כניסה לדף Logs Viewer
    1. ברשימה הנפתחת Recently selected resources (משאבים שנבחרו לאחרונה), לוחצים על GAE Application (אפליקציית GAE) ואז על All module_id (כל מזהי המודולים). מוצגת רשימה של בקשות מהביקור שלכם באפליקציה. אם לא מוצגת רשימה של בקשות, מוודאים שבחרתם באפשרות All module_id מהרשימה הנפתחת. אם הודעות השגיאה מודפסות ב Google Cloud מסוף, צריך לוודא שקוד האפליקציה זהה לקוד שמופיע בקטע על הבנת האפליקציה.
    2. ברשימה הנפתחת Recently selected resources, לוחצים על Cloud Function ואז על All function name. תופיע פונקציה לכל תרגום שביקשתם. אם לא, בודקים שפונקציית Cloud Run ואפליקציית App Engine משתמשות באותו נושא Pub/Sub:

הסרת המשאבים

כדי להימנע מחיובים בחשבון Google Cloud בגלל השימוש במשאבים שנעשה במסגרת המדריך הזה, אפשר למחוק את הפרויקט שמכיל את המשאבים, או להשאיר את הפרויקט ולמחוק את המשאבים בנפרד.

מחיקת הפרויקט Google Cloud

  1. במסוף Google Cloud , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

מחיקת מכונת App Engine

  1. במסוף Google Cloud , נכנסים לדף Versions של App Engine.

    כניסה לדף Versions

  2. מסמנים את התיבה שלצד גרסת האפליקציה שלא מוגדרת כברירת מחדל ושרוצים למחוק.
  3. כדי למחוק את גרסת האפליקציה, לוחצים על Delete.

מחיקת פונקציית Cloud Run

  • מוחקים את הפונקציה של Cloud Run שיצרתם במדריך הזה:
    gcloud functions delete Translate

המאמרים הבאים