Cloud Spanner: Go ile oyunlar için skor tablosu oluşturma

1. Genel Bakış

Google Cloud Spanner, performanstan ve yüksek kullanılabilirlikten ödün vermeden ACID işlemleri ve SQL semantiği sağlayan, tümüyle yönetilen, yatay olarak ölçeklenebilir, küresel olarak dağıtılmış bir ilişkisel veritabanı hizmetidir.

Bu laboratuvarda, Cloud Spanner örneği oluşturmayı öğreneceksiniz. Oyun skor tablosu için kullanılabilecek bir veritabanı ve şema oluşturma adımlarını inceleyeceksiniz. Oyuncu bilgilerini saklamak için bir Oyuncular tablosu, oyuncu skorlarını saklamak için bir Skor tablosu oluşturarak başlayın.

Ardından tabloları örnek verilerle dolduracaksınız. Ardından, kaynakları boşa çıkarmak için en iyi on örnek sorguyu çalıştırarak ve son olarak da örneği silerek laboratuvarı tamamlayacaksınız.

Neler öğreneceksiniz?

  • Cloud Spanner örneği oluşturma.
  • Veritabanı ve tablo oluşturma
  • Kaydetme zaman damgası sütunu nasıl kullanılır?
  • Cloud Spanner veritabanı tablonuza zaman damgalarıyla veri yükleme.
  • Cloud Spanner veritabanınızı sorgulama.
  • Cloud Spanner örneğinizi silme.

İhtiyacınız olanlar

Bu eğiticiden nasıl yararlanacaksınız?

Yalnızca okuma Okuyun ve alıştırmaları tamamlayın

Google Cloud Platform deneyiminizi nasıl değerlendirirsiniz?

Acemi Orta Yeterli

2. Kurulum ve Gereksinimler

Kendi hızınızda ortam kurulumu

Google Hesabınız (Gmail veya Google Apps) yoksa bir hesap oluşturmanız gerekir. Google Cloud Platform konsolunda ( console.cloud.google.com) oturum açın ve yeni bir proje oluşturun.

Zaten bir projeniz varsa konsolun sol üst köşesindeki proje seçimi açılan menüsünü tıklayın:

6c9406d9b014760.png

Sonra ‘YENİ PROJE’yi tıklayın. düğmesini tıklayın:

f708315ae07353d0.png

Henüz projeniz yoksa ilk projenizi oluşturmak için şuna benzer bir iletişim kutusu görmeniz gerekir:

870a3cbd6541ee86.png

Sonraki proje oluşturma iletişim kutusu yeni projenizin ayrıntılarını girmenize olanak tanır:

6a92c57d3250a4b3.png

Tüm Google Cloud projeleri için benzersiz bir ad olan proje kimliğini unutmayın (yukarıdaki ad daha önce alınmış ve size uygun olmayacaktır!). Bu kod laboratuvarın ilerleyen bölümlerinde PROJECT_ID olarak adlandırılacaktır.

Ardından, henüz yapmadıysanız Developers Console'da faturalandırmayı etkinleştirmeniz ve Google Cloud kaynaklarını kullanmanız ve Cloud Spanner API'yi etkinleştirmeniz gerekir.

15d0ef27a8fbab27.png

Bu codelab'i çalıştırmanın maliyeti birkaç dolardan fazla değildir. Ancak daha fazla kaynak kullanmaya karar verirseniz veya bu kaynakları çalışır durumda bırakırsanız daha yüksek ücret ödemeniz gerekebilir (bu belgenin sonundaki "temizlik" bölümüne bakın). Google Cloud Spanner fiyatlandırması burada açıklanmıştır.

Yeni Google Cloud Platform kullanıcıları, bu codelab'i tamamen ücretsiz hale getirecek 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Google Cloud Shell Kurulumu

Google Cloud ve Spanner, dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de bu codelab'de, Cloud'da çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacağız.

