1. نظرة عامة
Google Cloud Spanner هي خدمة قاعدة بيانات ارتباطية وقابلة للتطوير أفقيًا وموزعة عالميًا، وتوفِّر معاملات ACID ودلالات SQL بدون التخلّي عن الأداء والتوفّر العالي.
سوف تتعلم في هذا التمرين المعملي كيفية إعداد مثيل Cloud Spanner. ستتعرف على خطوات إنشاء قاعدة بيانات ومخطط يمكن استخدامهما لقائمة الصدارة في الألعاب. ستبدأ بإنشاء جدول "اللاعبين" لتخزين معلومات اللاعب وجدول النتائج لتخزين نتائج اللاعبين.
بعد ذلك، ستقوم بتعبئة الجداول بنماذج بيانات. ثم ستختتم التمرين المعملي بتشغيل بعض أهم عشرة استعلامات وأخيرًا حذف المثيل لتحرير الموارد.
المعلومات التي ستطّلع عليها
- كيفية إعداد مثيل Cloud Spanner
- كيفية إنشاء قاعدة بيانات وجداول
- كيفية استخدام عمود الطابع الزمني للتنفيذ
- كيفية تحميل البيانات إلى جدول قاعدة بيانات Cloud Spanner مع الطوابع الزمنية
- كيفية طلب بحث في قاعدة بيانات Cloud Spanner
- كيفية حذف مثيل Cloud Spanner
كل ما ستحتاج إليه
كيف ستستخدم هذا البرنامج التعليمي؟
ما هو تقييمك لتجربتك في استخدام Google Cloud Platform؟
2. الإعداد والمتطلبات
إعداد بيئة ذاتية
إذا لم يكن لديك حساب Google (Gmail أو Google Apps)، يجب عليك إنشاء حساب. سجِّل الدخول إلى وحدة تحكُّم Google Cloud Platform ( console.cloud.google.com) وأنشئ مشروعًا جديدًا.
إذا كان لديك مشروع بالفعل، فانقر فوق القائمة المنسدلة لاختيار المشروع في أعلى يسار وحدة التحكم:
وانقر على "مشروع جديد" في مربع الحوار الناتج لإنشاء مشروع جديد:
إذا لم يكن لديك مشروع، من المفترض أن يظهر لك مربع حوار مثل هذا لإنشاء مشروعك الأول:
يتيح لك مربع الحوار اللاحق لإنشاء المشروع إدخال تفاصيل مشروعك الجديد:
يُرجى تذكُّر رقم تعريف المشروع، وهو اسم فريد في جميع مشاريع Google Cloud (سبق أن تم استخدام الاسم أعلاه ولن يكون مناسبًا لك). ستتم الإشارة إليها لاحقًا في هذا الدرس التطبيقي حول الترميز باسم PROJECT_ID
.
بعد ذلك، عليك تفعيل الفوترة في Developers Console لاستخدام موارد Google Cloud وتفعيل Cloud Spanner API إذا لم يسبق لك إجراء ذلك.
لن يكلفك تنفيذ هذا الدرس التطبيقي أكثر من بضعة دولارات، ولكن قد تزيد التكاليف إذا قررت استخدام المزيد من الموارد أو إذا تركتها قيد التشغيل (يُرجى الاطّلاع على قسم "التنظيف" في نهاية هذا المستند). يمكن الاطّلاع على أسعار خدمة Google Cloud Spanner هنا.
إنّ مستخدمي Google Cloud Platform الجدد مؤهّلون للاستفادة من فترة تجريبية مجانية بقيمة 300 دولار أمريكي، ما يجعل هذا الدرس التطبيقي حول الترميز بدون أي تكلفة.
إعداد Google Cloud Shell
يمكن إدارة Google Cloud وSpanner عن بُعد من الكمبيوتر المحمول، ولكن في هذا الدرس التطبيقي حول الترميز، سنستخدم Google Cloud Shell، وهي بيئة سطر أوامر يتم تشغيلها في السحابة الإلكترونية.
هذا الجهاز الافتراضي المستند إلى نظام دبيان محمل بكل أدوات التطوير التي ستحتاج إليها. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 5 غيغابايت ويتم تشغيله في Google Cloud، ما يحسّن بشكل كبير من أداء الشبكة والمصادقة. وهذا يعني أنّ كل ما ستحتاجه في هذا الدرس التطبيقي حول الترميز هو متصفّح (نعم، يعمل على جهاز Chromebook).
- لتفعيل Cloud Shell من Cloud Console، ما عليك سوى النقر على تفعيل Cloud Shell (من المفترَض أن تستغرق عملية الإعداد والاتصال بالبيئة بضع دقائق فقط).
بعد الربط بتطبيق Cloud Shell، من المفترض أن يظهر لك أنّه قد تمت مصادقتك وأنّ المشروع سبق أن تم ضبطه على PROJECT_ID
.
gcloud auth list
مخرجات الأمر
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
مخرجات الأمر
[core] project = <PROJECT_ID>
إذا لم يتم ضبط المشروع لسبب ما، ما عليك سوى إصدار الأمر التالي:
gcloud config set project <PROJECT_ID>
هل تبحث عن PROJECT_ID
؟ تحقَّق من المعرّف الذي استخدمته في خطوات الإعداد أو ابحث عنه في لوحة بيانات Cloud Console:
تضبط Cloud Shell أيضًا بعض متغيرات البيئة تلقائيًا، وهو ما قد يكون مفيدًا عند تشغيل الأوامر المستقبلية.
echo $GOOGLE_CLOUD_PROJECT
مخرجات الأمر
<PROJECT_ID>
- أخيرًا، قم بتعيين تهيئة المنطقة الافتراضية والمشروع.
gcloud config set compute/zone us-central1-f
يمكنك اختيار مجموعة متنوعة من المناطق المختلفة. لمزيد من المعلومات، راجع المناطق المناطق:
ملخّص
في هذه الخطوة، يتم إعداد البيئة.
التالي
بعد ذلك، سيتم إعداد مثيل Cloud Spanner.
3- إعداد مثيل Cloud Spanner
في هذه الخطوة، نُعِدّ مثيل Cloud Spanner لهذا الدرس التطبيقي حول الترميز. ابحث عن إدخال Spanner في قائمة همبرغر أعلى اليمين أو ابحث عن Spanner عن طريق الضغط على "/" واكتب "Spanner"
بعد ذلك، انقر على واملأ النموذج عن طريق إدخال اسم المثيل cloudspanner-leaderboard للمثيل، واختيار الإعدادات (اختيار مثيل إقليمي)، وضبط عدد العُقد. سنحتاج إلى عقدة واحدة فقط في هذا الدرس التطبيقي حول الترميز. بالنسبة إلى مثيلات الإنتاج وللتأهل لاتفاقية مستوى الخدمة في Cloud Spanner، ستحتاج إلى تشغيل 3 عُقد أو أكثر في مثيل Cloud Spanner.
أخيرًا وليس آخرًا، انقر على "إنشاء" وستكون لديك مثيل Cloud Spanner تحت تصرفك خلال ثوانٍ.
في الخطوة التالية، سنستخدم مكتبة عملاء Go لإنشاء قاعدة بيانات ومخطط في المثيل الجديد.
4. إنشاء قاعدة بيانات ومخطط
في هذه الخطوة، سننشئ نموذجًا لقاعدة البيانات والمخطط.
لنستخدم مكتبة عملاء Go لإنشاء جدولين؛ جدول "اللاعبين" للحصول على معلومات عن اللاعب وجدول النتائج لتخزين نتائج اللاعبين ولإجراء ذلك، سننتقل إلى خطوات إنشاء تطبيق Go console في Cloud Shell.
استنسِخ أولاً نموذج الرمز البرمجي لهذا الدرس التطبيقي من GitHub من خلال كتابة الأمر التالي في Cloud Shell:
export GO111MODULE=auto
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
ثم تغيير الدليل إلى "ليدربورد" الدليل الذي ستنشئ فيه التطبيق.
cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard
تتوفّر كلّ الرموز المطلوبة لهذا الدرس التطبيقي حول الترميز في دليل golang-samples/spanner/spanner_leaderboard/
الحالي كتطبيق Go قابل للتنفيذ باسم leaderboard
، ويمكن استخدامه كمرجع أثناء تقدّمك في الدرس التطبيقي حول الترميز. سننشئ دليلاً جديدًا وننشئ نسخة من تطبيق "ليدربورد" على مراحل.
إنشاء دليل جديد باسم "درس تطبيقي حول الترميز" للتطبيق وتغيير الدليل إليه باستخدام الأمر التالي:
mkdir codelab && cd $_
لنقم الآن بإنشاء تطبيق Go أساسي اسمه "Leaderboard" تستخدم مكتبة عملاء Spanner لإنشاء لوحة صدارة تتألف من جدولين؛ اللاعبون والنتائج ويمكنك إجراء ذلك مباشرةً في "محرِّر Cloud Shell" باتّباع الخطوات التالية:
افتح محرِّر Cloud Shell من خلال النقر على "Open editor" (فتح المحرِّر) المحدد أدناه:
أنشئ ملفًا باسم leaderboard.go في المجلد ~/gopath/src/github.com/GoogleCloudPlatform/golang- sample/spanner/codelab.
- تأكّد أولاً من أنّ لديك "درس تطبيقي حول الترميز" المجلد المحدَّد في قائمة المجلدات في "محرِّر Cloud Shell".
- ثم حدد "ملف جديد" ضمن الزر "File" في محرر Cloud Shell القائمة.
- أدخِل "leaderboard.go". كاسم للملف الجديد.
هذا هو الملف الرئيسي للتطبيق الذي سيحتوي على رمز التطبيق والمراجع الخاصة بنا لتضمين أي ملحقات.
لإنشاء قاعدة بيانات leaderboard
والجدولين Players
وScores
، انسخ (Ctrl + P) والصقه (Ctrl + V) رمز Go التالي في ملف 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)
}
}
احفظ التغييرات التي أجريتها على ملف leaderboard.go
من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.
يمكنك استخدام ملف leaderboard.go
في دليل golang-samples/spanner/spanner_leaderboard
للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف leaderboard.go
بعد إضافة الرمز لتفعيل الأمر createdatabase
.
لإنشاء تطبيقك في Cloud Shell، عليك تشغيل "go معيَّن". من دليل codelab
حيث يوجد ملف leaderboard.go
:
go build leaderboard.go
بعد إنشاء التطبيق بنجاح، شغِّل التطبيق الناتج في Cloud Shell من خلال إدخال الأمر التالي:
./leaderboard
من المفترض أن تظهر لك نتيجة على النحو التالي:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project.
من خلال هذا الردّ، يمكننا ملاحظة أنّ هذا هو تطبيق Leaderboard
الذي يتضمّن حاليًا أمرًا واحدًا محتملاً: createdatabase
. يتبيّن لنا أنّ الوسيطة المتوقّعة للأمر createdatabase
هي سلسلة تحتوي على معرّف مثيل ومعرّف قاعدة بيانات محدّدَين.
قم الآن بتشغيل الأمر التالي. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
في قسم النظرة العامة على قواعد بيانات Cloud Spanner في Cloud Console، من المفترض أن تظهر لك قاعدة البيانات والجداول الجديدة في القائمة الجانبية اليمنى.
في الخطوة التالية سنحدث تطبيقنا لتحميل بعض البيانات إلى قاعدة بياناتك الجديدة.
5- تحميل البيانات
لدينا الآن قاعدة بيانات تسمى leaderboard
تحتوي على جدولين؛ Players
وScores
لنستخدم الآن مكتبة عملاء Go لملء جدول Players
باللاعبين وجدول Scores
بنتائج عشوائية لكل لاعب.
إذا لم يكن التطبيق مفتوحًا، افتح محرِّر Cloud Shell بالنقر على الرمز المميّز أدناه:
بعد ذلك، عدِّل ملف leaderboard.go
في "محرِّر Cloud Shell" لإضافة الأمر insertplayers
الذي يمكن استخدامه لإدراج 100 لاعب في جدول Players
. سنضيف أيضًا الأمر insertscores
الذي يمكن استخدامه لإدراج 4 نتائج عشوائية في جدول Scores
لكل لاعب في جدول Players
.
حدِّث أولاً القسم imports
أعلى الملف leaderboard.go
، مع استبدال العناصر الموجودة حاليًا، بحيث يظهر على النحو التالي بعد الانتهاء:
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
بعد ذلك، أضف نوع أوامر جديد إلى جانب قائمة بالأوامر في أعلى الملف، أسفل السطر الذي يبدأ مباشرةً بـ "اكتب adminCommand ..." بحيث بمجرد الانتهاء، يجب أن يبدو على النحو التالي:
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
type command func(ctx context.Context, w io.Writer, client *spanner.Client) error
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
}
)
بعد ذلك، أضف دوال insertPlayers التالية ودوال insertScores أسفل دالة createdatabase()
الحالية:
func insertPlayers(ctx context.Context, w io.Writer, client *spanner.Client) error {
// Get number of players to use as an incrementing value for each PlayerName to be inserted
stmt := spanner.Statement{
SQL: `SELECT Count(PlayerId) as PlayerCount FROM Players`,
}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
row, err := iter.Next()
if err != nil {
return err
}
var numberOfPlayers int64 = 0
if err := row.Columns(&numberOfPlayers); err != nil {
return err
}
// Initialize values for random PlayerId
rand.Seed(time.Now().UnixNano())
min := 1000000000
max := 9000000000
// Insert 100 player records into the Players table
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
stmts := []spanner.Statement{}
for i := 1; i <= 100; i++ {
numberOfPlayers++
playerID := rand.Intn(max-min) + min
playerName := fmt.Sprintf("Player %d", numberOfPlayers)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Players
(PlayerId, PlayerName)
VALUES (@playerID, @playerName)`,
Params: map[string]interface{}{
"playerID": playerID,
"playerName": playerName,
},
})
}
_, err := txn.BatchUpdate(ctx, stmts)
if err != nil {
return err
}
return nil
})
fmt.Fprintf(w, "Inserted players \n")
return nil
}
func insertScores(ctx context.Context, w io.Writer, client *spanner.Client) error {
playerRecordsFound := false
// Create slice for insert statements
stmts := []spanner.Statement{}
// Select all player records
stmt := spanner.Statement{SQL: `SELECT PlayerId FROM Players`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
// Insert 4 score records into the Scores table for each player in the Players table
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return err
}
playerRecordsFound = true
var playerID int64
if err := row.ColumnByName("PlayerId", &playerID); err != nil {
return err
}
// Initialize values for random score and date
rand.Seed(time.Now().UnixNano())
min := 1000
max := 1000000
for i := 0; i < 4; i++ {
// Generate random score between 1,000 and 1,000,000
score := rand.Intn(max-min) + min
// Generate random day within the past two years
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
// Add insert statement to stmts slice
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
}
}
if !playerRecordsFound {
fmt.Fprintln(w, "No player records currently exist. First insert players then insert scores.")
} else {
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Commit insert statements for all scores to be inserted as a single transaction
_, err := txn.BatchUpdate(ctx, stmts)
return err
})
if err != nil {
return err
}
fmt.Fprintln(w, "Inserted scores")
}
return nil
}
بعد ذلك، لتفعيل الأمر insert
، أضِف الرمز التالي إلى قسم "تشغيل" التطبيق في تطبيقك. أسفل عبارة المعالجة createdatabase
، لتحل محل عبارة return nil
:
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
بعد الانتهاء، من المفترض أن تظهر الدالة run
على النحو التالي:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
الخطوة الأخيرة لإكمال عملية إضافة "insert" إلى إضافة نص مساعدة لـ "insertplayers" و"insertscores" إلى الدالة flag.Usage()
. أضِف نص المساعدة التالي إلى الدالة flag.Usage()
لتضمين نص مساعدة لأوامر الإدراج:
أضِف الأمرَين إلى قائمة الأوامر المحتملة:
Command can be one of: createdatabase, insertplayers, insertscores
وأضِف نص المساعدة الإضافي هذا أسفل نص المساعدة للأمر createdatabase
.
leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
- Insert 100 sample Player records into the database.
leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
- Insert sample score data into Scores sample Cloud Spanner database table.
احفظ التغييرات التي أجريتها على ملف leaderboard.go
من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.
يمكنك استخدام ملف leaderboard.go
في دليل golang-samples/spanner/spanner_leaderboard
للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف leaderboard.go
بعد إضافة الرمز لتفعيل الأمرَين insertplayers
وinsertscores
.
والآن، لننشئ التطبيق ونشغِّله للتأكّد من تضمين الأمرَين الجديدَين insertplayers
وinsertscores
في قائمة الأوامر المحتملة في التطبيق. شغِّل الأمر التالي لإنشاء التطبيق:
go build leaderboard.go
شغّل التطبيق الناتج في Cloud Shell من خلال إدخال الأمر التالي:
./leaderboard
من المفترض أن يظهر لك الأمران insertplayers
وinsertscores
مضمَّنان الآن في الناتج التلقائي للتطبيق:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase, insertplayers, insertscores Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project. leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db - Insert 100 sample Player records into the database. leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db - Insert sample score data into Scores sample Cloud Spanner database table.
لنقم الآن بتشغيل الأمر insertplayers
باستخدام قيم الوسيطة نفسها التي استخدمناها عند استدعينا الأمر createdatabase
. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:
Inserted players
لنستخدم الآن مكتبة عملاء Go لملء جدول Scores
بأربع نتائج عشوائية بالإضافة إلى طوابع زمنية لكل لاعب في جدول Players
.
تم تعريف عمود Timestamp
في جدول Scores
على أنّه "طابع زمني للالتزام". عبر عبارة SQL التالية التي تم تنفيذها عند تشغيل الأمر create
سابقًا:
CREATE TABLE Scores(
PlayerId INT64 NOT NULL,
Score INT64 NOT NULL,
Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
INTERLEAVE IN PARENT Players ON DELETE NO ACTION
لاحِظ السمة OPTIONS(allow_commit_timestamp=true)
. يجعل هذا الإجراء Timestamp
"طابعًا زمنيًا للالتزام". وتمكين ملؤه تلقائيًا بالطابع الزمني الدقيق للمعاملة لعمليات INSERT وUPDATE في صف جدول معين.
يمكنك أيضًا إدراج قيم الطوابع الزمنية الخاصة بك في "الطابع الزمني للالتزام". طالما يتم إدراج طابع زمني يتضمّن قيمة في الماضي، وهذا ما سنفعله في هذا الدرس التطبيقي حول الترميز.
لنقم الآن بتشغيل الأمر insertscores
باستخدام قيم الوسيطة نفسها التي استخدمناها عند استدعينا الأمر insertplayers
. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:
Inserted scores
يؤدي تشغيل الدالة insertScores
إلى استخدام مقتطف الرمز التالي لإدراج طابع زمني تم إنشاؤه عشوائيًا مع تاريخ ووقت يقع في الماضي:
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
لتعبئة عمود Timestamp
تلقائيًا بالطابع الزمني عند ضبط الزر "إدراج" بالضبط تحدث العملية، يمكنك بدلاً من ذلك إدراج spanner.CommitTimestamp
ثابتة كما هو الحال في مقتطف الرمز التالي:
...
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": spanner.CommitTimestamp,
},
})
الآن وبعد أن أكملنا عملية تحميل البيانات، علينا التحقّق من القيم التي كتبناها للتو إلى جداولنا الجديدة في قسم Cloud Spanner من Cloud Console. اختَر أولاً قاعدة بيانات leaderboard
ثم اختَر الجدول Players
. انقر على علامة التبويب Data
. من المفترض أن يظهر لك أنّ لديك بيانات في العمودَين PlayerId
وPlayerName
في الجدول.
بعد ذلك، لنتحقق من أن جدول "النتائج" يحتوي أيضًا على بيانات من خلال النقر على جدول Scores
واختيار علامة التبويب Data
. من المفترض أن يظهر لك أنّ لديك بيانات في الأعمدة PlayerId
وTimestamp
وScore
في الجدول.
أحسنت! لنعدّل تطبيقنا لتشغيل بعض طلبات البحث التي يمكننا استخدامها لإنشاء قائمة الصدارة للألعاب.
6- تنفيذ طلبات بحث في قائمة الصدارة
الآن بعد أن أعددت قاعدة البيانات وتحميل المعلومات إلى جداولنا، لنقم بإنشاء قائمة صدارة باستخدام هذه البيانات. ولتنفيذ ذلك، يجب الإجابة عن الأسئلة الأربعة التالية:
- مَن هم اللاعبين ضمن "أفضل عشرة" من جميع الأوقات؟
- مَن هم اللاعبين ضمن "أفضل عشرة" من العام؟
- مَن هم اللاعبين ضمن "أفضل عشرة" من الشهر؟
- مَن هم اللاعبين ضمن "أفضل عشرة" من الأسبوع؟
لنقم بتحديث تطبيقنا لتشغيل استعلامات SQL (لغة الاستعلام البنيوية) التي سوف تجيب على هذه الأسئلة.
سنضيف الأمر query
والأمر queryWithTimespan
الذي يوفر طريقة لتشغيل الاستعلامات للإجابة عن الأسئلة، وسينتج عن ذلك المعلومات المطلوبة لقائمة الصدارة.
يمكنك تعديل ملف leaderboard.go
في "محرِّر Cloud Shell" لتحديث التطبيق من خلال إضافة الأمر query
والأمر queryWithTimespan
. سنضيف أيضًا دالة مساعدة formatWithCommas
لتنسيق نتائجنا باستخدام الفواصل.
حدِّث أولاً القسم imports
أعلى الملف leaderboard.go
، مع استبدال العناصر الموجودة حاليًا، بحيث يظهر على النحو التالي بعد الانتهاء:
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"strconv"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
أضِف بعد ذلك الدالتَين التاليتَين والدالة المساعدة أسفل طريقة insertScores
الحالية:
func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
stmt := spanner.Statement{
SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
FROM Players p
JOIN Scores s ON p.PlayerId = s.PlayerId
ORDER BY s.Score DESC LIMIT 10`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
for {
row, err := iter.Next()
if err == iterator.Done {
return nil
}
if err != nil {
return err
}
var playerID, score int64
var playerName string
var timestamp time.Time
if err := row.Columns(&playerID, &playerName, &score, ×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()
}
أضِف "طلب بحث" في أعلى الملف leaderboard.go
. كخيار أمر واحد في المتغير commands
، أسفل الخيار "insertscores": insertScores
" مباشرةً بحيث يظهر المتغير commands
على النحو التالي:
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
أضِف بعد ذلك "queryWithTimespan" كخيار أمر داخل الدالة run
، أسفل "createdatabase" وفوق "إدراج واستعلام" قسم معالجة الأوامر:
// querywithtimespan command
if cmd == "querywithtimespan" {
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
بعد الانتهاء، من المفترض أن تظهر الدالة run
على النحو التالي:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// querywithtimespan command
if cmd == "querywithtimespan" {
if timespan == 0 {
flag.Usage()
os.Exit(2)
}
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
بعد ذلك، لتفعيل الأمر queryWithTimespan
، عليك تعديل كتلة الرموز التجريبية flag.Parse() في "الرئيسي" لتطبيقك. بحيث يبدو كما يلي:
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)
}
الخطوة الأخيرة لإكمال إضافة "طلب البحث" بشكل أفضل إلى تطبيقك، هي إضافة نص مساعدة
لطلب البحث و"querywithtimespan" إلى الدالة flag.Usage()
. أضِف سطور الرمز التالية إلى الدالة flag.Usage()
لتضمين نص مساعدة لأوامر طلب البحث:
أضف اثنين من "استعلامات البحث" إلى قائمة الأوامر المحتملة:
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
وأضِف نص المساعدة الإضافي هذا أسفل نص المساعدة للأمر insertscores
.
leaderboard query projects/my-project/instances/my-instance/databases/example-db
- Query players with top ten scores of all time.
leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
- Query players with top ten scores within a timespan specified in hours.
احفظ التغييرات التي أجريتها على ملف leaderboard.go
من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.
يمكنك استخدام ملف leaderboard.go
في دليل golang-samples/spanner/spanner_leaderboard
للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف leaderboard.go
بعد إضافة الرمز لتفعيل الأمرَين query
وquerywithtimespan
.
والآن، لننشئ التطبيق ونشغِّله للتأكّد من تضمين الأمرَين الجديدَين query
وquerywithtimespan
في قائمة الأوامر المحتملة في التطبيق.
شغّل الأمر التالي في Cloud Shell لإنشاء التطبيق:
go build leaderboard.go
شغّل التطبيق الناتج في Cloud Shell من خلال إدخال الأمر التالي:
./leaderboard
من المفترَض أن يظهر لك الأمران query
وquerywithtimespan
المضمّنان الآن في الناتج التلقائي للتطبيق كخيار أمر جديد:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project. leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db - Insert 100 sample Player records into the database. leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db - Insert sample score data into Scores sample Cloud Spanner database table. leaderboard query projects/my-project/instances/my-instance/databases/example-db - Query players with top ten scores of all time. leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168 - Query players with top ten scores within a timespan specified in hours.
يمكنك أن ترى من الإجابة أنه يمكننا استخدام الأمر query
للحصول على قائمة بـ "أهم عشرة" لاعبين في جميع الأوقات. يتضح لنا أيضًا أنّ الأمر querywithtimespan
يتيح لنا تحديد فترة زمنية بعدد الساعات لاستخدامها في فلترة السجلّات استنادًا إلى قيمتها في العمود Timestamp
ضمن جدول Scores
.
لننفِّذ الأمر query
باستخدام قيم الوسيطات نفسها التي استخدمناها عند تشغيل الأمر create
. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard query projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبين من جميع العصور مثل ما يلي:
PlayerId: 4018687297 PlayerName: Player 83 Score: 999,618 Timestamp: 2017-07-01
PlayerId: 4018687297 PlayerName: Player 83 Score: 998,956 Timestamp: 2017-09-02
PlayerId: 4285713246 PlayerName: Player 51 Score: 998,648 Timestamp: 2017-12-01
PlayerId: 5267931774 PlayerName: Player 49 Score: 997,733 Timestamp: 2017-11-09
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 2456736905 PlayerName: Player 84 Score: 992,881 Timestamp: 2017-04-14
PlayerId: 8234617611 PlayerName: Player 19 Score: 992,399 Timestamp: 2017-12-27
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 7127686505 PlayerName: Player 97 Score: 992,038 Timestamp: 2017-12-02
لنُشغِّل الآن الأمر querywithtimespan
مع الوسيطات اللازمة للاستعلام عن "أهم عشرة". أفضل لاعب في العام من خلال تحديد "فترة زمنية" يساوي عدد الساعات في السنة وهو 8760. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760
من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" أفضل لاعبي العام كما يلي:
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 6862349579 PlayerName: Player 30 Score: 990,877 Timestamp: 2018-09-14
PlayerId: 5529627211 PlayerName: Player 16 Score: 989,142 Timestamp: 2018-03-30
PlayerId: 9743904155 PlayerName: Player 1 Score: 988,765 Timestamp: 2018-05-30
PlayerId: 6809119884 PlayerName: Player 7 Score: 986,673 Timestamp: 2018-05-16
PlayerId: 2132710638 PlayerName: Player 54 Score: 983,108 Timestamp: 2018-09-11
PlayerId: 2320093590 PlayerName: Player 79 Score: 981,373 Timestamp: 2018-05-07
PlayerId: 9554181430 PlayerName: Player 80 Score: 981,087 Timestamp: 2018-06-21
لنقم الآن بتشغيل الأمر querywithtimespan
للاستعلام عن "أعلى عشرة" لاعب الشهر من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الشهر وهو 730. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730
من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" اللاعبين الأفضل في الشهر كما يلي:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 7448359883 PlayerName: Player 20 Score: 938,998 Timestamp: 2019-02-07
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 9336678658 PlayerName: Player 44 Score: 914,106 Timestamp: 2019-01-27
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 896,433 Timestamp: 2019-01-29
PlayerId: 9395039625 PlayerName: Player 59 Score: 879,495 Timestamp: 2019-02-09
PlayerId: 2094604854 PlayerName: Player 39 Score: 860,434 Timestamp: 2019-02-01
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 4285713246 PlayerName: Player 51 Score: 805,654 Timestamp: 2019-02-02
لنقم الآن بتشغيل الأمر querywithtimespan
للاستعلام عن "أعلى عشرة" لاعبو الأسبوع من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الأسبوع وهو 168. تأكَّد من استبدال my-project
برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168
من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبي الأسبوع مثل ما يلي:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 5954045812 PlayerName: Player 8 Score: 795,639 Timestamp: 2019-02-22
PlayerId: 3889939638 PlayerName: Player 71 Score: 775,252 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 604,695 Timestamp: 2019-02-19
PlayerId: 9006728426 PlayerName: Player 3 Score: 457,208 Timestamp: 2019-02-22
PlayerId: 8289497066 PlayerName: Player 58 Score: 227,697 Timestamp: 2019-02-20
PlayerId: 8065482904 PlayerName: Player 99 Score: 198,429 Timestamp: 2019-02-24
ممتاز!
والآن بعد إضافة السجلات، ستتمكن خدمة Cloud Spanner من تغيير حجم قاعدة البيانات إلى الحجم الذي تحتاج إليه. بغض النظر عن حجم قاعدة بياناتك، يمكن أن يستمر تغيير قائمة الصدارة في لعبتك بدقة باستخدام Cloud Spanner وتقنية Truetime الخاصة بها.
7. تنظيف
بعد كل متعة اللعب مع Spanner، علينا تنظيف ساحة اللعب وتوفير الموارد والمال الثمينة. لحسن الحظّ، هذه خطوة سهلة، ما عليك سوى الانتقال إلى قسم Cloud Spanner على Cloud Console وحذف المثيل الذي أنشأناه في خطوة الدرس التطبيقي حول الترميز المسماة "إعداد مثيل Cloud Spanner".
8. تهانينا!
المواضيع التي تناولناها:
- مثيلات Google Cloud Spanner وقواعد البيانات ومخطط الجدول لقائمة الصدارة
- طريقة إنشاء تطبيق Go console
- كيفية إنشاء قاعدة بيانات Spanner وTables باستخدام مكتبة عملاء Go
- كيفية تحميل البيانات إلى قاعدة بيانات Spanner باستخدام مكتبة عميل Go
- كيفية الاستعلام عن "أعلى عشرة" نتائج من بياناتك باستخدام الطوابع الزمنية للتنفيذ في Spanner ومكتبة برامج Go
الخطوات التالية:
- الاطّلاع على التقرير الموجز حول Spanner CAP
- تعرَّف على أفضل ممارسات طلب البحث وتصميم المخطط.
- مزيد من المعلومات حول الطوابع الزمنية للالتزام في Cloud Spanner
يُرجى إرسال ملاحظاتك إلينا
- يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.