1. نظرة عامة
Google Cloud Spanner هي خدمة قواعد بيانات ارتباطية مُدارة بالكامل وقابلة للتوسّع أفقيًا وموزّعة على مستوى العالم، وتوفّر معاملات متوافقة مع ACID ودلالات SQL بدون التنازل عن الأداء العالي ومدى التوفّر العالي.
في هذا الدرس التطبيقي، ستتعرّف على كيفية إعداد مثيل Cloud Spanner. ستتّبع خطوات إنشاء قاعدة بيانات ومخطط يمكن استخدامهما في قائمة الصدارة الخاصة بالألعاب. ستبدأ بإنشاء جدول "اللاعبون" لتخزين معلومات اللاعبين وجدول "النتائج" لتخزين نتائج اللاعبين.
بعد ذلك، ستملأ الجداول ببيانات نموذجية. بعد ذلك، ستنهي الدرس التطبيقي من خلال تنفيذ بعض نماذج طلبات البحث العشرة الأوائل، ثم حذف الجهاز الظاهري لإتاحة الموارد.
ما ستتعلمه
- كيفية إعداد مثيل Cloud Spanner
- كيفية إنشاء قاعدة بيانات وجداول
- كيفية استخدام عمود الطابع الزمني لعملية الإيداع
- كيفية تحميل البيانات إلى جدول قاعدة بيانات Cloud Spanner باستخدام الطوابع الزمنية
- كيفية طلب البحث في قاعدة بيانات Cloud Spanner
- كيفية حذف مثيل Cloud Spanner
W****المتطلبات
كيف ستستخدم هذا البرنامج التعليمي؟
ما هو تقييمك لتجربة استخدام 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، وهي بيئة سطر أوامر تعمل في السحابة الإلكترونية.
يتم تحميل هذا الجهاز الافتراضي المستند إلى Debian بجميع أدوات التطوير التي تحتاج إليها. توفّر هذه الخدمة دليلًا رئيسيًا دائمًا بسعة 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 لإنشاء جدولَين: جدول Players لمعلومات اللاعبين وجدول Scores لتخزين نتائج اللاعبين. لإجراء ذلك، سنشرح خطوات إنشاء تطبيق وحدة تحكّم Go في 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 ليكون مرجعًا لك أثناء تقدّمك في الدرس التطبيقي حول الترميز. سننشئ دليلاً جديدًا وننشئ نسخة من تطبيق "قائمة الصدارة" على مراحل.
أنشئ دليلاً جديدًا باسم "codelab" للتطبيق وانتقِل إلى الدليل باستخدام الأمر التالي:
mkdir codelab && cd $_
لننشئ الآن تطبيق Go أساسيًا باسم "Leaderboard" يستخدم مكتبة برامج Spanner لإنشاء قائمة صدارة تتألف من جدولَين: "اللاعبون" و"النتائج". يمكنك إجراء ذلك مباشرةً في "محرِّر Cloud Shell" باتّباع الخطوات التالية:
افتح "محرّر Cloud Shell" من خلال النقر على رمز "فتح المحرّر" المميّز أدناه:

أنشئ ملفًا باسم "leaderboard.go" في المجلد ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.
- تأكَّد أولاً من اختيار مجلد "codelab " في قائمة المجلدات في"محرّر Cloud Shell".
- بعد ذلك، انقر على "ملف جديد" (New File) ضمن قائمة "ملف" (File) في "محرّر Cloud Shell" (Cloud Shell Editor).
- أدخِل "leaderboard.go" كاسم للملف الجديد.
هذا هو الملف الرئيسي للتطبيق الذي سيحتوي على رمز التطبيق ومراجع لتضمين أي تبعيات.
لإنشاء قاعدة بيانات leaderboard وجدولَي Players وScores، انسخ (Ctrl + P) رمز Go التالي والصقه (Ctrl + V) في الملف 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 من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر Cloud Shell".
يمكنك استخدام الملف leaderboard.go في الدليل golang-samples/spanner/spanner_leaderboard للاطّلاع على مثال حول الشكل الذي يجب أن يبدو عليه الملف leaderboard.go بعد إضافة الرمز لتفعيل الأمر createdatabase.
لإنشاء تطبيقك في Cloud Shell، شغِّل الأمر "go build" من الدليل codelab الذي يوجد فيه ملف leaderboard.go:
go build leaderboard.go
بعد إنشاء تطبيقك بنجاح، شغِّل التطبيق الناتج في Cloud Shell عن طريق إدخال الأمر التالي:
./leaderboard
من المفترض أن تظهر لك نتيجة مثل ما يلي:
Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
من هذه الاستجابة، يمكننا أن نرى أنّ هذا هو تطبيق Leaderboard الذي يتضمّن حاليًا أمرًا واحدًا محتملاً: createdatabase. يمكننا أن نرى أنّ الوسيطة المتوقّعة للأمر createdatabase هي سلسلة تحتوي على معرّف مثيل ومعرّف قاعدة بيانات محدّدين.
الآن، شغِّل الأمر التالي. تأكَّد من استبدال my-project برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس العملي.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
بعد بضع ثوانٍ، من المفترض أن يظهر لك ردّ على النحو التالي:
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
في قسم "نظرة عامة على قواعد بيانات Cloud Spanner" في Cloud Console، من المفترض أن تظهر قاعدة البيانات والجداول الجديدة في القائمة على الجانب الأيمن.