Bu Debian tabanlı sanal makine, ihtiyacınız olan tüm geliştirme araçlarıyla yüklüdür. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud'da çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Yani bu codelab'de ihtiyacınız olan tek şey bir tarayıcıdır (Evet, Chromebook'ta çalışır).

  1. Cloud Shell'i Cloud Console'dan etkinleştirmek için Cloud Shell'i etkinleştir a8460e837e9f5fda.png simgesini tıklamanız yeterlidir (sağlanması ve ortama bağlanması yalnızca birkaç dakika sürer).

b532b2f19ab85dda.png

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

Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin PROJECT_ID olarak ayarlanmış olduğunu göreceksiniz.

gcloud auth list

Komut çıkışı

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

Komut çıkışı

[core]
project = <PROJECT_ID>

Herhangi bir nedenle proje ayarlanmamışsa şu komutu vermeniz yeterlidir:

gcloud config set project <PROJECT_ID>

PROJECT_ID cihazınızı mı arıyorsunuz? Kurulum adımlarında kullandığınız kimliği kontrol edin veya Cloud Console kontrol panelinden arayın:

2485e00c1223af09.png

Cloud Shell bazı ortam değişkenlerini de varsayılan olarak ayarlar. Bu değişkenler, gelecekte komut çalıştırdığınızda işinize yarayabilir.

echo $GOOGLE_CLOUD_PROJECT

Komut çıkışı

<PROJECT_ID>
  1. Son olarak, varsayılan alt bölgeyi ve proje yapılandırmasını ayarlayın.
gcloud config set compute/zone us-central1-f

Çeşitli farklı alt bölgeler seçebilirsiniz. Daha fazla bilgi için Bölgeler ve Bölgeler.

Özet

Bu adımda ortamınızı ayarlarsınız.

Sonraki bölüm

Şimdi bir Cloud Spanner örneği kuracaksınız.

3. Cloud Spanner Örneği Oluşturma

Bu adımda, bu codelab için Cloud Spanner örneğimizi oluşturduk. Soldaki Hamburger Menüsünde 1a6580bd3d3e6783.png Spanner girişini 3129589f7bc9e5ce.png veya "/" tuşuna basarak Spanner girişini arayın ve "Spanner" yazın.

36e52f8df8e13b99.png

Ardından 19bb9864067757cb.png düğmesini tıklayın ve örneğiniz için cloudspanner-leaderboard örnek adını girip bir yapılandırma seçip (bölgesel bir örnek seçin) ve düğüm sayısını ayarlayın. Bu codelab için yalnızca 1 düğüme ihtiyacımız olacaktır. Üretim örnekleri için ve Cloud Spanner HDS'si (Hizmet Düzeyi Sözleşmesi) şartlarına uygun olmak üzere, Cloud Spanner örneğinizde en az 3 düğüm çalıştırmanız gerekir.

Son olarak, "Oluştur"u tıklayın. ve birkaç saniye içinde Cloud Spanner örneğiniz olur.

dceb68e9ed3801e8.png

Sonraki adımda yeni örneğimizde veritabanı ve şema oluşturmak için Go istemci kitaplığını kullanacağız.

4. Veritabanı ve şema oluşturma

Bu adımda örnek veritabanımızı ve şemamızı oluşturacağız.

Go istemci kitaplığını kullanarak iki tablo oluşturalım: Oyuncu bilgileri için Oyuncular tablosu, oyuncu skorlarını kaydetmek için de Skorlar tablosu. Bunu yapmak için Cloud Shell'de Go konsolu uygulaması oluşturma adımlarını inceleyeceğiz.

Öncelikle Cloud Shell'de aşağıdaki komutu yazarak GitHub'dan bu codelab için örnek kodu klonlayın:

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

Ardından dizini "leaderboard" olarak değiştirin uygulamanızı oluşturacağınız dizindir.

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

Bu codelab'de gerekli olan tüm kodlar, codelab'de ilerlerken referans olarak kullanılmak üzere leaderboard adlı çalıştırılabilir bir Go uygulaması olarak mevcut golang-samples/spanner/spanner_leaderboard/ dizininde bulunmaktadır. Yeni bir dizin oluşturup Skor Tablosu uygulamasının bir kopyasını aşamalı olarak hazırlayacağız.

"codelab" adlı yeni bir dizin oluşturun dokunun ve aşağıdaki komutla dizini değiştirin:

mkdir codelab && cd $_

Şimdi "Leaderboard" adlı temel bir Go uygulaması oluşturalım iki tablodan oluşan bir leaderboard oluşturmak için Spanner istemci kitaplığını kullanan; Oyuncular ve Skorlar. Bu işlemi doğrudan Cloud Shell Düzenleyici'de yapabilirsiniz:

"Düzenleyiciyi aç"ı tıklayarak Cloud Shell Düzenleyici'yi açın. simge aşağıda vurgulanmıştır:

7519d016b96ca51b.png

"Leaderboard.go" adlı bir dosya oluşturun ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab klasöründe bulabilirsiniz.

  • Önce "codelab"i yüklediğinizden emin olun Cloud Shell Düzenleyici'nin klasör listesinde seçili olan klasörü görürsünüz.
  • Ardından "Yeni Dosya"yı seçin. Cloud Shell Düzenleyici'nin "Dosya" bölümünde tıklayın.
  • "leaderboard.go" yazın yeni dosyanın adı olarak ayarlanır.

Bu, uygulamanın uygulama kodumuzu ve bağımlılıkları içeren referansları içerecek ana dosyasıdır.

leaderboard veritabanı ile Players ve Scores tablolarını oluşturmak için aşağıdaki Go kodunu kopyalayıp (Ctrl + P) ve leaderboard.go dosyasına yapıştırın (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)
        }
}

