Cloud Spanner: با Go یک تابلوی امتیازات بازی ایجاد کنید

1. بررسی اجمالی

Google Cloud Spanner یک سرویس پایگاه داده رابطه‌ای با مقیاس‌پذیر افقی، توزیع‌شده در سطح جهانی و کاملاً مدیریت شده است که تراکنش‌های ACID و معنایی SQL را بدون کاهش عملکرد و در دسترس بودن بالا ارائه می‌کند.

در این آزمایشگاه، نحوه راه اندازی یک نمونه Cloud Spanner را خواهید آموخت. شما مراحل ایجاد یک پایگاه داده و طرحواره ای را طی خواهید کرد که می تواند برای امتیازات بازی استفاده شود. شما با ایجاد یک جدول بازیکنان برای ذخیره اطلاعات بازیکن و یک جدول امتیازات برای ذخیره امتیازات بازیکنان شروع خواهید کرد.

سپس جداول را با داده های نمونه پر می کنید. سپس با اجرای چند پرس و جوی نمونه برتر و در نهایت حذف نمونه برای آزاد کردن منابع، آزمایشگاه را به پایان می رسانید.

چیزی که یاد خواهید گرفت

  • نحوه راه اندازی یک نمونه Cloud Spanner.
  • نحوه ایجاد پایگاه داده و جداول
  • نحوه استفاده از ستون مهر زمان commit
  • نحوه بارگیری داده ها در جدول پایگاه داده Cloud Spanner با مُهر زمانی.
  • چگونه پایگاه داده Cloud Spanner خود را پرس و جو کنیم.
  • چگونه نمونه Cloud Spanner خود را حذف کنیم.

چه **** آنچه شما نیاز دارید

چگونه از این آموزش استفاده خواهید کرد؟

فقط از طریق آن را بخوانید آن را بخوانید و تمرینات را کامل کنید

تجربه خود را با Google Cloud Platform چگونه ارزیابی می کنید؟

تازه کار متوسط مسلط

2. راه اندازی و الزامات

تنظیم محیط خود به خود

اگر قبلاً یک حساب Google (Gmail یا Google Apps) ندارید، باید یک حساب ایجاد کنید . به کنسول Google Cloud Platform ( consol.cloud.google.com ) وارد شوید و یک پروژه جدید ایجاد کنید.

اگر قبلاً پروژه ای دارید، روی منوی کشویی انتخاب پروژه در سمت چپ بالای کنسول کلیک کنید:

6c9406d9b014760.png

و روی دکمه "پروژه جدید" در گفتگوی حاصل کلیک کنید تا یک پروژه جدید ایجاد کنید:

f708315ae07353d0.png

اگر قبلاً پروژه ای ندارید، باید یک دیالوگ مانند این را ببینید تا اولین پروژه خود را ایجاد کنید:

870a3cbd6541ee86.png

گفتگوی بعدی ایجاد پروژه به شما امکان می دهد جزئیات پروژه جدید خود را وارد کنید:

6a92c57d3250a4b3.png

شناسه پروژه را به خاطر بسپارید، که یک نام منحصر به فرد در تمام پروژه های Google Cloud است (نام بالا قبلاً گرفته شده است و برای شما کار نخواهد کرد، متأسفیم!). بعداً در این آزمایشگاه کد به عنوان PROJECT_ID نامیده خواهد شد.

در مرحله بعد، اگر قبلاً این کار را انجام نداده‌اید، برای استفاده از منابع Google Cloud و فعال کردن Cloud Spanner API، باید صورت‌حساب را در Developers Console فعال کنید .

15d0ef27a8fbab27.png

گذراندن این کد نباید بیش از چند دلار هزینه داشته باشد، اما اگر تصمیم به استفاده از منابع بیشتری داشته باشید یا آنها را در حال اجرا رها کنید، ممکن است بیشتر باشد (به بخش "پاکسازی" در انتهای این سند مراجعه کنید). قیمت Google Cloud Spanner در اینجا مستند شده است.

کاربران جدید Google Cloud Platform واجد شرایط استفاده آزمایشی رایگان 300 دلاری هستند که باید این نرم افزار کد را کاملاً رایگان کند.

Google Cloud Shell Setup

در حالی که Google Cloud و Spanner را می‌توان از راه دور از لپ‌تاپ شما کار کرد، در این نرم‌افزار از Google Cloud Shell استفاده می‌کنیم، یک محیط خط فرمان که در Cloud اجرا می‌شود.

این ماشین مجازی مبتنی بر دبیان با تمام ابزارهای توسعه که شما نیاز دارید بارگذاری شده است. این دایرکتوری اصلی 5 گیگابایتی دائمی را ارائه می دهد و در Google Cloud اجرا می شود و عملکرد شبکه و احراز هویت را بسیار افزایش می دهد. این بدان معنی است که تمام چیزی که برای این کد لبه نیاز دارید یک مرورگر است (بله، روی کروم بوک کار می کند).

  1. برای فعال کردن Cloud Shell از Cloud Console، کافی است روی Activate Cloud Shell کلیک کنید. a8460e837e9f5fda.png (تهیه و اتصال به محیط فقط چند لحظه طول می کشد).

b532b2f19ab85dda.png

Screen Shot 2017-06-14 at 10.13.43 PM.png

پس از اتصال به Cloud Shell، باید ببینید که قبلاً احراز هویت شده اید و پروژه قبلاً روی PROJECT_ID شما تنظیم شده است.

gcloud auth list

خروجی فرمان

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

خروجی فرمان

[core]
project = <PROJECT_ID>

اگر به دلایلی پروژه تنظیم نشد، به سادگی دستور زیر را صادر کنید:

gcloud config set project <PROJECT_ID>

به دنبال PROJECT_ID خود هستید؟ بررسی کنید از چه شناسه ای در مراحل راه اندازی استفاده کرده اید یا آن را در داشبورد Cloud Console جستجو کنید:

