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?
Jak oceniasz swoje doświadczenia z Google Cloud Platform?
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:
i kliknij „NOWY PROJEKT”. w wyświetlonym oknie, aby utworzyć nowy projekt:
Jeśli nie masz jeszcze projektu, zobaczysz takie okno dialogowe umożliwiające utworzenie pierwszego:
W kolejnym oknie tworzenia projektu możesz wpisać szczegóły nowego projektu:
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.
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).
- Aby aktywować Cloud Shell z poziomu konsoli Cloud, kliknij Aktywuj Cloud Shell (udostępnienie środowiska i połączenie z nim powinno zająć tylko chwilę).
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:
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>
- 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 w menu Hamburger po lewej stronie lub wyszukaj usługę Spanner, naciskając „/” i wpisz „Spanner”
Następnie kliknij 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.
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:
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.
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ę:
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.
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.
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:
- Którzy gracze są w „największej dziesiątce” wszech czasów?
- Którzy gracze są w „największej dziesiątce” roku?
- Którzy gracze są w „największej dziesiątce” dzień miesiąca?
- 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, ×tamp); 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, ×tamp); 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:
- Przeczytaj dokument na temat platformy Sppanner CAP (w języku angielskim).
- Poznaj sprawdzone metody dotyczące projektowania schematu i zapytań
- Więcej informacji o sygnaturach czasowych zatwierdzenia w Cloud Spanner
Prześlij nam swoją opinię
- Poświęć chwilę na wypełnienie naszej bardzo krótkiej ankiety