Cloud Spanner: tworzenie tabeli wyników w grach w Go

1. Omówienie

Google Cloud Spanner to w pełni zarządzana, skalowalna w poziomie, rozproszona globalnie, relacyjna usługa baz danych, która zapewnia transakcje ACID i semantykę SQL bez utraty wydajności i wysokiej dostępności.

W tym module dowiesz się, jak skonfigurować instancję Cloud Spanner. Wykonasz kolejne kroki tworzenia bazy danych i schematu, które będą służyć do tworzenia tabeli wyników w grach. Na początek utwórz tabelę zawodników, w której będą przechowywane informacje o graczach, oraz tabelę wyników, by zapisać wyniki.

Następnie wypełnij tabele przykładowymi danymi. Następnie na koniec modułu uruchomisz kilka przykładowych zapytań o 10 najczęstszych pytań i usuniesz instancję, aby zwolnić zasoby.

Czego się nauczysz

  • Jak skonfigurować instancję Cloud Spanner.
  • Jak utworzyć bazę danych i tabele.
  • Jak używać kolumny z sygnaturą czasową zatwierdzenia.
  • Jak wczytywać dane do tabeli bazy danych Cloud Spanner z sygnaturami czasowymi.
  • Jak wysyłać zapytania do bazy danych Cloud Spanner.
  • Jak usunąć instancję Cloud Spanner.

Czego potrzebujesz

Jak wykorzystasz ten samouczek?

Tylko do przeczytania Przeczytaj go i wykonaj ćwiczenia

Jak oceniasz swoje doświadczenia z Google Cloud Platform?

Początkujący Poziom średnio zaawansowany Biegły
.

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

Jeśli nie masz jeszcze konta Google (w Gmailu lub Google Apps), musisz je utworzyć. Zaloguj się w konsoli Google Cloud Platform ( console.cloud.google.com) i utwórz nowy projekt.

Jeśli masz już projekt, kliknij menu wyboru projektu w lewym górnym rogu konsoli:

6c9406d9b014760.png

i kliknij „NOWY PROJEKT”. w wyświetlonym oknie, aby utworzyć nowy projekt:

f708315ae07353d0.png

Jeśli nie masz jeszcze projektu, zobaczysz takie okno dialogowe umożliwiające utworzenie pierwszego:

870a3cbd6541ee86.png

W kolejnym oknie tworzenia projektu możesz wpisać szczegóły nowego projektu:

6a92c57d3250a4b3.png

Zapamiętaj identyfikator projektu, który jest niepowtarzalną nazwą we wszystkich projektach Google Cloud (powyższa nazwa jest już zajęta i nie będzie Ci odpowiadać). W dalszej części tego ćwiczenia w programie będzie ona określana jako PROJECT_ID.

Następnie musisz włączyć płatności w Developers Console, aby korzystać z zasobów Google Cloud i włączyć interfejs Cloud Spanner API.

15d0ef27a8fbab27.png

Ukończenie tego ćwiczenia w Codelabs nie powinno kosztować więcej niż kilka dolarów, ale może być droższe, jeśli zdecydujesz się użyć więcej zasobów lub nie chcesz ich uruchamiać (patrz sekcja „Czyszczenie” na końcu tego dokumentu). Cennik Google Cloud Spanner znajdziesz tutaj.

Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego w wysokości 300 USD, dzięki czemu te ćwiczenia z programowania są całkowicie bezpłatne.

Konfiguracja Google Cloud Shell

Usługi Google Cloud i Spanner można obsługiwać zdalnie z poziomu laptopa, ale w ramach tego ćwiczenia w programowaniu użyjemy Google Cloud Shell – środowiska wiersza poleceń działającego w chmurze.

Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia z programowania wystarczy przeglądarka (tak, działa ona na Chromebooku).

  1. Aby aktywować Cloud Shell z poziomu konsoli Cloud, kliknij Aktywuj Cloud Shell a8460e837e9f5fda.png (udostępnienie środowiska i połączenie z nim powinno zająć tylko chwilę).

b532b2f19ab85dda.png

Zrzut ekranu 2017-06-14 o 10.13.43 PM.png

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest już uwierzytelniony, a projekt jest już ustawiony na PROJECT_ID.

gcloud auth list

Dane wyjściowe polecenia

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

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest skonfigurowany, uruchom po prostu to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź identyfikator użyty w krokach konfiguracji lub wyszukaj go w panelu Cloud Console:

2485e00c1223af09.png

Cloud Shell ustawia też domyślnie niektóre zmienne środowiskowe, które mogą być przydatne podczas uruchamiania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Dane wyjściowe polecenia

<PROJECT_ID>
  1. Na koniec ustaw domyślną strefę i konfigurację projektu.
gcloud config set compute/zone us-central1-f

Możesz wybrać różne strefy. Więcej informacji znajdziesz w artykule Regiony i Strefy.

Podsumowanie

W tym kroku skonfigurujesz środowisko.

Dalsze czynności

Następnie skonfigurujesz instancję Cloud Spanner.

3. Konfigurowanie instancji Cloud Spanner

W tym kroku skonfigurujemy instancję Cloud Spanner na potrzeby tego ćwiczenia w Codelabs. Wyszukaj pozycję Spannera 1a6580bd3d3e6783.png w menu Hamburger po lewej stronie 3129589f7bc9e5ce.png lub wyszukaj usługę Spanner, naciskając „/” i wpisz „Spanner”

36e52f8df8e13b99.png

Następnie kliknij 19bb9864067757cb.png i wypełnij formularz, wpisując nazwę instancji cloudspanner-leaderboard dla swojej instancji, wybierając konfigurację (wybierz instancję regionalną) i ustaw liczbę węzłów. Do tego ćwiczenia w Codelabs potrzebujemy tylko 1 węzła. Aby korzystać z instancji produkcyjnych i kwalifikować się do gwarancji jakości usług Cloud Spanner, w instancji Cloud Spanner musisz mieć co najmniej 3 węzły.

Kliknij „Utwórz”. a w ciągu kilku sekund będziesz mieć do dyspozycji instancję Cloud Spanner.

dceb68e9ed3801e8.png

W następnym kroku użyjemy biblioteki klienta w języku Go, aby utworzyć bazę danych i schemat w naszej nowej instancji.

4. Tworzenie bazy danych i schematu

W tym kroku utworzymy naszą przykładową bazę danych i schemat.

Utwórzmy dwie tabele za pomocą biblioteki klienta w języku Go: tabela zawodników z informacjami o graczach i tabela wyników, w której są przechowywane ich wyniki. Przeprowadzimy Cię przez proces tworzenia aplikacji konsoli Go w Cloud Shell.

Najpierw skopiuj z GitHub przykładowy kod tego ćwiczenia z programowania, wpisując w Cloud Shell to polecenie:

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

Następnie zmień katalog na „Tablica wyników”. w którym utworzysz aplikację.

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

Cały kod wymagany do tego ćwiczenia w Codelabs znajduje się w dotychczasowym golang-samples/spanner/spanner_leaderboard/katalogu jako uruchamiana aplikacja w języku Go o nazwie leaderboard, która będzie służyć jako wzorzec. Utworzymy nowy katalog i etapami utworzymy kopię aplikacji Tabela wyników.

Utwórz nowy katalog o nazwie „codelab” dla aplikacji i zmień na nią katalog za pomocą tego polecenia:

mkdir codelab && cd $_

Teraz utwórzmy podstawową aplikację w języku Go o nazwie „Długi baner”. używający biblioteki klienta Spannera do tworzenia tabeli wyników złożonej z 2 tabel; Gracze i wyniki. Możesz to zrobić w edytorze Cloud Shell:

Otwórz edytor Cloud Shell, klikając „Otwórz edytor” wyróżniona poniżej ikona:

7519d016b96ca51b.png

Utwórz plik o nazwie „leaderboard.go”. w folderze ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.

  • Najpierw sprawdź, czy masz wybrany na liście folderów edytora Cloud Shell.
  • Następnie wybierz „Nowy plik”. w menu „Plik” edytora Cloud Shell .
  • Wpisz „leaderboard.go” .

To jest główny plik aplikacji, który będzie zawierał kod naszej aplikacji oraz odniesienia do wszelkich zależności.

Aby utworzyć bazę danych leaderboard oraz tabele Players i Scores, skopiuj (Ctrl + P) i wklej (Ctrl + V) ten kod Go do pliku leaderboard.go:

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)
        }
}

Aby zapisać zmiany wprowadzone w pliku leaderboard.go, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku leaderboard.go po dodaniu kodu włączającego polecenie createdatabase, możesz użyć pliku leaderboard.go z katalogu golang-samples/spanner/spanner_leaderboard.