2485e00c1223af09.png

Cloud Shell همچنین برخی از متغیرهای محیطی را به صورت پیش‌فرض تنظیم می‌کند که ممکن است هنگام اجرای دستورات آینده مفید باشند.

echo $GOOGLE_CLOUD_PROJECT

خروجی فرمان

<PROJECT_ID>
  1. در نهایت، منطقه پیش فرض و پیکربندی پروژه را تنظیم کنید.
gcloud config set compute/zone us-central1-f

شما می توانید مناطق مختلفی را انتخاب کنید. برای اطلاعات بیشتر، به مناطق و مناطق مراجعه کنید.

خلاصه

در این مرحله محیط خود را راه اندازی می کنید.

بعدی

در مرحله بعد، یک نمونه Cloud Spanner را تنظیم خواهید کرد.

3. یک نمونه Cloud Spanner راه اندازی کنید

در این مرحله ما نمونه Cloud Spanner خود را برای این Codelab راه اندازی می کنیم. ورودی آچار را جستجو کنید 1a6580bd3d3e6783.png در سمت چپ منوی همبرگر بالا 3129589f7bc9e5ce.png یا با فشار دادن "/" عبارت "Spanner" را جستجو کنید و "Spanner" را تایپ کنید.

36e52f8df8e13b99.png

بعد، بر روی کلیک کنید 19bb9864067757cb.png و با وارد کردن نام نمونه cloudspanner-leaderboard برای نمونه خود، انتخاب یک پیکربندی (انتخاب یک نمونه منطقه ای) و تنظیم تعداد گره ها، فرم را پر کنید، برای این کد لبه تنها به 1 گره نیاز داریم. برای نمونه های تولید و برای واجد شرایط بودن برای Cloud Spanner SLA، باید 3 یا بیشتر گره را در نمونه Cloud Spanner خود اجرا کنید.

آخرین، اما نه کم اهمیت، روی "ایجاد" کلیک کنید و در عرض چند ثانیه یک نمونه Cloud Spanner در اختیار دارید.

dceb68e9ed3801e8.png

در مرحله بعدی از کتابخانه سرویس گیرنده Go برای ایجاد یک پایگاه داده و طرحواره در نمونه جدید خود استفاده می کنیم.

4. یک پایگاه داده و طرحواره ایجاد کنید

در این مرحله می‌خواهیم پایگاه داده و طرحواره خود را بسازیم.

بیایید از کتابخانه سرویس گیرنده Go برای ایجاد دو جدول استفاده کنیم. یک جدول بازیکنان برای اطلاعات بازیکن و یک جدول امتیازات برای ذخیره امتیازات بازیکنان. برای انجام این کار، مراحل ایجاد یک برنامه کنسول Go در Cloud Shell را طی می کنیم.

ابتدا با تایپ دستور زیر در Cloud Shell، کد نمونه این کد لبه را از Github کلون کنید:

export GO111MODULE=auto
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...

سپس دایرکتوری را به دایرکتوری "Leaderboard" تغییر دهید که در آن برنامه خود را ایجاد خواهید کرد.

cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard

تمام کدهای مورد نیاز برای این کد لبه در فهرست راهنمای golang-samples/spanner/spanner_leaderboard/ موجود به عنوان یک برنامه Go قابل اجرا با نام leaderboard قرار دارد تا در حین پیشروی شما از طریق Codelab به عنوان مرجع عمل کند. ما یک دایرکتوری جدید ایجاد می کنیم و یک کپی از برنامه Leaderboard در مراحل ایجاد می کنیم.

یک دایرکتوری جدید با نام "codelab" برای برنامه ایجاد کنید و دایرکتوری را با دستور زیر به آن تغییر دهید:

mkdir codelab && cd $_

حالا بیایید یک برنامه اصلی Go به نام "Leaderboard" ایجاد کنیم که از کتابخانه کلاینت Spanner برای ایجاد تابلوی امتیازات متشکل از دو جدول استفاده می کند. بازیکنان و امتیازات می توانید این کار را درست در ویرایشگر پوسته ابری انجام دهید:

ویرایشگر پوسته ابری را با کلیک بر روی نماد «ویرایشگر باز» که در زیر مشخص شده است، باز کنید:

7519d016b96ca51b.png

فایلی با نام "leaderboard.go" در پوشه ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab ایجاد کنید.

  • ابتدا مطمئن شوید که پوشه "codelab" را در لیست پوشه های Cloud Shell Editor انتخاب کرده اید.
  • سپس «فایل جدید» را در منوی «فایل» ویرایشگر پوسته ابری انتخاب کنید.
  • "leaderboard.go" را به عنوان نام فایل جدید وارد کنید.

این فایل اصلی برنامه است که حاوی کد برنامه ما و ارجاعاتی است که وابستگی ها را شامل می شود.

برای ایجاد پایگاه داده leaderboard و جداول Players و Scores ، کد Go زیر را در فایل leaderboard.go کپی کنید (Ctrl + P) و (Ctrl + V) پیست کنید:

package main

import (
        "context"
        "flag"
        "fmt"
        "io"
        "log"
        "os"
        "regexp"
        "time"

        "cloud.google.com/go/spanner"
        database "cloud.google.com/go/spanner/admin/database/apiv1"

        adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error


func createDatabase(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, db string) error {
        matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
        if matches == nil || len(matches) != 3 {
                return fmt.Errorf("Invalid database id %s", db)
        }
        op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
                Parent:          matches[1],
                CreateStatement: "CREATE DATABASE `" + matches[2] + "`",
                ExtraStatements: []string{
                        `CREATE TABLE Players(
                            PlayerId INT64 NOT NULL,
                            PlayerName STRING(2048) NOT NULL
                        ) PRIMARY KEY(PlayerId)`,
                        `CREATE TABLE Scores(
                            PlayerId INT64 NOT NULL,
                            Score INT64 NOT NULL,
                            Timestamp TIMESTAMP NOT NULL
                            OPTIONS(allow_commit_timestamp=true)
                        ) PRIMARY KEY(PlayerId, Timestamp),
                        INTERLEAVE IN PARENT Players ON DELETE NO ACTION`,
                },
        })
        if err != nil {
                return err
        }
        if _, err := op.Wait(ctx); err != nil {
                return err
        }
        fmt.Fprintf(w, "Created database [%s]\n", db)
        return nil
}