leaderboard.go dosyasında yaptığınız değişiklikleri "Kaydet"i seçerek kaydedin. Cloud Shell Düzenleyici'nin "Dosya" bölümünde tıklayın.

createdatabase komutunu etkinleştirme kodu eklendikten sonra leaderboard.go dosyanızın nasıl görünmesi gerektiğine dair bir örnek görmek için golang-samples/spanner/spanner_leaderboard dizinindeki leaderboard.go dosyasını kullanabilirsiniz.

Uygulamanızı Cloud Shell'de derlemek için "go build"i çalıştırın leaderboard.go dosyanızın bulunduğu codelab dizininden:

go build leaderboard.go

Uygulamanız başarıyla oluşturulduğunda aşağıdaki komutu girerek Cloud Shell'de uygulamayı çalıştırın:

./leaderboard

Şuna benzer bir çıkış alırsınız:

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.

Bu yanıttan, şu anda olası bir komuta sahip olan Leaderboard uygulamasının bu olduğunu görüyoruz: createdatabase. createdatabase komutunun beklenen bağımsız değişkeni, belirli bir Örnek Kimliği ve Veritabanı Kimliği içeren bir dizedir.

Şimdi aşağıdaki komutu çalıştırın. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

Birkaç saniye sonra, aşağıdaki gibi bir yanıt görürsünüz:

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

Cloud Console'un Cloud Spanner veritabanlarına genel bakış bölümünde, sol taraftaki menüde yeni veritabanınızı ve tablolarınızı göreceksiniz.

a12fa65e352836b1.png

Sonraki adımda, yeni veritabanınıza bazı veriler yüklemek için uygulamamızı güncelleyeceğiz.

5. Verileri Yükle

Artık leaderboard adında iki tablo içeren bir veritabanımız var; Players ve Scores. Şimdi Players tablomuzu oyuncularla, Scores tablomuzu da her oyuncu için rastgele skorlarla doldurmak için Go istemci kitaplığını kullanalım.

Açık değilse Cloud Shell Düzenleyici'yi aşağıda vurgulanan simgeyi tıklayarak açın:

7519d016b96ca51b.png

Ardından, Players tablosuna 100 oynatıcı eklemek için kullanılabilecek bir insertplayers komutu eklemek üzere Cloud Shell Düzenleyici'de leaderboard.go dosyasını düzenleyin. Ayrıca, Players tablosundaki her bir oyuncu için Scores tablosuna 4 rastgele skor eklemek amacıyla kullanılabilecek bir insertscores komutu ekleyeceğiz.

Öncelikle leaderboard.go dosyasının üst kısmındaki imports bölümünü güncelleyin. Mevcut bölümü değiştirerek, işiniz bittiğinde aşağıdaki gibi görünmesi için bu bölümü değiştirin:

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