Aby skompilować aplikację w Cloud Shell, uruchom polecenie „go build” z katalogu codelab, w którym znajduje się plik leaderboard.go:

go build leaderboard.go

Gdy aplikacja zostanie skompilowana, uruchom ją w Cloud Shell, wpisując to polecenie:

./leaderboard

Zostaną wyświetlone dane wyjściowe podobne do tych:

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.

Na podstawie tej odpowiedzi widzimy, że jest to aplikacja Leaderboard, która obecnie ma 1 możliwe polecenie: createdatabase. Jak widać, oczekiwanym argumentem polecenia createdatabase jest ciąg znaków zawierający identyfikator instancji i bazy danych.

Teraz uruchom następujące polecenie. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

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

W sekcji przeglądu baz danych Cloud Spanner w konsoli Cloud powinna pojawić się nowa baza danych i tabele w menu po lewej stronie.

a12fa65e352836b1.png

W następnym kroku zaktualizujemy naszą aplikację, aby wczytywała część danych do Twojej nowej bazy danych.

5. Wczytaj dane

Mamy teraz bazę danych o nazwie leaderboard, która zawiera 2 tabele; Players i Scores. Teraz użyjemy biblioteki klienta Go, aby wypełnić tabelę Players graczami, a tabelę Scores losowymi wynikami każdego z nich.

Jeśli edytor Cloud Shell nie jest jeszcze otwarty, otwórz go, klikając podświetloną ikonę:

7519d016b96ca51b.png

Następnie zmodyfikuj plik leaderboard.go w edytorze Cloud Shell, dodając polecenie insertplayers, które pozwala wstawić 100 odtwarzaczy do tabeli Players. Dodamy też polecenie insertscores umożliwiające wstawienie 4 losowych wyników w tabeli Scores każdego gracza w tabeli Players.

Najpierw zaktualizuj sekcję imports u góry pliku leaderboard.go, zastępując obecną sekcję. Gdy to zrobisz, powinna wyglądać mniej więcej tak:

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"
)

Następnie dodaj nowy typ polecenia wraz z listą poleceń na górze pliku, tuż pod wierszem zaczynającym się od „wpisz polecenie adminCommand ...” aby po zakończeniu konfiguracji wszystko było wyglądać tak:

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,
        }
)

Następnie dodaj te funkcje insertPlayer i insertScores pod istniejącą funkcją 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
}

Następnie, aby polecenie insert działało, dodaj ten kod do polecenia „uruchomienie” aplikacji poniżej instrukcji obsługi createdatabase, zastępując instrukcję 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

Gdy skończysz, funkcja run powinna wyglądać tak:

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
}

Ostatni krok potrzebny do dodania funkcji „insert” do Twojej aplikacji jest dodanie tekstu pomocy do i „insertscores” do funkcji flag.Usage(). Dodaj ten tekst pomocy do funkcji flag.Usage(), aby dołączyć tekst pomocy do poleceń wstawiania:

Dodaj te 2 polecenia do listy możliwych poleceń:

Command can be one of: createdatabase, insertplayers, insertscores

Dodaj ten dodatkowy tekst pomocy pod tekstem pomocy dotyczącym polecenia 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.

Aby zapisać zmiany wprowadzone w pliku leaderboard.go, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku leaderboard.go po dodaniu kodu umożliwiającego włączenie poleceń insertplayers i insertscores, możesz użyć pliku leaderboard.go z katalogu golang-samples/spanner/spanner_leaderboard.

Teraz skompiluj i uruchommy aplikację, aby sprawdzić, czy nowe polecenia insertplayers i insertscores są uwzględnione na liście możliwych poleceń aplikacji. Uruchom to polecenie, aby skompilować aplikację:

go build leaderboard.go

Uruchom powstałą aplikację w Cloud Shell, wpisując to polecenie:

./leaderboard

Polecenia insertplayers i insertscores powinny być teraz widoczne w domyślnych danych wyjściowych aplikacji:

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.

Teraz uruchom polecenie insertplayers z tymi samymi wartościami argumentów, które użyliśmy przy wywołaniu polecenia createdatabase. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

Inserted players

Użyjmy teraz biblioteki klienta w języku Go, aby uzupełnić tabelę Scores 4 losowymi wynikami wraz z sygnaturami czasowymi każdego gracza w tabeli Players.