في الخطوة التالية، سنعدّل تطبيقنا لتحميل بعض البيانات إلى قاعدة البيانات الجديدة.
5- تحميل البيانات
لدينا الآن قاعدة بيانات باسم leaderboard تحتوي على جدولَين هما Players وScores. لنستخدِم الآن مكتبة برامج Go لملء الجدول Players باللاعبين والجدول Scores بالنتائج العشوائية لكل لاعب.
إذا لم يكن مفتوحًا من قبل، افتح "محرِّر Cloud Shell" من خلال النقر على الرمز المميّز أدناه:

بعد ذلك، عدِّل ملف leaderboard.go في Cloud Shell Editor لإضافة أمر insertplayers يمكن استخدامه لإدراج 100 لاعب في جدول Players. سنضيف أيضًا الأمر insertscores الذي يمكن استخدامه لإدراج 4 نتائج عشوائية في الجدول Scores لكل لاعب في الجدول Players.
عدِّل أولاً القسم imports في أعلى ملف leaderboard.go، واستبدِل المحتوى الحالي بما يلي:
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
بعد ذلك، أضِف نوع أمر جديدًا مع قائمة بالأوامر في أعلى الملف، أسفل السطر الذي يبدأ بـ "type adminCommand ..." مباشرةً، بحيث يبدو الملف بعد الانتهاء على النحو التالي:
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
type command func(ctx context.Context, w io.Writer, client *spanner.Client) error
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
}
)
بعد ذلك، أضِف الدالتَين insertPlayers وinsertScores أدناه إلى الدالة createdatabase() الحالية:
func insertPlayers(ctx context.Context, w io.Writer, client *spanner.Client) error {
// Get number of players to use as an incrementing value for each PlayerName to be inserted
stmt := spanner.Statement{
SQL: `SELECT Count(PlayerId) as PlayerCount FROM Players`,
}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
row, err := iter.Next()
if err != nil {
return err
}
var numberOfPlayers int64 = 0
if err := row.Columns(&numberOfPlayers); err != nil {
return err
}
// Initialize values for random PlayerId
rand.Seed(time.Now().UnixNano())
min := 1000000000
max := 9000000000
// Insert 100 player records into the Players table
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
stmts := []spanner.Statement{}
for i := 1; i <= 100; i++ {
numberOfPlayers++
playerID := rand.Intn(max-min) + min
playerName := fmt.Sprintf("Player %d", numberOfPlayers)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Players
(PlayerId, PlayerName)
VALUES (@playerID, @playerName)`,
Params: map[string]interface{}{
"playerID": playerID,
"playerName": playerName,
},
})
}
_, err := txn.BatchUpdate(ctx, stmts)
if err != nil {
return err
}
return nil
})
fmt.Fprintf(w, "Inserted players \n")
return nil
}
func insertScores(ctx context.Context, w io.Writer, client *spanner.Client) error {
playerRecordsFound := false
// Create slice for insert statements
stmts := []spanner.Statement{}
// Select all player records
stmt := spanner.Statement{SQL: `SELECT PlayerId FROM Players`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
// Insert 4 score records into the Scores table for each player in the Players table
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return err
}
playerRecordsFound = true
var playerID int64
if err := row.ColumnByName("PlayerId", &playerID); err != nil {
return err
}
// Initialize values for random score and date
rand.Seed(time.Now().UnixNano())
min := 1000
max := 1000000
for i := 0; i < 4; i++ {
// Generate random score between 1,000 and 1,000,000
score := rand.Intn(max-min) + min
// Generate random day within the past two years
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
// Add insert statement to stmts slice
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
}
}
if !playerRecordsFound {
fmt.Fprintln(w, "No player records currently exist. First insert players then insert scores.")
} else {
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Commit insert statements for all scores to be inserted as a single transaction
_, err := txn.BatchUpdate(ctx, stmts)
return err
})
if err != nil {
return err
}
fmt.Fprintln(w, "Inserted scores")
}
return nil
}
بعد ذلك، لجعل الأمر insert يعمل، أضِف الرمز التالي إلى دالة "التشغيل" في تطبيقك أسفل عبارة معالجة 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
}
الخطوة الأخيرة لإكمال إضافة وظيفة "الإدراج" إلى تطبيقك هي إضافة نص مساعدة للأمرين "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 من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر 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 في صف جدول معيّن.
يمكنك أيضًا إدراج قيم الطابع الزمني الخاصة بك في عمود "الطابع الزمني للتثبيت" (commit timestamp) طالما أنّك تُدرج طابعًا زمنيًا بقيمة في الماضي، وهو ما سنفعله لأغراض هذا الدرس البرمجي.
لننفّذ الآن الأمر 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 في Go كما في مقتطف الرمز التالي:
...
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": spanner.CommitTimestamp,
},
})
بعد اكتمال تحميل البيانات، لننتقل إلى التحقّق من القيم التي كتبناها للتو في جداولنا الجديدة في قسم Cloud Spanner في Cloud Console. اختَر أولاً قاعدة بيانات leaderboard ثم اختَر جدول Players. انقر على علامة التبويب Data. يجب أن تظهر لك بيانات في العمودَين PlayerId وPlayerName في الجدول.

بعد ذلك، لننتحقق من أنّ جدول "النتائج" يتضمّن أيضًا بيانات من خلال النقر على الجدول 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، أضِف "query" كأحد خيارات الأوامر في المتغيّر commands، أسفل الخيار "insertscores": insertScores مباشرةً، ليصبح المتغيّر commands على النحو التالي:
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
بعد ذلك، أضِف "queryWithTimespan" كخيار أمر ضمن الدالة run، أسفل قسم الأمر "createdatabase" وفوق قسم معالجة الأوامر "insert and query":
// querywithtimespan command
if cmd == "querywithtimespan" {
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
بعد الانتهاء، من المفترض أن تبدو الدالة run على النحو التالي:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// querywithtimespan command
if cmd == "querywithtimespan" {
if timespan == 0 {
flag.Usage()
os.Exit(2)
}
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
بعد ذلك، لجعل الأمر queryWithTimespan يعمل، عدِّل مجموعة الرموز flag.Parse() في طريقة "main" لتطبيقك لتصبح على النحو التالي:
flag.Parse()
flagCount := len(flag.Args())
if flagCount < 2 || flagCount > 3 {
flag.Usage()
os.Exit(2)
}
cmd, db := flag.Arg(0), flag.Arg(1)
// If query timespan flag is specified, parse to int
var timespan int = 0
if flagCount == 3 {
parsedTimespan, err := strconv.Atoi(flag.Arg(2))
if err != nil {
fmt.Println(err)
os.Exit(2)
}
timespan = parsedTimespan
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
adminClient, dataClient := createClients(ctx, db)
if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
os.Exit(1)
}
الخطوة الأخيرة لإكمال إضافة وظيفة "طلب البحث" إلى تطبيقك هي إضافة نص مساعدة للأمرين "طلب البحث" و "طلب البحث مع نطاق زمني" إلى الدالة flag.Usage(). أضِف أسطر الرمز التالية إلى الدالة flag.Usage() لتضمين نص المساعدة لأوامر طلب البحث:
أضِف الأمرَين "query" إلى قائمة الأوامر المحتملة:
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
وأضِف نص المساعدة الإضافي هذا أسفل نص المساعدة الخاص بالأمر insertscores.
leaderboard query projects/my-project/instances/my-instance/databases/example-db
- Query players with top ten scores of all time.
leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
- Query players with top ten scores within a timespan specified in hours.
احفظ التغييرات التي أجريتها على ملف leaderboard.go من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر 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 وحذف المثيل الذي أنشأناه في خطوة codelab المسماة "إعداد مثيل Cloud Spanner".
8. تهانينا!
المواضيع التي تناولناها:
- مثيلات وقواعد بيانات ومخطط جداول Google Cloud Spanner للوحات الصدارة
- كيفية إنشاء تطبيق وحدة تحكّم Go
- كيفية إنشاء قاعدة بيانات وجداول Spanner باستخدام مكتبة برامج Go للعملاء
- كيفية تحميل البيانات إلى قاعدة بيانات Spanner باستخدام مكتبة برامج Go
- كيفية طلب نتائج "أفضل 10" من بياناتك باستخدام الطوابع الزمنية لعمليات الإكمال في Spanner ومكتبة برامج Go
الخطوات التالية:
- الاطّلاع على التقرير الموجز حول إمكانات Spanner
- مزيد من المعلومات عن تصميم المخطط وأفضل الممارسات المتعلّقة بالطلبات
- مزيد من المعلومات عن الطوابع الزمنية لعمليات الإيداع في Cloud Spanner
تقديم ملاحظاتك
- يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.