func createClients(ctx context.Context, db string) (*database.DatabaseAdminClient, *spanner.Client) {
        adminClient, err := database.NewDatabaseAdminClient(ctx)
        if err != nil {
                log.Fatal(err)
        }

        dataClient, err := spanner.NewClient(ctx, db)
        if err != nil {
                log.Fatal(err)
        }

        return adminClient, dataClient
}

func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
        cmd string, db string, timespan int) error {
        // createdatabase command
        if cmd == "createdatabase" {
                err := createDatabase(ctx, w, adminClient, db)
                if err != nil {
                        fmt.Fprintf(w, "%s failed with %v", cmd, err)
                }
                return err
        }
        return nil
}

func main() {
        flag.Usage = func() {
                fmt.Fprintf(os.Stderr, `Usage: leaderboard <command> <database_name> [command_option]

        Command can be one of: createdatabase

Examples:
        leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
                - Create a sample Cloud Spanner database along with sample tables in your project.
`)
        }

        flag.Parse()
        flagCount := len(flag.Args())
        if flagCount != 2 {
                flag.Usage()
                os.Exit(2)
        }

        cmd, db := flag.Arg(0), flag.Arg(1)
        // Set timespan to zero, as it's not currently being used
        var timespan int = 0

        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
        defer cancel()
        adminClient, dataClient := createClients(ctx, db)
        if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
                os.Exit(1)
        }
}

با انتخاب «ذخیره» در منوی «فایل» ویرایشگر Cloud Shell، تغییراتی را که در فایل leaderboard.go ایجاد کرده‌اید، ذخیره کنید.

می‌توانید از فایل leaderboard.go در فهرست راهنمای golang-samples/spanner/spanner_leaderboard استفاده کنید تا نمونه‌ای از نحوه نگاه فایل leaderboard.go شما پس از افزودن کد برای فعال کردن دستور createdatabase را ببینید.

برای ساخت برنامه خود در Cloud Shell، «go build» را از پوشه codelab که فایل leaderboard.go شما در آن قرار دارد اجرا کنید:

go build leaderboard.go

هنگامی که برنامه شما با موفقیت ساخته شد، برنامه به دست آمده را با وارد کردن دستور زیر در Cloud Shell اجرا کنید:

./leaderboard

شما باید خروجی را مانند زیر ببینید:

Usage: leaderboard <command> <database_name> [command_option]

        Command can be one of: createdatabase

Examples:
        leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
                - Create a sample Cloud Spanner database along with sample tables in your project.

از این پاسخ می بینیم که این برنامه Leaderboard است که در حال حاضر یک دستور ممکن دارد: createdatabase . می‌توانیم ببینیم که آرگومان مورد انتظار دستور createdatabase رشته‌ای است که شامل شناسه نمونه و شناسه پایگاه داده خاص است.

حالا دستور زیر را اجرا کنید. مطمئن شوید که my-project با شناسه پروژه ای که در ابتدای این کد لبه ایجاد کرده اید جایگزین کرده اید.

./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

پس از چند ثانیه باید پاسخی مانند زیر را مشاهده کنید:

Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard] 

در بخش نمای کلی پایگاه‌های داده Cloud Spanner در Cloud Console، باید پایگاه داده و جداول جدید خود را در منوی سمت چپ مشاهده کنید.

a12fa65e352836b1.png

در مرحله بعدی برنامه خود را برای بارگیری برخی از داده ها در پایگاه داده جدید شما به روز می کنیم.

5. بارگذاری داده ها

ما اکنون یک پایگاه داده به نام leaderboard داریم که شامل دو جدول است. Players و Scores اکنون بیایید از کتابخانه مشتری Go استفاده کنیم تا جدول Players خود را با بازیکنان و جدول Scores خود را با امتیازهای تصادفی برای هر بازیکن پر کنیم.

اگر قبلاً باز نشده است، ویرایشگر پوسته ابری را با کلیک بر روی نماد برجسته شده در زیر باز کنید:

7519d016b96ca51b.png

در مرحله بعد، فایل leaderboard.go را در ویرایشگر پوسته ابری ویرایش کنید تا دستور insertplayers اضافه کنید که می تواند برای وارد کردن 100 بازیکن در جدول Players استفاده شود. ما همچنین یک دستور insertscores اضافه می کنیم که می تواند برای درج 4 امتیاز تصادفی در جدول Scores برای هر بازیکن در جدول Players استفاده شود.

ابتدا بخش imports را در بالای فایل leaderboard.go به‌روزرسانی کنید، و جایگزین آنچه در حال حاضر وجود دارد را جایگزین کنید، به طوری که پس از اتمام کار به شکل زیر باشد:

import (
        "context"
        "flag"
        "fmt"
        "io"
        "log"
        "math/rand"
        "os"
        "regexp"
        "time"

        "cloud.google.com/go/spanner"
        database "cloud.google.com/go/spanner/admin/database/apiv1"

        "google.golang.org/api/iterator"
        adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

سپس یک نوع دستور جدید به همراه لیستی از دستورات را در بالای فایل، درست زیر خطی که با "type adminCommand ..." شروع می شود، اضافه کنید، به طوری که پس از اتمام کار، مانند زیر باشد:

type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error

type command func(ctx context.Context, w io.Writer, client *spanner.Client) error
var (
        commands = map[string]command{
                "insertplayers": insertPlayers,
                "insertscores":  insertScores,
        }
)

سپس insertPlayers زیر را اضافه کنید و توابع insertScores را زیر تابع createdatabase() موجود اضافه کنید:

func insertPlayers(ctx context.Context, w io.Writer, client *spanner.Client) error {
        // Get number of players to use as an incrementing value for each PlayerName to be inserted
        stmt := spanner.Statement{
                SQL: `SELECT Count(PlayerId) as PlayerCount FROM Players`,
        }
        iter := client.Single().Query(ctx, stmt)
        defer iter.Stop()
        row, err := iter.Next()
        if err != nil {
                return err
        }
        var numberOfPlayers int64 = 0
        if err := row.Columns(&numberOfPlayers); err != nil {
                return err
        }
        // Initialize values for random PlayerId
        rand.Seed(time.Now().UnixNano())
        min := 1000000000
        max := 9000000000
        // Insert 100 player records into the Players table
        _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
                stmts := []spanner.Statement{}
                for i := 1; i <= 100; i++ {
                        numberOfPlayers++
                        playerID := rand.Intn(max-min) + min
                        playerName := fmt.Sprintf("Player %d", numberOfPlayers)
                        stmts = append(stmts, spanner.Statement{
                                SQL: `INSERT INTO Players
                                                (PlayerId, PlayerName)
                                                VALUES (@playerID, @playerName)`,
                                Params: map[string]interface{}{
                                        "playerID":   playerID,
                                        "playerName": playerName,
                                },
                        })
                }
                _, err := txn.BatchUpdate(ctx, stmts)
                if err != nil {
                        return err
                }
                return nil
        })
        fmt.Fprintf(w, "Inserted players \n")
        return nil
}

func insertScores(ctx context.Context, w io.Writer, client *spanner.Client) error {
        playerRecordsFound := false
        // Create slice for insert statements
        stmts := []spanner.Statement{}
        // Select all player records
        stmt := spanner.Statement{SQL: `SELECT PlayerId FROM Players`}
        iter := client.Single().Query(ctx, stmt)
        defer iter.Stop()
        // Insert 4 score records into the Scores table for each player in the Players table
        for {
                row, err := iter.Next()
                if err == iterator.Done {
                        break
                }
                if err != nil {
                        return err
                }
                playerRecordsFound = true
                var playerID int64
                if err := row.ColumnByName("PlayerId", &playerID); err != nil {
                        return err
                }
                // Initialize values for random score and date
                rand.Seed(time.Now().UnixNano())
                min := 1000
                max := 1000000
                for i := 0; i < 4; i++ {
                        // Generate random score between 1,000 and 1,000,000
                        score := rand.Intn(max-min) + min
                        // Generate random day within the past two years
                        now := time.Now()
                        endDate := now.Unix()
                        past := now.AddDate(0, -24, 0)
                        startDate := past.Unix()
                        randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
                        randomDate := time.Unix(randomDateInSeconds, 0)
                        // Add insert statement to stmts slice
                        stmts = append(stmts, spanner.Statement{
                                SQL: `INSERT INTO Scores
                                                (PlayerId, Score, Timestamp)
                                                VALUES (@playerID, @score, @timestamp)`,
                                Params: map[string]interface{}{
                                        "playerID":  playerID,
                                        "score":     score,
                                        "timestamp": randomDate,
                                },
                        })
                }

        }
        if !playerRecordsFound {
                fmt.Fprintln(w, "No player records currently exist. First insert players then insert scores.")
        } else {
                _, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
                        // Commit insert statements for all scores to be inserted as a single transaction
                        _, err := txn.BatchUpdate(ctx, stmts)
                        return err
                })
                if err != nil {
                        return err
                }
                fmt.Fprintln(w, "Inserted scores")
        }
        return nil
}

سپس، برای کاربردی کردن دستور insert ، کد زیر را به تابع "run" برنامه خود در زیر عبارت createdatabase و جایگزین عبارت return nil اضافه کنید:

        // insert and query commands
        cmdFn := commands[cmd]
        if cmdFn == nil {
                flag.Usage()
                os.Exit(2)
        }
        err := cmdFn(ctx, w, dataClient)
        if err != nil {
                fmt.Fprintf(w, "%s failed with %v", cmd, err)
        }
        return err

پس از اتمام کار، تابع run باید به شکل زیر باشد:

func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
        cmd string, db string, timespan int) error {
        // createdatabase command
        if cmd == "createdatabase" {
                err := createDatabase(ctx, w, adminClient, db)
                if err != nil {
                        fmt.Fprintf(w, "%s failed with %v", cmd, err)
                }
                return err
        }

        // insert and query commands
        cmdFn := commands[cmd]
        if cmdFn == nil {
                flag.Usage()
                os.Exit(2)
        }
        err := cmdFn(ctx, w, dataClient)
        if err != nil {
                fmt.Fprintf(w, "%s failed with %v", cmd, err)
        }
        return err
}

آخرین مرحله برای تکمیل افزودن قابلیت "insert" به برنامه شما، افزودن متن راهنما برای دستورات "insertplayers" و "insertscores" به تابع flag.Usage() است. متن راهنما زیر را به flag.Usage() اضافه کنید تا متن راهنما را برای دستورات درج اضافه کنید:

دو دستور را به لیست دستورات ممکن اضافه کنید:

Command can be one of: createdatabase, insertplayers, insertscores

و این متن کمکی اضافی را در زیر متن راهنما برای دستور createdatabase اضافه کنید.

        leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
                - Insert 100 sample Player records into the database.
        leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
                - Insert sample score data into Scores sample Cloud Spanner database table.

با انتخاب «ذخیره» در منوی «فایل» ویرایشگر Cloud Shell، تغییراتی را که در فایل leaderboard.go ایجاد کرده‌اید، ذخیره کنید.