Sonra, dosyanın üst kısmına "adminCommand ..." ile başlayan satırın hemen altına, bir komut listesiyle birlikte yeni bir komut türü ekleyin. işlemi tamamladıktan sonra aşağıdaki gibi görünecektir:

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

Ardından, mevcut createdatabase() işlevinin altına aşağıdaki insertPlayers komutunu ve insertScores işlevlerini ekleyin:

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
}

Ardından insert komutunun çalışması için uygulamanızın "run" komutuna aşağıdaki kodu ekleyin. createdatabase işleme ifadesinin altında return nil deyiminin yerine geçecek şekilde :

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

İşiniz bittiğinde run işlevi aşağıdaki gibi görünmelidir:

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
}

"Ekle" eklemeyi tamamlamak için son adım işlevi, "insertplayers" için yardım metni eklemektir. ve "insertscores" gibi flag.Usage() işlevine komut dosyası ekler. Insert komutları için yardım metni dahil etmek üzere flag.Usage() işlevine aşağıdaki yardım metnini ekleyin:

İki komutu olası komutlar listesine ekleyin:

Command can be one of: createdatabase, insertplayers, insertscores

Bu ek yardım metnini, createdatabase komutuyla ilgili yardım metninin altına ekleyin.

        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.go dosyasında yaptığınız değişiklikleri "Kaydet"i seçerek kaydedin. Cloud Shell Düzenleyici'nin "Dosya" bölümünde tıklayın.

insertplayers ve insertscores komutlarını etkinleştirmek üzere kod eklendikten sonra leaderboard.go dosyanızın nasıl görünmesi gerektiğine ilişkin bir örnek görmek için golang-samples/spanner/spanner_leaderboard dizinindeki leaderboard.go dosyasını kullanabilirsiniz.

Şimdi yeni insertplayers ve insertscores komutlarının, uygulamanın olası komutlar listesine eklendiğini onaylamak için uygulamayı derleyip çalıştıralım. Uygulamayı derlemek için aşağıdaki komutu çalıştırın:

go build leaderboard.go

Aşağıdaki komutu girerek, oluşturulan uygulamayı Cloud Shell'de çalıştırın:

./leaderboard

insertplayers ve insertscores komutlarının artık uygulamanın varsayılan çıkışına dahil olduğunu göreceksiniz:

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.

Şimdi insertplayers komutunu, createdatabase komutunu çağırdığımızda kullandığımız bağımsız değişken değerleriyle çalıştıralım. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

Birkaç saniye sonra, aşağıdaki gibi bir yanıt görürsünüz:

Inserted players

Şimdi Scores tablomuzu Players tablosundaki her oyuncu için zaman damgalarıyla birlikte dört rastgele puanla doldurmak üzere Go istemci kitaplığını kullanalım.

Scores tablosunun Timestamp sütunu, "taahhüt zaman damgası" olarak tanımlandı sütununu kontrol edin: daha önce create komutunu çalıştırdığımızda yürütülen aşağıdaki SQL deyimi:

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) özelliğine dikkat edin. Bu durumda Timestamp, "taahhüt zaman damgası" haline gelir. sütununu görüntüler ve belirli bir tablo satırındaki INSERT ve UPDATE işlemleri için tam işlem zaman damgasıyla otomatik olarak doldurulmasını sağlar.

Ayrıca, bir "kaydetme zaman damgası"na kendi zaman damgası değerlerinizi de ekleyebilirsiniz. geçmiş bir değere sahip bir zaman damgası eklemeniz gerekir. Bu codelab'in amacı budur.

Şimdi insertscores komutunu, insertplayers komutunu çağırdığımızda kullandığımız bağımsız değişken değerleriyle çalıştıralım. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

Birkaç saniye sonra, aşağıdaki gibi bir yanıt görürsünüz:

Inserted scores

insertScores işlevini çalıştırdığınızda, rastgele oluşturulmuş bir zaman damgası (tarih/saat geçmişte olan bir zaman damgası) eklemek için aşağıdaki kod snippet'i kullanılır:

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 sütununun tam olarak "Insert" öğesinin zaman damgasıyla otomatik olarak doldurulması için işlemi gerçekleşirse bunun yerine Go sabitini spanner.CommitTimestamp ekleyebilirsiniz. Bunun için aşağıdaki kod snippet'ini kullanabilirsiniz:

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