Kolumna Timestamp w tabeli Scores jest zdefiniowana jako „sygnatura czasowa zatwierdzenia” za pomocą tej instrukcji SQL, która została wykonana podczas wcześniejszego uruchamiania polecenia 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

Zwróć uwagę na atrybut OPTIONS(allow_commit_timestamp=true). Powoduje to, że plik Timestamp jest „sygnaturą czasową zatwierdzenia” i umożliwia jego automatyczne wypełnianie dokładną sygnaturą czasową transakcji w przypadku operacji INSERT i UPDATE w danym wierszu tabeli.

W polu „sygnatura czasowa zatwierdzenia” możesz też wstawić własne wartości sygnatury czasowej o ile wstawisz sygnaturę czasową z wartością z przeszłości, co zrobimy na potrzeby tego ćwiczenia z programowania.

Teraz uruchom polecenie insertscores z tymi samymi wartościami argumentów, które użyliśmy przy wywołaniu polecenia insertplayers. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

Inserted scores

Uruchomienie funkcji insertScores powoduje użycie poniższego fragmentu kodu w celu wstawienia losowo wygenerowanej sygnatury czasowej z datą i godziną przypadającą w przeszłości:

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,
        },
})

Aby automatycznie wypełniać kolumnę Timestamp sygnaturą czasową, w której znajduje się pole „Wstaw” jest realizowana transakcja, możesz zamiast tego wstawić stałą Go spanner.CommitTimestamp jak w tym fragmencie kodu:

...
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,
        },
})

Po zakończeniu wczytywania danych sprawdźmy wartości, które właśnie zapisaliśmy w nowych tabelach w sekcji Cloud Spanner w konsoli Cloud. Najpierw wybierz bazę danych leaderboard, a potem tabelę Players. Kliknij kartę Data. Powinny być widoczne dane w kolumnach PlayerId i PlayerName tabeli.

86dc5b927809a4ec.png

Następnie sprawdźmy, czy tabela wyników zawiera też dane, klikając tabelę Scores i wybierając kartę Data. Powinny być widoczne dane w kolumnach PlayerId, Timestamp i Score tabeli.

87c8610c92d3c612.png

Brawo! Zaktualizujmy aplikację, aby uruchomić kilka zapytań, które pozwolą nam utworzyć tabelę wyników w grach.

6. Uruchamianie zapytań dotyczących tabel wyników

Po skonfigurowaniu bazy danych i wczytaniu informacji do tabel utwórzmy długi baner przy użyciu tych danych. W tym celu musimy odpowiedzieć na następujące 4 pytania:

  1. Którzy gracze są w „największej dziesiątce” wszech czasów?
  2. Którzy gracze są w „największej dziesiątce” roku?
  3. Którzy gracze są w „największej dziesiątce” dzień miesiąca?
  4. Którzy gracze są w „największej dziesiątce” dnia tygodnia?

Zaktualizujmy naszą aplikację, aby uruchomić zapytania SQL, które pozwolą odpowiedzieć na te pytania.

Dodamy polecenie query i queryWithTimespan, które umożliwią uruchamianie zapytań w celu uzyskania odpowiedzi na pytania, które pozwolą uzyskać informacje wymagane do utworzenia tabeli wyników.

Edytuj plik leaderboard.go w edytorze Cloud Shell, aby zaktualizować aplikację przez dodanie polecenia query i queryWithTimespan. Dodamy również funkcję pomocniczą formatWithCommas, która będzie formatować wyniki za pomocą przecinków.

Najpierw zaktualizuj sekcję imports u góry pliku leaderboard.go, zastępując obecną sekcję. Gdy to zrobisz, powinna wyglądać mniej więcej tak:

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"
)

Następnie dodaj 2 poniższe funkcje i funkcję pomocniczą pod istniejącą metodą 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()
}

Następnie na górze pliku leaderboard.go dodaj „query” jako jedną z opcji polecenia w zmiennej commands, tuż pod opcją „insertscores": insertScores”, aby zmienna commands wyglądała tak:

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

Następnie dodaj „queryWithTimespan” jako opcję polecenia w funkcji run, pod „createdatabase” sekcji poleceń oraz nad sekcją „insert and query” w sekcji obsługi poleceń:

        // 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
        }

Gdy skończysz, funkcja run powinna wyglądać tak:

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
}