می‌توانید از فایل leaderboard.go در فهرست راهنمای golang-samples/spanner/spanner_leaderboard استفاده کنید تا نمونه‌ای از نحوه نگاه فایل leaderboard.go شما پس از افزودن کد برای فعال کردن دستورات insertplayers و insertscores را ببینید.

حالا بیایید برنامه را بسازیم و اجرا کنیم تا تأیید کنیم که دستورات insertplayers و insertscores جدید در لیست دستورات ممکن برنامه گنجانده شده است. دستور زیر را برای ساخت اپلیکیشن اجرا کنید:

go build leaderboard.go

با وارد کردن دستور زیر برنامه به دست آمده را در پوسته ابری اجرا کنید:

./leaderboard

اکنون باید دستورات insertplayers و insertscores در خروجی پیش فرض برنامه مشاهده کنید:

Usage: leaderboard <command> <database_name> [command_option]

        Command can be one of: createdatabase, insertplayers, insertscores

Examples:
        leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
                - Create a sample Cloud Spanner database along with sample tables in your project.
        leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
                - Insert 100 sample Player records into the database.
        leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
                - Insert sample score data into Scores sample Cloud Spanner database table.

حالا بیایید دستور insertplayers را با همان مقادیر آرگومان که هنگام فراخوانی دستور createdatabase استفاده کردیم، اجرا کنیم. مطمئن شوید که my-project با شناسه پروژه ای که در ابتدای این کد لبه ایجاد کرده اید جایگزین کرده اید.

./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

پس از چند ثانیه باید پاسخی مانند زیر را مشاهده کنید:

Inserted players

اکنون بیایید از کتابخانه سرویس گیرنده Go استفاده کنیم تا جدول Scores خود را با چهار امتیاز تصادفی همراه با مهرهای زمانی برای هر بازیکن در جدول Players پر کنیم.

ستون Timestamp جدول Scores به عنوان یک ستون "commit timestamp" از طریق عبارت SQL زیر تعریف شد که زمانی که قبلاً دستور create را اجرا می کردیم اجرا می شد:

CREATE TABLE Scores(
  PlayerId INT64 NOT NULL,
  Score INT64 NOT NULL,
  Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
    INTERLEAVE IN PARENT Players ON DELETE NO ACTION

به ویژگی OPTIONS(allow_commit_timestamp=true) توجه کنید. این موضوع Timestamp به یک ستون "Commit timestamp" تبدیل می‌کند و آن را قادر می‌سازد تا به طور خودکار با مهر زمانی دقیق تراکنش برای عملیات INSERT و UPDATE در یک ردیف جدول مشخص شود.

همچنین می‌توانید مقادیر مهر زمانی خود را در ستون «مهر زمانی تعهد» درج کنید، البته تا زمانی که یک مهر زمانی با مقداری در گذشته وارد کنید، این همان کاری است که ما برای هدف این آزمایشگاه انجام خواهیم داد.

حالا بیایید دستور insertscores را با همان مقادیر آرگومان که هنگام فراخوانی دستور insertplayers استفاده کردیم، اجرا کنیم. مطمئن شوید که my-project با شناسه پروژه ای که در ابتدای این کد لبه ایجاد کرده اید جایگزین کرده اید.

./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

پس از چند ثانیه باید پاسخی مانند زیر را مشاهده کنید:

Inserted scores

اجرای تابع insertScores از قطعه کد زیر برای درج مهر زمانی تولید شده به طور تصادفی با تاریخ-زمان در گذشته استفاده می کند:

now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
stmts = append(stmts, spanner.Statement{
        SQL: `INSERT INTO Scores
              (PlayerId, Score, Timestamp)
                 VALUES (@playerID, @score, @timestamp)`,
        Params: map[string]interface{}{
                "playerID":  playerID,
                "score":     score,
                "timestamp": randomDate,
        },
})

برای پر کردن خودکار ستون Timestamp با مهر زمانی دقیقاً زمانی که تراکنش «Insert» انجام می‌شود، می‌توانید به جای آن، spanner.CommitTimestamp ثابت Go را مانند قطعه کد زیر وارد کنید.

...
stmts = append(stmts, spanner.Statement{
        SQL: `INSERT INTO Scores
              (PlayerId, Score, Timestamp)
                 VALUES (@playerID, @score, @timestamp)`,
        Params: map[string]interface{}{
                "playerID":  playerID,
                "score":     score,
                "timestamp": spanner.CommitTimestamp,
        },
})

اکنون که بارگیری داده ها را کامل کرده ایم، بیایید مقادیری را که به تازگی در جداول جدید خود در بخش Cloud Spanner در Cloud Console نوشته ایم، تأیید کنیم. ابتدا پایگاه داده leaderboard را انتخاب کنید و سپس جدول Players را انتخاب کنید. روی تب Data کلیک کنید. باید ببینید که داده‌هایی در ستون‌های PlayerId و PlayerName جدول دارید.

86dc5b927809a4ec.png

سپس اجازه دهید با کلیک بر روی جدول Scores و انتخاب زبانه Data ، بررسی کنیم که جدول امتیازات نیز دارای داده است. باید ببینید که داده‌هایی در ستون‌های PlayerId ، Timestamp و Score جدول دارید.

87c8610c92d3c612.png

آفرین! بیایید برنامه خود را به روز کنیم تا برخی از پرس و جوها را اجرا کنیم تا بتوانیم از آنها برای ایجاد تابلوی امتیازات بازی استفاده کنیم.

6. کوئری های تابلوی امتیازات را اجرا کنید

اکنون که پایگاه داده خود را راه اندازی کرده ایم و اطلاعات را در جداول خود بارگذاری کرده ایم، بیایید با استفاده از این داده ها یک تابلوی امتیازات ایجاد کنیم. برای این کار باید به چهار سوال زیر پاسخ دهیم:

  1. کدام بازیکنان "ده" برتر تمام دوران هستند؟
  2. کدام بازیکنان "ده" برتر سال هستند؟
  3. کدام بازیکنان "ده" برتر ماه هستند؟
  4. کدام بازیکنان "ده" برتر هفته هستند؟

بیایید برنامه خود را برای اجرای پرس و جوهای SQL که به این سؤالات پاسخ می دهند، به روز کنیم.

ما یک دستور query و یک دستور queryWithTimespan را اضافه می کنیم که راهی برای اجرای پرس و جوها برای پاسخ به سوالاتی که اطلاعات مورد نیاز برای تابلوی امتیازات ما را تولید می کند، ارائه می دهد.

فایل leaderboard.go را در Cloud Shell Editor ویرایش کنید تا برنامه را برای اضافه کردن یک دستور query و یک دستور queryWithTimespan به روز کنید. ما همچنین یک تابع کمکی formatWithCommas اضافه می کنیم تا نمرات خود را با کاما قالب بندی کنیم.

ابتدا بخش imports را در بالای فایل leaderboard.go به‌روزرسانی کنید، و جایگزین آنچه در حال حاضر وجود دارد را جایگزین کنید، به طوری که پس از اتمام کار به شکل زیر باشد:

import (
        "bytes"
        "context"
        "flag"
        "fmt"
        "io"
        "log"
        "math/rand"
        "os"
        "regexp"
        "strconv"
        "time"

        "cloud.google.com/go/spanner"
        database "cloud.google.com/go/spanner/admin/database/apiv1"

        "google.golang.org/api/iterator"
        adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

سپس دو تابع زیر و تابع کمکی را در زیر متد insertScores موجود اضافه کنید:

func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
        stmt := spanner.Statement{
                SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                        FROM Players p
                        JOIN Scores s ON p.PlayerId = s.PlayerId
                        ORDER BY s.Score DESC LIMIT 10`}
        iter := client.Single().Query(ctx, stmt)
        defer iter.Stop()
        for {
                row, err := iter.Next()
                if err == iterator.Done {
                        return nil
                }
                if err != nil {
                        return err
                }
                var playerID, score int64
                var playerName string
                var timestamp time.Time
                if err := row.Columns(&playerID, &playerName, &score, &timestamp); err != nil {
                        return err
                }
                fmt.Fprintf(w, "PlayerId: %d  PlayerName: %s  Score: %s  Timestamp: %s\n",
                        playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
        }
}

func queryWithTimespan(ctx context.Context, w io.Writer, client *spanner.Client, timespan int) error {
        stmt := spanner.Statement{
                SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                                FROM Players p
                                JOIN Scores s ON p.PlayerId = s.PlayerId 
                                WHERE s.Timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL @Timespan HOUR)
                                ORDER BY s.Score DESC LIMIT 10`,
                Params: map[string]interface{}{"Timespan": timespan},
        }
        iter := client.Single().Query(ctx, stmt)
        defer iter.Stop()
        for {
                row, err := iter.Next()
                if err == iterator.Done {
                        return nil
                }
                if err != nil {
                        return err
                }
                var playerID, score int64
                var playerName string
                var timestamp time.Time
                if err := row.Columns(&playerID, &playerName, &score, &timestamp); err != nil {
                        return err
                }
                fmt.Fprintf(w, "PlayerId: %d  PlayerName: %s  Score: %s  Timestamp: %s\n",
                        playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
        }
}

func formatWithCommas(n int64) string {
        numberAsString := strconv.FormatInt(n, 10)
        numberLength := len(numberAsString)
        if numberLength < 4 {
                return numberAsString
        }
        var buffer bytes.Buffer
        comma := []rune(",")
        bufferPosition := numberLength % 3
        if (bufferPosition) > 0 {
                bufferPosition = 3 - bufferPosition
        }
        for i := 0; i < numberLength; i++ {
                if bufferPosition == 3 {
                        buffer.WriteRune(comma[0])
                        bufferPosition = 0
                }
                bufferPosition++
                buffer.WriteByte(numberAsString[i])
        }
        return buffer.String()
}

سپس در بالای فایل leaderboard.go "query" را به عنوان یکی از گزینه های فرمان در متغیر commands ، درست زیر " insertscores": insertScores به طوری که متغیر commands به شکل زیر باشد:

var (
        commands = map[string]command{
                "insertplayers": insertPlayers,
                "insertscores":  insertScores,
                "query":         query,
        }
)

سپس «queryWithTimespan» را به‌عنوان یک گزینه فرمان در تابع run ، زیر بخش فرمان «createdatabase» و بالای بخش مدیریت دستورات «insert and query» اضافه کنید:

        // querywithtimespan command
        if cmd == "querywithtimespan" {
                err := queryWithTimespan(ctx, w, dataClient, timespan)
                if err != nil {
                        fmt.Fprintf(w, "%s failed with %v", cmd, err)
                }
                return err
        }

پس از اتمام کار، تابع run باید به شکل زیر باشد:

func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
        cmd string, db string, timespan int) error {
        // createdatabase command
        if cmd == "createdatabase" {
                err := createDatabase(ctx, w, adminClient, db)
                if err != nil {
                        fmt.Fprintf(w, "%s failed with %v", cmd, err)
                }
                return err
        }

        // querywithtimespan command
        if cmd == "querywithtimespan" {
                if timespan == 0 {
                        flag.Usage()
                        os.Exit(2)
                }
                err := queryWithTimespan(ctx, w, dataClient, timespan)
                if err != nil {
                        fmt.Fprintf(w, "%s failed with %v", cmd, err)
                }
                return err
        }

        // insert and query commands
        cmdFn := commands[cmd]
        if cmdFn == nil {
                flag.Usage()
                os.Exit(2)
        }
        err := cmdFn(ctx, w, dataClient)
        if err != nil {
                fmt.Fprintf(w, "%s failed with %v", cmd, err)
        }
        return err
}

سپس، برای کاربردی کردن دستور queryWithTimespan ، بلوک کد flag.Parse() را در متد "main" برنامه خود به‌روزرسانی کنید تا به شکل زیر باشد:

        flag.Parse()
        flagCount := len(flag.Args())
        if flagCount < 2 || flagCount > 3 {
                flag.Usage()
                os.Exit(2)
        }

        cmd, db := flag.Arg(0), flag.Arg(1)
        // If query timespan flag is specified, parse to int
        var timespan int = 0
        if flagCount == 3 {
                parsedTimespan, err := strconv.Atoi(flag.Arg(2))
                if err != nil {
                        fmt.Println(err)
                        os.Exit(2)
                }
                timespan = parsedTimespan
        }

        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
        defer cancel()
        adminClient, dataClient := createClients(ctx, db)
        if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
                os.Exit(1)
        }