Veri yüklemeyi tamamladığımıza göre, Cloud Console'un Cloud Spanner bölümünde yeni tablolarımıza yazdığımız değerleri doğrulayalım. Öncelikle leaderboard veritabanını ve ardından Players tablosunu seçin. Data sekmesini tıklayın. Tablonun PlayerId ve PlayerName sütunlarında veri bulunduğunu göreceksiniz.

86dc5b927809a4ec.png

Şimdi de Scores tablosunu tıklayıp Data sekmesini seçerek Puanlar tablosunun veri içerdiğini doğrulayalım. Tablonun PlayerId, Timestamp ve Score sütunlarında verileriniz olduğunu göreceksiniz.

87c8610c92d3c612.png

Tebrikler! Oyun skor tablosu oluşturmak için kullanabileceğimiz bazı sorgular çalıştırmak için uygulamamızı güncelleyelim.

6. Skor tablosu sorgularını çalıştırma

Veritabanımızı kurup bilgileri tablolarımıza yüklediğimize göre şimdi bu verileri kullanarak bir skor tablosu oluşturalım. Bunu yapmak için aşağıdaki dört soruyu cevaplamamız gerekiyor:

  1. "En İyi On" Oyuncular var mı?
  2. "En İyi On" Oyuncular ne durumda?
  3. "En İyi On" Oyuncular hakkında bilgi edindiniz?
  4. "En İyi On" Oyuncular merak ediyor musunuz?

Şimdi uygulamamızı, bu soruları cevaplayacak SQL sorgularını çalıştıracak şekilde güncelleyelim.

Skor tablosu için gereken bilgileri üretecek soruları yanıtlamak amacıyla, sorgu çalıştırma yöntemi sağlayacak bir query komutu ve queryWithTimespan komutu ekleyeceğiz.

Uygulamayı query komutu ve queryWithTimespan komutu ekleyerek güncellemek için Cloud Shell Düzenleyici'de leaderboard.go dosyasını düzenleyin. Ayrıca, puanlarımızı virgülle biçimlendirmek için bir formatWithCommas yardımcı işlevi ekleyeceğiz.

Öncelikle leaderboard.go dosyasının üst kısmındaki imports bölümünü güncelleyin. Mevcut bölümü değiştirerek, işiniz bittiğinde aşağıdaki gibi görünmesi için bu bölümü değiştirin:

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

Ardından aşağıdaki iki işlevi ve yardımcı işlevi mevcut insertScores yönteminin altına ekleyin:

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

Ardından, leaderboard.go dosyasının en üstüne "query" ifadesini ekleyin. "insertscores": insertScores seçeneğinin hemen altında" commands değişkeninde bir komut seçeneği olarak bulunur. Böylece commands değişkeni şu şekilde görünür:

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

Ardından "queryWithTimespan" öğesini ekleyin run işlevi içinde, "createdatabase" öğesinin altındaki komut seçeneği olarak komut bölümünün üzerinde "insert and query" komut işleme bölümü:

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

İşiniz bittiğinde run işlevi aşağıdaki gibi görünmelidir:

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
}

Ardından, queryWithTimespan komutunun çalışması için uygulamanızın "main" komutundaki flag.Parse() kod bloğunu güncelleyin. yöntemini çağırın.

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

"Sorgu" eklemeyi tamamlamak için son adım işlevi, "sorgu" sorgusu için yardım metni eklemektir. ve "querywithtimespan" flag.Usage() işlevine komut dosyası ekler. Sorgu komutların yardım metnini dahil etmek için flag.Usage() işlevine aşağıdaki kod satırlarını ekleyin:

Bu iki "query"yi toplayın komut listesine eklemeniz gerekir:

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

Bu ek yardım metnini, insertscores komutuyla ilgili yardım metninin altına ekleyin.

        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.

leaderboard.go dosyasında yaptığınız değişiklikleri "Kaydet"i seçerek kaydedin. Cloud Shell Düzenleyici'nin "Dosya" bölümünde tıklayın.