Następnie, aby polecenie queryWithTimespan działało, zaktualizuj blok kodu flag.Parse() w elemencie „main” aplikacji przez co wygląda tak:

        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)
        }

Ostatni krok potrzebny do dodania „zapytania” dla Twojej aplikacji jest dodanie tekstu pomocy do zapytania i „querywithtimespan” do funkcji flag.Usage(). Dodaj do funkcji flag.Usage() te wiersze kodu, aby dodać tekst pomocy do poleceń zapytania:

Dodaj oba „zapytania”, na listę możliwych poleceń:

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

Dodaj ten dodatkowy tekst pomocy pod tekstem pomocy dotyczącym polecenia 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.

Aby zapisać zmiany wprowadzone w pliku leaderboard.go, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku leaderboard.go po dodaniu kodu umożliwiającego włączenie poleceń query i querywithtimespan, możesz użyć pliku leaderboard.go z katalogu golang-samples/spanner/spanner_leaderboard.

Teraz skompiluj i uruchommy aplikację, aby sprawdzić, czy nowe polecenia query i querywithtimespan są uwzględnione na liście możliwych poleceń aplikacji.

Uruchom w Cloud Shell to polecenie, aby skompilować aplikację:

go build leaderboard.go

Uruchom powstałą aplikację w Cloud Shell, wpisując to polecenie:

./leaderboard

Polecenia query i querywithtimespan powinny być widoczne w domyślnych danych wyjściowych aplikacji jako nowa opcja polecenia:

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.

Jak widać w odpowiedzi, za pomocą polecenia query graczy wszechczasów. Widzimy również, że polecenie querywithtimespan umożliwia określenie przedziału czasu w godzinach, które ma być używane do filtrowania rekordów na podstawie ich wartości w kolumnie Timestamp tabeli Scores.

Uruchommy polecenie query, używając wartości argumentów podanych podczas uruchamiania polecenia create. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy wszech czasów jak poniżej:

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

Teraz uruchom polecenie querywithtimespan z niezbędnymi argumentami, aby wysłać zapytanie do listy „10 najwyższych wyników”. graczy w danym roku, określając przedział czasu równy liczbie godzin w roku, czyli 8760. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy roku, na przykład:

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

Teraz uruchommy polecenie querywithtimespan, aby wysłać zapytanie do listy „10 największych” graczy w miesiącu, określając „timespan” równa się liczbie godzin w miesiącu, która wynosi 730. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy w miesiącu na przykład tak:

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

Teraz uruchommy polecenie querywithtimespan, aby wysłać zapytanie do listy „10 największych” graczy tygodnia poprzez określenie „czasu trwania”, co tydzień, czyli 168 godzin. Pamiętaj, aby zastąpić my-project identyfikatorem projektu utworzonym na początku tego ćwiczenia.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy tygodnia na przykład poniżej:

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

Doskonale!

Teraz podczas dodawania rekordów Cloud Spanner przeskaluje bazę danych do potrzebnego rozmiaru. Bez względu na to, jak bardzo Twoja baza danych się rozrasta, tablica wyników Twojej gry może być nadal skalowalna z dokładnością dzięki usłudze Cloud Spanner i technologii Truetime.

7. Czyszczenie

Po całej zabawie ze Spannerem musimy też posprzątać nasz plac zabaw, by zaoszczędzić cenne zasoby i pieniądze. Na szczęście to łatwy krok. Wystarczy przejść do sekcji Cloud Spanner w konsoli Cloud i usunąć instancję, którą utworzyliśmy w kroku w Codelabs o nazwie „Skonfiguruj instancję Cloud Spanner”.

8. Gratulacje!

Omówione zagadnienia:

  • Schemat instancji, baz danych i tabel Google Cloud Spanner do tablicy wyników
  • Jak utworzyć aplikację konsoli Go
  • Jak utworzyć bazę danych i tabele Spannera za pomocą biblioteki klienta w języku Go
  • Jak wczytywać dane do bazy danych Spannera przy użyciu biblioteki klienta w języku Go
  • Jak wysłać zapytanie dotyczące listy „Top 10” wyników na podstawie Twoich danych za pomocą sygnatur czasowych zatwierdzenia usługi Spanner i biblioteki klienta w języku Go

Dalsze kroki:

Prześlij nam swoją opinię

  • Poświęć chwilę na wypełnienie naszej bardzo krótkiej ankiety