آخرین مرحله برای تکمیل افزودن قابلیت "query" به برنامه شما، افزودن متن راهنما برای دستورات "query" و "querywithtimespan" به تابع flag.Usage() است. خطوط کد زیر را به تابع flag.Usage() اضافه کنید تا متن راهنما برای دستورات query اضافه شود:

دو دستور query را به لیست دستورات ممکن اضافه کنید:

Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan

و این متن کمکی اضافی را در زیر متن راهنما برای دستور insertscores اضافه کنید.

        leaderboard query projects/my-project/instances/my-instance/databases/example-db
                - Query players with top ten scores of all time.
        leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
                - Query players with top ten scores within a timespan specified in hours.

با انتخاب «ذخیره» در منوی «فایل» ویرایشگر Cloud Shell، تغییراتی را که در فایل leaderboard.go ایجاد کرده‌اید، ذخیره کنید.

می‌توانید از فایل leaderboard.go در فهرست راهنمای golang-samples/spanner/spanner_leaderboard استفاده کنید تا نمونه‌ای از نحوه نگاه فایل leaderboard.go شما پس از افزودن کد برای فعال کردن دستورات query و querywithtimespan را ببینید.

حالا بیایید برنامه را بسازیم و اجرا کنیم تا تأیید کنیم که دستورات query و querywithtimespan جدید در لیست دستورات ممکن برنامه گنجانده شده است.

برای ساخت اپلیکیشن دستور زیر را در Cloud Shell اجرا کنید:

go build leaderboard.go

با وارد کردن دستور زیر برنامه به دست آمده را در پوسته ابری اجرا کنید:

./leaderboard

اکنون باید دستورات query و querywithtimespan را به عنوان یک گزینه دستور جدید در خروجی پیش‌فرض برنامه مشاهده کنید:

Usage: leaderboard <command> <database_name> [command_option]

        Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan

Examples:
        leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
                - Create a sample Cloud Spanner database along with sample tables in your project.
        leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
                - Insert 100 sample Player records into the database.
        leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
                - Insert sample score data into Scores sample Cloud Spanner database table.
        leaderboard query projects/my-project/instances/my-instance/databases/example-db
                - Query players with top ten scores of all time.
        leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
                - Query players with top ten scores within a timespan specified in hours.

از پاسخ می‌بینید که می‌توانیم از دستور query برای دریافت لیستی از "ده بازیکن برتر" خود در تمام دوران استفاده کنیم. همچنین می‌توانیم ببینیم که دستور querywithtimespan به ما اجازه می‌دهد یک بازه زمانی را بر حسب ساعت تعیین کنیم تا از آن برای فیلتر کردن رکوردها بر اساس مقدار آنها در ستون Timestamp جدول Scores استفاده کنیم.

بیایید دستور query را با استفاده از همان مقادیر آرگومان که هنگام اجرای دستور create استفاده می کردیم، اجرا کنیم. مطمئن شوید که my-project با شناسه پروژه ای که در ابتدای این کد لبه ایجاد کرده اید جایگزین کرده اید.

./leaderboard query  projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

شما باید پاسخی را ببینید که شامل "ده بازیکن برتر" تمام دوران است مانند موارد زیر:

PlayerId: 4018687297  PlayerName: Player 83  Score: 999,618  Timestamp: 2017-07-01
PlayerId: 4018687297  PlayerName: Player 83  Score: 998,956  Timestamp: 2017-09-02
PlayerId: 4285713246  PlayerName: Player 51  Score: 998,648  Timestamp: 2017-12-01
PlayerId: 5267931774  PlayerName: Player 49  Score: 997,733  Timestamp: 2017-11-09
PlayerId: 1981654448  PlayerName: Player 35  Score: 997,480  Timestamp: 2018-12-06
PlayerId: 4953940705  PlayerName: Player 87  Score: 995,184  Timestamp: 2018-09-14
PlayerId: 2456736905  PlayerName: Player 84  Score: 992,881  Timestamp: 2017-04-14
PlayerId: 8234617611  PlayerName: Player 19  Score: 992,399  Timestamp: 2017-12-27
PlayerId: 1788051688  PlayerName: Player 76  Score: 992,265  Timestamp: 2018-11-22
PlayerId: 7127686505  PlayerName: Player 97  Score: 992,038  Timestamp: 2017-12-02

حالا بیایید دستور querywithtimespan را با آرگومان های لازم برای پرس و جو از "ده بازیکن برتر" سال اجرا کنیم، با تعیین "زمان زمانی" برابر با تعداد ساعت های یک سال که 8760 است. مطمئن شوید که my-project را جایگزین پروژه کرده اید. شناسه ای که در ابتدای این کد لبه ایجاد کردید.

./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760

شما باید پاسخی را ببینید که شامل «ده بازیکن برتر» سال مانند زیر است:

