עיבוד ברקע באמצעות ‎ .NET

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

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

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

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

  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. עם זאת, כדי להבין את כל הקוד, כדאי שיהיה לכם ניסיון מסוים ב- .NET, ב-JavaScript וב-HTML.

מטרות

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

עלויות

במסמך הזה משתמשים ברכיבים הבאים של 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, 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. התקינו את ה-CLI של Google Cloud.

  6. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  7. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  8. 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

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

  10. Enable the Firestore, Cloud Run, 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

  11. התקינו את ה-CLI של Google Cloud.

  12. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  13. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  14. עדכון רכיבים של gcloud:
    gcloud components update
  15. מכינים את סביבת הפיתוח.

    הגדרת סביבת פיתוח של ‎ .NET

הכנת האפליקציה

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

    git clone https://github.com/GoogleCloudPlatform/getting-started-dotnet.git

    אפשרות נוספת היא להוריד את הדוגמה כקובץ ZIP ולחלץ אותה.

  2. עוברים לספרייה שמכילה את הקוד לדוגמה של משימת הרקע:

    cd getting-started-dotnet/BackgroundProcessing

הסבר על השירות TranslateWorker

  • השירות מתחיל בייבוא של כמה תלויות כמו Firestore ו-Translation.

  • לקוחות Firestore ו-Translation מאותחלים כדי שאפשר יהיה להשתמש בהם מחדש בין הפעלות של handler. כך לא תצטרכו לאתחל לקוחות חדשים בכל הפעלה, מה שיאט את הביצוע.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<FirestoreDb>(provider =>
            FirestoreDb.Create(GetFirestoreProjectId()));
        services.AddSingleton<TranslationClient>(
            TranslationClient.Create());
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
    
  • ה-API של Translation מתרגם את המחרוזת לשפה שבחרתם.

    var result = await _translator.TranslateTextAsync(sourceText, "es");
    
  • הקונסטרוקטור של בקר מקבל את לקוחות Firestore ו-Pub/Sub.

    השיטה Post מנתחת את הודעת Pub/Sub כדי לקבל את הטקסט לתרגום. הוא משתמש במזהה ההודעה כשם ייחודי לבקשת התרגום כדי לוודא שהוא לא שומר תרגומים כפולים.

    using Google.Cloud.Firestore;
    using Google.Cloud.Translation.V2;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TranslateWorker.Controllers
    {
        /// <summary>
        /// The message Pubsub posts to our controller.
        /// </summary>
        public class PostMessage
        {
            public PubsubMessage message { get; set; }
            public string subscription { get; set; }
        }
    
        /// <summary>
        /// Pubsub's inner message.
        /// </summary>
        public class PubsubMessage
        {
            public string data { get; set; }
            public string messageId { get; set; }
            public Dictionary<string, string> attributes { get; set; }
        }
    
    
        [Route("api/[controller]")]
        [ApiController]
        public class TranslateController : ControllerBase
        {
            private readonly ILogger<TranslateController> _logger;
            private readonly FirestoreDb _firestore;
            private readonly TranslationClient _translator;
            // The Firestore collection where we store translations.
            private readonly CollectionReference _translations;
    
            public TranslateController(ILogger<TranslateController> logger,
                FirestoreDb firestore,
                TranslationClient translator)
            {
                _logger = logger ?? throw new ArgumentNullException(nameof(logger));
                _firestore = firestore ?? throw new ArgumentNullException(
                    nameof(firestore));
                _translator = translator ?? throw new ArgumentNullException(
                    nameof(translator));
                _translations = _firestore.Collection("Translations");
            }
    
            /// <summary>
            /// Handle a posted message from Pubsub.
            /// </summary>
            /// <param name="request">The message Pubsub posts to this process.</param>
            /// <returns>NoContent on success.</returns>
            [HttpPost]
            public async Task<IActionResult> Post([FromBody] PostMessage request)
            {
                // Unpack the message from Pubsub.
                string sourceText;
                try
                {
                    byte[] data = Convert.FromBase64String(request.message.data);
                    sourceText = Encoding.UTF8.GetString(data);
                }
                catch (Exception e)
                {
                    _logger.LogError(1, e, "Bad request");
                    return BadRequest();
                }
                // Translate the source text.
                _logger.LogDebug(2, "Translating {0} to Spanish.", sourceText);
                var result = await _translator.TranslateTextAsync(sourceText, "es");
                // Store the result in Firestore.
                Translation translation = new Translation()
                {
                    TimeStamp = DateTime.UtcNow,
                    SourceText = sourceText,
                    TranslatedText = result.TranslatedText
                };
                _logger.LogDebug(3, "Saving translation {0} to {1}.",
                    translation.TranslatedText, _translations.Path);
                await _translations.Document(request.message.messageId)
                    .SetAsync(translation);
                // Return a success code.
                return NoContent();
            }
    
            /// <summary>
            /// Serve a root page so Cloud Run knows this process is healthy.
            /// </summary>
            [Route("/")]
            public IActionResult Index()
            {
                return Content("Serving translate requests...");
            }
        }
    }
    