query ve querywithtimespan komutlarını etkinleştirmek üzere kod eklendikten sonra leaderboard.go dosyanızın nasıl görünmesi gerektiğine ilişkin bir örnek görmek için golang-samples/spanner/spanner_leaderboard dizinindeki leaderboard.go dosyasını kullanabilirsiniz.

Şimdi yeni query ve querywithtimespan komutlarının, uygulamanın olası komutlar listesine eklendiğini onaylamak için uygulamayı derleyip çalıştıralım.

Uygulamayı derlemek için Cloud Shell'de aşağıdaki komutu çalıştırın:

go build leaderboard.go

Aşağıdaki komutu girerek, oluşturulan uygulamayı Cloud Shell'de çalıştırın:

./leaderboard

query ve querywithtimespan komutlarının artık uygulamanın varsayılan çıkışına yeni bir komut seçeneği olarak dahil edildiğini göreceksiniz:

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.

Yanıtta, "En İyi On" listesinin listesini almak için query komutunu kullanabileceğimizi görebilirsiniz. tüm zamanların en iyi oyuncuları. Ayrıca querywithtimespan komutunun, kayıtları Scores tablosunun Timestamp sütunundaki değerlerine göre filtrelemek için kullanılacak, saat cinsinden bir zaman aralığı belirtmemize olanak tanıdığını da görebiliriz.

create komutunu çalıştırdığımızda kullandığımız bağımsız değişken değerlerini kullanarak query komutunu çalıştıralım. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

"İlk On" bilgisini içeren bir yanıt göreceksiniz oyuncuları beğenmişsinizdir:

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

Şimdi "İlk On"u sorgulamak için gerekli bağımsız değişkenlerle birlikte querywithtimespan komutunu çalıştıralım. yılın oyuncuları için bir "zaman aralığı" 8.760 olan bir yıldaki saat sayısına eşittir. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

"İlk On" bilgisini içeren bir yanıt göreceksiniz en iyi performans gösteren oyuncuları şunlar:

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

Şimdi "İlk On"u sorgulamak için querywithtimespan komutunu çalıştıralım bir "zaman aralığı" belirleyerek ayın oyuncuları bir aydaki saat sayısına (730) eşittir. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

"İlk On" bilgisini içeren bir yanıt göreceksiniz şunlar gibi ayın oyuncuları:

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

Şimdi "İlk On"u sorgulamak için querywithtimespan komutunu çalıştıralım "zaman aralığı" belirleyerek haftanın oyuncuları bir haftadaki 168 saat olan saat sayısına eşittir. my-project yerine bu codelab'in başında oluşturduğunuz proje kimliğini emin olun.

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

"İlk On" bilgisini içeren bir yanıt göreceksiniz şunlar gibi haftanın oyuncuları:

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

Harika!

Artık kayıt ekledikçe Cloud Spanner, veritabanınızı ihtiyacınız ne kadar büyük olacak şekilde ölçeklendirir. Veritabanınız ne kadar büyürse yükselsin, oyununuzun skor tablosu, Cloud Spanner ve Truetime teknolojisiyle doğrulukla ölçeklendirilmeye devam edebilir.

7. Temizleme

Spanner ile oynadığımız eğlencenin ardından, oyun alanını temizleyerek değerli kaynaklardan ve paradan tasarruf etmemiz gerekiyor. Neyse ki bu, kolay bir adımdır. Cloud Console'un Cloud Spanner bölümüne gidip "Cloud Spanner Örneği Oluşturma" adlı codelab adımında oluşturduğumuz örneği silmeniz yeterlidir.

8. Tebrikler!

İşlediğimiz konular:

  • Leaderboard için Google Cloud Spanner Örnekleri, Veritabanları ve Tablo Şeması
  • Go konsolu uygulaması nasıl oluşturulur?
  • Go istemci kitaplığını kullanarak Spanner Veritabanı ve Tabloları oluşturma
  • Go istemci kitaplığını kullanarak Spanner Veritabanına veri yükleme
  • "İlk On"u sorgulama Spanner kaydetme zaman damgalarını ve Go istemci kitaplığını kullanarak verilerinizden sonuçlar

Sonraki Adımlar:

Geri bildiriminizi paylaşın

  • Lütfen çok kısa bir süre ayırarak anketimizi doldurun.