PlayerId: 1981654448  PlayerName: Player 35  Score: 997,480  Timestamp: 2018-12-06
PlayerId: 4953940705  PlayerName: Player 87  Score: 995,184  Timestamp: 2018-09-14
PlayerId: 1788051688  PlayerName: Player 76  Score: 992,265  Timestamp: 2018-11-22
PlayerId: 6862349579  PlayerName: Player 30  Score: 990,877  Timestamp: 2018-09-14
PlayerId: 5529627211  PlayerName: Player 16  Score: 989,142  Timestamp: 2018-03-30
PlayerId: 9743904155  PlayerName: Player 1  Score: 988,765  Timestamp: 2018-05-30
PlayerId: 6809119884  PlayerName: Player 7  Score: 986,673  Timestamp: 2018-05-16
PlayerId: 2132710638  PlayerName: Player 54  Score: 983,108  Timestamp: 2018-09-11
PlayerId: 2320093590  PlayerName: Player 79  Score: 981,373  Timestamp: 2018-05-07
PlayerId: 9554181430  PlayerName: Player 80  Score: 981,087  Timestamp: 2018-06-21

حالا بیایید دستور querywithtimespan را اجرا کنیم تا با تعیین یک "زمان زمانی" برابر با تعداد ساعت های یک ماه که 730 ساعت است، از "ده بازیکن برتر" ماه پرس و جو کنیم. مطمئن شوید که my-project را با ID پروژه ای که در آن ایجاد کرده اید جایگزین کنید ابتدای این کد لبه.

./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730

شما باید پاسخی را ببینید که شامل "ده بازیکن برتر" ماه است مانند زیر:

PlayerId: 3869829195  PlayerName: Player 69  Score: 949,686  Timestamp: 2019-02-19
PlayerId: 7448359883  PlayerName: Player 20  Score: 938,998  Timestamp: 2019-02-07
PlayerId: 1981654448  PlayerName: Player 35  Score: 929,003  Timestamp: 2019-02-22
PlayerId: 9336678658  PlayerName: Player 44  Score: 914,106  Timestamp: 2019-01-27
PlayerId: 6968576389  PlayerName: Player 40  Score: 898,041  Timestamp: 2019-02-21
PlayerId: 5529627211  PlayerName: Player 16  Score: 896,433  Timestamp: 2019-01-29
PlayerId: 9395039625  PlayerName: Player 59  Score: 879,495  Timestamp: 2019-02-09
PlayerId: 2094604854  PlayerName: Player 39  Score: 860,434  Timestamp: 2019-02-01
PlayerId: 9395039625  PlayerName: Player 59  Score: 849,955  Timestamp: 2019-02-21
PlayerId: 4285713246  PlayerName: Player 51  Score: 805,654  Timestamp: 2019-02-02

حالا بیایید دستور querywithtimespan را اجرا کنیم تا با تعیین یک "زمان زمانی" برابر با تعداد ساعت های هفته که 168 است my-project "ده بازیکن برتر" هفته پرس و جو کنیم . ابتدای این کد لبه.

./leaderboard querywithtimespan  projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168

شما باید پاسخی را ببینید که شامل "ده بازیکن برتر" هفته مانند زیر است:

PlayerId: 3869829195  PlayerName: Player 69  Score: 949,686  Timestamp: 2019-02-19
PlayerId: 1981654448  PlayerName: Player 35  Score: 929,003  Timestamp: 2019-02-22
PlayerId: 6968576389  PlayerName: Player 40  Score: 898,041  Timestamp: 2019-02-21
PlayerId: 9395039625  PlayerName: Player 59  Score: 849,955  Timestamp: 2019-02-21
PlayerId: 5954045812  PlayerName: Player 8  Score: 795,639  Timestamp: 2019-02-22
PlayerId: 3889939638  PlayerName: Player 71  Score: 775,252  Timestamp: 2019-02-21
PlayerId: 5529627211  PlayerName: Player 16  Score: 604,695  Timestamp: 2019-02-19
PlayerId: 9006728426  PlayerName: Player 3  Score: 457,208  Timestamp: 2019-02-22
PlayerId: 8289497066  PlayerName: Player 58  Score: 227,697  Timestamp: 2019-02-20
PlayerId: 8065482904  PlayerName: Player 99  Score: 198,429  Timestamp: 2019-02-24

کار عالی!

اکنون همانطور که رکوردها را اضافه می کنید، Cloud Spanner پایگاه داده شما را به هر اندازه که نیاز دارید مقیاس می دهد. مهم نیست که پایگاه داده شما چقدر رشد می کند، تابلوی امتیازات بازی شما می تواند با استفاده از Cloud Spanner و فناوری Truetime آن با دقت به مقیاس خود ادامه دهد.

7. پاکسازی

پس از همه سرگرمی‌های بازی با Spanner، باید زمین بازی خود را تمیز کنیم و منابع و پول گرانبها را ذخیره کنیم. خوشبختانه این یک مرحله آسان است، کافی است به بخش Cloud Spanner در Cloud Console بروید و نمونه‌ای را که در مرحله Codelab ایجاد کردیم به نام "Setup a Cloud Spanner Instance" را حذف کنید.

8. تبریک!

آنچه ما پوشش داده ایم:

  • نمونه‌های Google Cloud Spanner، پایگاه‌های داده و جدول جدول برای تابلوی امتیازات
  • نحوه ایجاد اپلیکیشن کنسول Go
  • نحوه ایجاد یک پایگاه داده و جداول Spanner با استفاده از کتابخانه سرویس گیرنده Go
  • نحوه بارگذاری داده ها در پایگاه داده Spanner با استفاده از کتابخانه سرویس گیرنده Go
  • نحوه پرس و جو کردن نتایج "Top Ten" از داده های خود با استفاده از مهر زمانی Spanner commit و کتابخانه سرویس گیرنده Go

مراحل بعدی:

نظرات خود را با ما در میان بگذارید

  • لطفا یک لحظه برای تکمیل نظرسنجی بسیار کوتاه ما وقت بگذارید