פריסת שירות TranslateWorker

  • בספרייה BackgroundProcessing, מריצים את סקריפט PowerShell כדי ליצור ולפרוס את השירות ב-Cloud Run:

    PublishTo-CloudRun.ps1

הסבר על סקריפט PublishTo-CloudRun.ps1

הסקריפט PublishTo-CloudRun.ps1 מפרסם את השירות ב-Cloud Run ומגן על השירות TranslateWorker מפני ניצול לרעה. אם השירות מאפשר את כל החיבורים הנכנסים, כל אחד יכול לשלוח בקשות תרגום לבקר ובכך לגרום לעלויות. לכן, צריך להגדיר את השירות כך שיקבל רק בקשות מ-Pub/Sub.POST

הסקריפט מבצע את הפעולות הבאות:

  1. מבצע build של האפליקציה באופן מקומי באמצעות dotnet publish.
  2. יצירת קונטיינר שמריץ את האפליקציה באמצעות Cloud Build.
  3. פריסת האפליקציה ב-Cloud Run.
  4. מאפשר לפרויקט ליצור טוקנים לאימות Pub/Sub.
  5. יוצר חשבון שירות שמייצג את הזהות של המינוי ל-Pub/Sub.
  6. נותן לחשבון השירות הרשאה להפעיל את שירות TranslateWorker.
  7. יצירת נושא ומינוי ב-Pub/Sub.

    # 1. Build the application locally.
    dotnet publish -c Release
    
    # Collect some details about the project that we'll need later.
    $projectId = gcloud config get-value project
    $projectNumber = gcloud projects describe $projectId --format="get(projectNumber)"
    $region = "us-central1"
    
    # 2. Use Google Cloud Build to build the worker's container and publish to Google
    # Container Registry.
    gcloud builds submit --tag gcr.io/$projectId/translate-worker `
        TranslateWorker/bin/Release/netcoreapp2.1/publish
    
    # 3. Run the container with Google Cloud Run.
    gcloud beta run deploy translate-worker --region $region --platform managed `
        --image gcr.io/$projectId/translate-worker --no-allow-unauthenticated
    $url = gcloud beta run services describe translate-worker --platform managed `
        --region $region --format="get(status.address.hostname)"
    
    # 4. Enable the project to create pubsub authentication tokens.
    gcloud projects add-iam-policy-binding $projectId `
         --member=serviceAccount:service-$projectNumber@gcp-sa-pubsub.iam.gserviceaccount.com `
         --role=roles/iam.serviceAccountTokenCreator
    
    # 5. Create a service account to represent the Cloud Pub/Sub subscription identity.
    $serviceAccountExists = gcloud iam service-accounts describe `
        cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com 2> $null
    if (-not $serviceAccountExists) {
        gcloud iam service-accounts create cloud-run-pubsub-invoker `
            --display-name "Cloud Run Pub/Sub Invoker"
    }
    
    # 6. For Cloud Run, give this service account permission to invoke 
    # translate-worker service.
    gcloud beta run services add-iam-policy-binding translate-worker `
         --member=serviceAccount:cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com `
         --role=roles/run.invoker --region=$region
    
    # 7. Create a pubsub topic and subscription, if they don't already exist.
    $topicExists = gcloud pubsub topics describe translate-requests 2> $null 
    if (-not $topicExists) {
        gcloud pubsub topics create translate-requests
    }
    $subscriptionExists = gcloud pubsub subscriptions describe translate-requests 2> $null
    if ($subscriptionExists) {
        gcloud beta pubsub subscriptions modify-push-config translate-requests `
            --push-endpoint $url/api/translate `
            --push-auth-service-account cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com
    } else {
        gcloud beta pubsub subscriptions create translate-requests `
            --topic translate-requests --push-endpoint $url/api/translate `
            --push-auth-service-account cloud-run-pubsub-invoker@$projectId.iam.gserviceaccount.com
    }
    
    

הסבר על השירות TranslateUI

שירות TranslateUI מעבד דף אינטרנט שבו מוצגים תרגומים מהזמן האחרון, ומקבל בקשות לתרגומים חדשים.

  • המחלקות StartUp מגדירות אפליקציית ASP.NET ויוצרות לקוחות של Pub/Sub ו-Firestore.

    using Google.Apis.Auth.OAuth2;
    using Google.Cloud.Firestore;
    using Google.Cloud.PubSub.V1;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Net.Http;
    
    namespace TranslateUI
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddSingleton<FirestoreDb>(
                    provider => FirestoreDb.Create(GetFirestoreProjectId()));
                services.AddSingleton<PublisherClient>(
                    provider => PublisherClient.CreateAsync(new TopicName(
                        GetProjectId(), GetTopicName())).Result);
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
                app.UseCookiePolicy();
    
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
            }
    
        }
    }
    
  • הפונקציה לטיפול באינדקס Index מקבלת את כל התרגומים הקיימים מ-Firestore וממלאת את ViewModel ברשימה:

    using Google.Cloud.Firestore;
    using Google.Cloud.PubSub.V1;
    using Google.Protobuf;
    using Microsoft.AspNetCore.Mvc;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading.Tasks;
    using TranslateUI.Models;
    
    namespace TranslateUI.Controllers
    {
        public class HomeController : Controller
        {
            private readonly FirestoreDb _firestore;
            private readonly PublisherClient _publisher;
            private CollectionReference _translations;
    
            public HomeController(FirestoreDb firestore, PublisherClient publisher)
            {
                _firestore = firestore;
                _publisher = publisher;
                _translations = _firestore.Collection("Translations");
            }
    
            [HttpPost]
            [HttpGet]
            public async Task<IActionResult> Index(string SourceText)
            {
                // Look up the most recent 20 translations.
                var query = _translations.OrderByDescending("TimeStamp")
                    .Limit(20);
                var snapshotTask = query.GetSnapshotAsync();
    
                if (!string.IsNullOrWhiteSpace(SourceText))
                {
                    // Submit a new translation request.
                    await _publisher.PublishAsync(new PubsubMessage()
                    {
                        Data = ByteString.CopyFromUtf8(SourceText)
                    });
                }
    
                // Render the page.
                var model = new HomeViewModel()
                {
                    Translations = (await snapshotTask).Documents.Select(
                        doc => doc.ConvertTo<Translation>()).ToList(),
                    SourceText = SourceText
                };
                return View(model);
            }
    
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }
  • כדי לבקש תרגומים חדשים, צריך לשלוח טופס HTML. ה-handler של בקשת התרגום מאמת את הבקשה ומפרסם הודעה ב-Pub/Sub:

    // Submit a new translation request.
    await _publisher.PublishAsync(new PubsubMessage()
    {
        Data = ByteString.CopyFromUtf8(SourceText)
    });
    

פריסת שירות TranslateUI

  • בספרייה BackgroundProcessing, מריצים את סקריפט PowerShell כדי ליצור ולפרוס את השירות ב-Cloud Run:

    ./PublishTo-CloudRun.ps1

הסבר על סקריפט PublishTo-CloudRun.ps1

הסקריפט PublishTo-CloudRun.ps1 מפרסם את האפליקציה ב-Cloud Run.

הסקריפט מבצע את הפעולות הבאות:

  1. מבצע build של האפליקציה באופן מקומי באמצעות dotnet publish.
  2. מבצעים Build לקונטיינר שמריץ את האפליקציה באמצעות Cloud Build.
  3. פריסת האפליקציה ב-Cloud Run.

    # 1. Build the application locally.
    dotnet publish -c Release
    # 2. Use Google Cloud Build to build the UI's container and publish to Google
    # Container Registry. 
    gcloud builds submit --tag gcr.io/$projectId/translate-ui `
        TranslateUI/bin/Release/netcoreapp2.1/publish
    
    # 3. Run the container with Google Cloud Run.
    gcloud beta run deploy translate-ui --region $region --platform managed `
        --image gcr.io/$projectId/translate-ui --allow-unauthenticated
    
    

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

אחרי שהסקריפט PublishTo-CloudRun.ps1 יפעל בהצלחה, נסו לבקש תרגום.

  1. הפקודה האחרונה בסקריפט PublishTo-CloudRun.ps1 מציינת את כתובת ה-URL של שירות ממשק המשתמש. בחלון המסוף, מאתרים את כתובת ה-URL של שירות TranslateUI:

    gcloud beta run services describe translate-ui --region $region --format="get(status.address.hostname)"
  2. בדפדפן, עוברים לכתובת ה-URL שקיבלתם בשלב הקודם.

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

  3. בשדה Text to translate (טקסט לתרגום), מזינים טקסט לתרגום, למשל Hello, World..

  4. לוחצים על שליחה.

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

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

אם אתם לא מצליחים להתחבר לשירות Cloud Run או שלא רואים תרגומים חדשים, כדאי לבדוק את הדברים הבאים:

  • בודקים שהסקריפט PublishTo-CloudRun.ps1 הושלם בהצלחה ולא הפיק שגיאות. אם היו שגיאות (לדוגמה, message=Build failed), צריך לתקן אותן ולנסות להריץ שוב.

  • בודקים אם יש שגיאות ביומנים:

    1. נכנסים לדף Cloud Run במסוף Google Cloud .

      כניסה לדף Cloud Run

    2. לוחצים על שם השירות, translate-ui.

    3. לוחצים על יומנים.

הסרת המשאבים

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

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

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

    כניסה לדף Manage resources

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

מחיקת שירותי Cloud Run.

  • מוחקים את שירותי Cloud Run שיצרתם במדריך הזה:

    gcloud beta run services delete --region=$region translate-ui
    gcloud beta run services delete --region=$region translate-worker

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