Cloud Spanner का गेम डेवलपमेंट शुरू करना

1. परिचय

Cloud Spanner पूरी तरह से मैनेज की गई, हॉरिज़ॉन्टल तौर पर बढ़ाने लायक, दुनिया भर में डिस्ट्रिब्यूट की जाने वाली, रिलेशनल डेटाबेस सेवा है. यह परफ़ॉर्मेंस और ज़्यादा उपलब्धता को छोड़े बिना, ACID लेन-देन और SQL सिमैंटिक उपलब्ध कराती है.

इन सुविधाओं की वजह से, Spanner को उन गेम के फ़ॉर्मैट में बेहतर बनाया जा सकता है जिनमें दुनिया भर के खिलाड़ियों को शामिल करना है या जो डेटा के एक जैसे होने को लेकर परेशान हैं

इस लैब में, Go की दो सेवाएं बनाएं, जो किसी रीजनल स्पैनर डेटाबेस के साथ इंटरैक्ट करती हैं. इससे खिलाड़ियों को साइन अप करके गेम खेलने में मदद मिलती है.

413fdd57bb0b68bc.png

इसके बाद, साइन अप करने और गेम खेलने वाले खिलाड़ियों की नकल करने के लिए, Python लोड फ़्रेमवर्क Locust.io का इस्तेमाल करके डेटा जनरेट करें. इसके बाद, आपको Spanner से क्वेरी करनी होगी. इससे यह पता चलेगा कि कितने खिलाड़ी खेल रहे हैं. साथ ही, आपको खिलाड़ियों से जुड़े कुछ आंकड़े भी मिलेंगे जीते गए गेम बनाम खेले गए गेम.

आखिर में, इस लैब में बनाए गए संसाधनों को मिटाएं.

आपको क्या बनाना होगा

इस लैब का हिस्सा होने के नाते, आपको:

  • स्पैनर इंस्टेंस बनाना
  • खिलाड़ी साइनअप मैनेज करने के लिए, 'जाएं' में लिखी गई प्रोफ़ाइल सेवा को डिप्लॉय करें
  • खिलाड़ियों को गेम असाइन करने, विजेता तय करने, और खिलाड़ियों की जानकारी अपडेट करने के लिए, 'जाएं' पर लिखा गया मैचमेकिंग सर्विस डिप्लॉय करें गेम के आंकड़े.

आपको क्या सीखने को मिलेगा

  • Cloud Spanner इंस्टेंस सेट अप करने का तरीका
  • गेम डेटाबेस और स्कीमा बनाने का तरीका
  • Cloud Spanner के साथ काम करने के लिए Go ऐप्लिकेशन डिप्लॉय करने का तरीका
  • Locust का इस्तेमाल करके डेटा जनरेट करने का तरीका
  • गेम और खिलाड़ियों से जुड़े सवालों के जवाब देने के लिए, Cloud Spanner में डेटा के बारे में क्वेरी करने का तरीका.

आपको किन चीज़ों की ज़रूरत होगी

  • यह ऐसा Google Cloud प्रोजेक्ट है जो किसी बिलिंग खाते से जुड़ा होता है.
  • कोई वेब ब्राउज़र, जैसे कि Chrome या Firefox.

2. सेटअप और ज़रूरी शर्तें

प्रोजेक्ट बनाना

अगर आपके पास पहले से Google खाता (Gmail या Google Apps) नहीं है, तो आपको एक खाता बनाना होगा. Google Cloud Platform कंसोल ( console.cloud.google.com) में साइन इन करें और एक नया प्रोजेक्ट बनाएं.

अगर आपके पास पहले से कोई प्रोजेक्ट है, तो कंसोल के ऊपर बाईं ओर मौजूद, प्रोजेक्ट चुनने के लिए पुल डाउन मेन्यू पर क्लिक करें:

6c9406d9b014760.png

और 'नया प्रोजेक्ट' पर क्लिक करें बटन:

949d83c8a4ee17d9.png

अगर आपके पास पहले से कोई प्रोजेक्ट नहीं है, तो आपको अपना पहला प्रोजेक्ट बनाने के लिए, इस तरह का डायलॉग बॉक्स दिखेगा:

870a3cbd6541ee86.png

इसके बाद, प्रोजेक्ट बनाने वाले डायलॉग बॉक्स की मदद से अपने नए प्रोजेक्ट की जानकारी डाली जा सकती है:

6a92c57d3250a4b3.png

वह प्रोजेक्ट आईडी याद रखें जो Google Cloud के सभी प्रोजेक्ट के लिए एक यूनीक नाम होता है. ऊपर दिया गया नाम पहले ही किसी दूसरे प्रोजेक्ट के लिए इस्तेमाल किया जा चुका है. इसलिए, यह आपके लिए काम नहीं करेगा! बाद में, इसे इस कोडलैब में PROJECT_ID के तौर पर दिखाया जाएगा.

इसके बाद, अगर आपने पहले से ऐसा नहीं किया है, तो आपको Google Cloud के संसाधनों का इस्तेमाल करने के लिए, Developers Console में बिलिंग चालू करनी होगी. साथ ही, Cloud Spanner API को चालू करना होगा.

15d0ef27a8fबाबा27.png

इस कोडलैब को आज़माने के लिए आपको कुछ डॉलर से ज़्यादा खर्च नहीं करना चाहिए. हालांकि, अगर आप ज़्यादा संसाधनों का इस्तेमाल करने का फ़ैसला करते हैं या उन्हें बंद कर देते हैं, तो यह ज़्यादा हो सकता है (इस दस्तावेज़ के आखिर में "क्लीनअप" सेक्शन देखें). Google Cloud Spanner की कीमत की जानकारी यहां दी गई है.

Google Cloud Platform के नए उपयोगकर्ता 300 डॉलर के मुफ़्त में आज़माने की ज़रूरी शर्तें पूरी करते हैं. इसके तहत, कोडलैब का यह वर्शन बिना किसी शुल्क के इस्तेमाल किया जा सकता है.

Google Cloud Shell का सेटअप

Google Cloud और Spanner को आपके लैपटॉप से कहीं से भी ऑपरेट किया जा सकता है. हालांकि, इस कोडलैब में हम Google Cloud Shell का इस्तेमाल करेंगे. यह क्लाउड में चलने वाला कमांड लाइन एनवायरमेंट है.

Debian आधारित इस वर्चुअल मशीन में ऐसे सभी डेवलपमेंट टूल मौजूद हैं जिनकी आपको ज़रूरत पड़ेगी. यह पांच जीबी की स्थायी होम डायरेक्ट्री उपलब्ध कराता है और Google Cloud में चलता है. इससे नेटवर्क की परफ़ॉर्मेंस और पुष्टि करने की प्रक्रिया को बेहतर बनाने में मदद मिलती है. इसका मतलब है कि इस कोडलैब के लिए आपको सिर्फ़ एक ब्राउज़र की ज़रूरत होगी. हां, यह Chromebook पर काम करता है.

  1. Cloud Console से Cloud Shell को चालू करने के लिए, Cloud Shell को चालू करें gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A पर क्लिक करें. प्रावधान करने और एनवायरमेंट से कनेक्ट होने में कुछ ही समय लगेगा.

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

14-06-2017 को 10.13.43 PM.png पर स्क्रीन शॉट लिया गया

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 के डैशबोर्ड में देखें:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell, डिफ़ॉल्ट रूप से कुछ एनवायरमेंट वैरिएबल सेट करता है. ये वैरिएबल, आने वाले समय में कमांड चलाने के दौरान काम आ सकते हैं.

echo $GOOGLE_CLOUD_PROJECT

कमांड आउटपुट

<PROJECT_ID>

कोड डाउनलोड करें

Cloud Shell में, इस लैब का कोड डाउनलोड किया जा सकता है. यह v0.1.0 रिलीज़ पर आधारित है, इसलिए वह टैग देखें:

git clone https://github.com/cloudspannerecosystem/spanner-gaming-sample.git
cd spanner-gaming-sample/

# Check out v0.1.0 release
git checkout tags/v0.1.0 -b v0.1.0-branch

कमांड आउटपुट

Cloning into 'spanner-gaming-sample'...
*snip*
Switched to a new branch 'v0.1.0-branch'

लोकस्ट लोड जनरेटर सेटअप करें

लोकस्ट एक Python लोड टेस्टिंग फ़्रेमवर्क है, जिसकी मदद से REST API एंडपॉइंट की जांच की जा सकती है. इस कोडलैब में, 'जनरेटर' में लोड की दो अलग-अलग जांच होती हैं इस निर्देशिका को हाइलाइट करेंगे:

  • authentication_server.py: इसमें प्लेयर बनाने और सिंगल पॉइंट लुकअप की नकल करने के लिए, रैंडम प्लेयर पाने के टास्क शामिल होते हैं.
  • match_server.py: इसमें गेम बनाने और गेम बंद करने के टास्क शामिल हैं. गेम बनाने से 100 रैंडम प्लेयर असाइन किए जाएंगे, जो इस समय गेम नहीं खेल रहे हैं. गेम बंद करने पर, game_played और game_Wonders के आंकड़े अपडेट हो जाएंगे. साथ ही, उन खिलाड़ियों को आने वाले समय के किसी गेम में शामिल होने का मौका मिलेगा.

Cloud Shell में लोकस्ट को चलाने के लिए, आपको Python 3.7 या इसके बाद के वर्शन की ज़रूरत होगी. Cloud Shell में Python 3.9 का इस्तेमाल किया जा सकता है. इसलिए, सिर्फ़ इस वर्शन की पुष्टि करने की ज़रूरत है:

python -V

कमांड आउटपुट

Python 3.9.12

अब, लोक्ट के लिए ज़रूरी शर्तें इंस्टॉल की जा सकती हैं.

pip3 install -r requirements.txt

कमांड आउटपुट

Collecting locust==2.11.1
*snip*
Successfully installed ConfigArgParse-1.5.3 Flask-BasicAuth-0.2.0 Flask-Cors-3.0.10 brotli-1.0.9 gevent-21.12.0 geventhttpclient-2.0.2 greenlet-1.1.3 locust-2.11.1 msgpack-1.0.4 psutil-5.9.2 pyzmq-22.3.0 roundrobin-0.0.4 zope.event-4.5.0 zope.interface-5.4.0

अब PATH को अपडेट करें, ताकि इंस्टॉल की गई नई location बाइनरी का पता लगाया जा सके:

PATH=~/.local/bin":$PATH"
which locust

कमांड आउटपुट

/home/<user>/.local/bin/locust

खास जानकारी

अगर आपके पास पहले से प्रोजेक्ट नहीं था, तो आपने क्लाउड शेल चालू किया था और इस लैब के लिए कोड डाउनलोड किया था. इसके बाद, आपने अपना प्रोजेक्ट सेट अप कर लिया.

आखिर में, आप लैब में बाद में लोड जनरेट करने के लिए लोकस्ट को सेट अप करते हैं.

अगला अवॉर्ड

इसके बाद, आपको Cloud Spanner इंस्टेंस और डेटाबेस सेट अप करना होगा.

3. स्पैनर इंस्टेंस और डेटाबेस बनाना

स्पैनर इंस्टेंस बनाना

इस चरण में, हम कोडलैब के लिए स्पैनर इंस्टेंस सेट अप करते हैं. 1a6580bd3d3e6783.pngसबसे ऊपर बाईं ओर मौजूद हैमबर्गर मेन्यू 3129589f7bc9e5ce.png में स्पैनर एंट्री खोजें या "/" दबाकर स्पैनर खोजें और "स्पैनर" लिखें

36e52f8df8e13b99.png

इसके बाद, 95269e75bc8c3e4d.png पर क्लिक करें और अपने इंस्टेंस के लिए इंस्टेंस का नाम cloudspanner-gaming डालकर फ़ॉर्म भरें. इसके बाद, कोई कॉन्फ़िगरेशन (कोई रीजनल इंस्टेंस चुनें, जैसे कि us-central1) चुनें और नोड की संख्या सेट करें. इस कोडलैब के लिए, हमें सिर्फ़ 500 processing units की ज़रूरत होगी.

आखिर में, "बनाएं" पर क्लिक करें. और कुछ ही सेकंड में आपके पास Cloud Spanner का इंस्टेंस उपलब्ध हो जाता है.

4457c324c94f93e6.png

डेटाबेस और स्कीमा बनाना

आपका इंस्टेंस चालू होने के बाद, डेटाबेस बनाया जा सकता है. स्पैनर की मदद से किसी एक इंस्टेंस पर कई डेटाबेस बनाए जा सकते हैं.

डेटाबेस वह जगह है जहां आपका स्कीमा तय किया जाता है. आपके पास यह भी कंट्रोल करने का विकल्प होता है कि डेटाबेस को ऐक्सेस करने के लिए किसके पास ऐक्सेस हो. कस्टम एन्क्रिप्शन सेट अप करें, ऑप्टिमाइज़र कॉन्फ़िगर करें, और डेटा के रखरखाव की अवधि सेट करें.

एक से ज़्यादा रीजनल इंस्टेंस पर, डिफ़ॉल्ट लीडर को भी कॉन्फ़िगर किया जा सकता है. स्पैनर पर डेटाबेस के बारे में ज़्यादा पढ़ें.

इस कोड-लैब के लिए, आपको डिफ़ॉल्ट विकल्पों के साथ डेटाबेस बनाना होगा और बनाते समय स्कीमा उपलब्ध कराना होगा.

यह लैब दो टेबल बनाएगा: खिलाड़ी और गेम.

77651ac12e47fe2a.png

खिलाड़ी समय के साथ कई गेम में हिस्सा ले सकते हैं, लेकिन एक बार में सिर्फ़ एक ही गेम में हिस्सा ले सकते हैं. खिलाड़ियों के पास JSON डेटा टाइप के तौर पर आंकड़े भी होते हैं, ताकि games_played और games_Wonder जैसे दिलचस्प आंकड़ों पर नज़र रखी जा सके. दूसरे आंकड़े बाद में जोड़े जा सकते हैं. इसलिए, खिलाड़ियों के लिए यह कॉलम पूरी तरह से स्कीमालेस होता है.

गेम में, स्पैनर के ARRAY डेटा टाइप का इस्तेमाल करके, हिस्सा लेने वाले खिलाड़ियों पर नज़र रखी जाती है. जब तक गेम बंद नहीं किया जाता, तब तक उसके विजेता और खत्म हो चुके एट्रिब्यूट की जानकारी अपने-आप नहीं भरती.

खिलाड़ी के current_game एक मान्य गेम है, यह पक्का करने के लिए एक विदेशी कुंजी होती है.

अब ‘डेटाबेस बनाएं' पर क्लिक करके डेटाबेस बनाएं इंस्टेंस की खास जानकारी में:

a820db6c4a4d6f2d.png

इसके बाद, जानकारी भरें. डेटाबेस का नाम और भाषा अहम विकल्प हैं. इस उदाहरण में, हमने डेटाबेस को सैंपल-गेम नाम दिया है और Google के स्टैंडर्ड एसक्यूएल भाषा को चुना है.

स्कीमा के लिए, इस डीडीएल को कॉपी करके बॉक्स में चिपकाएं:

CREATE TABLE games (
  gameUUID STRING(36) NOT NULL,
  players ARRAY<STRING(36)> NOT NULL,
  winner STRING(36),
  created TIMESTAMP,
  finished TIMESTAMP,
) PRIMARY KEY(gameUUID);

CREATE TABLE players (
  playerUUID STRING(36) NOT NULL,
  player_name STRING(64) NOT NULL,
  email STRING(MAX) NOT NULL,
  password_hash BYTES(60) NOT NULL,
  created TIMESTAMP,
  updated TIMESTAMP,
  stats JSON,
  account_balance NUMERIC NOT NULL DEFAULT (0.00),
  is_logged_in BOOL,
  last_login TIMESTAMP,
  valid_email BOOL,
  current_game STRING(36),
  FOREIGN KEY (current_game) REFERENCES games (gameUUID),
) PRIMARY KEY(playerUUID);

CREATE UNIQUE INDEX PlayerAuthentication ON players(email) STORING (password_hash);

CREATE INDEX PlayerGame ON players(current_game);

CREATE UNIQUE INDEX PlayerName ON players(player_name);

इसके बाद, 'बनाएं' बटन पर क्लिक करें और अपना डेटाबेस बनने के लिए कुछ सेकंड इंतज़ार करें.

डेटाबेस बनाएं पेज कुछ ऐसा दिखना चाहिए:

d39d358dc7d32939.png

अब आपको Cloud Shell में कुछ एनवायरमेंट वैरिएबल सेट करने होंगे, ताकि कोड लैब में बाद में उनका इस्तेमाल किया जा सके. इसलिए, इंस्टेंस-आईडी को नोट करके, Cloud Shell में INSTANCE_ID और DATABASE_ID को सेट करें

f6f98848d3aea9c.png

export SPANNER_PROJECT_ID=$GOOGLE_CLOUD_PROJECT
export SPANNER_INSTANCE_ID=cloudspanner-gaming
export SPANNER_DATABASE_ID=sample-game

खास जानकारी

इस चरण में, आपने एक स्पैनर इंस्टेंस और गेम-गेम का डेटाबेस बनाया है. आपने इस सैंपल गेम में इस्तेमाल होने वाला स्कीमा भी तय किया है.

अगला अवॉर्ड

इसके बाद, आपको प्रोफ़ाइल सेवा का इस्तेमाल करना होगा, ताकि खिलाड़ी गेम खेलने के लिए साइन अप कर सकें!

4. प्रोफ़ाइल सेवा को डिप्लॉय करें

सेवा की खास जानकारी

प्रोफ़ाइल सेवा, Go में लिखी गई एक REST API है, जो जिन फ़्रेमवर्क का इस्तेमाल करती है.

4fce45ee6c858b3e.png

इस एपीआई में, खिलाड़ी गेम खेलने के लिए साइन अप कर सकते हैं. इसे एक आसान से पीओएसटी निर्देश से बनाया जाता है. इसमें प्लेयर का नाम, ईमेल पता, और पासवर्ड शामिल होता है. पासवर्ड को bcrypt से एन्क्रिप्ट (सुरक्षित) किया जाता है और हैश को डेटाबेस में सेव किया जाता है.

ईमेल को एक यूनीक आइडेंटिफ़ायर माना जाता है, जबकि player_name का इस्तेमाल गेम के डिसप्ले के लिए किया जाता है.

फ़िलहाल, यह एपीआई लॉगिन को हैंडल नहीं करता. हालांकि, इसे लागू करने के लिए आपको एक अतिरिक्त चरण दिया जा सकता है.

प्रोफ़ाइल सेवा के लिए ./src/golang/profile-service/main.go फ़ाइल में दो प्राइमरी एंडपॉइंट को इस तरह दिखाया जाता है:

func main() {
   configuration, _ := config.NewConfig()

   router := gin.Default()
   router.SetTrustedProxies(nil)

   router.Use(setSpannerConnection(configuration))

   router.POST("/players", createPlayer)
   router.GET("/players", getPlayerUUIDs)
   router.GET("/players/:id", getPlayerByID)

   router.Run(configuration.Server.URL())
}

साथ ही, उन एंडपॉइंट का कोड प्लेयर मॉडल पर भेजा जाएगा.

func getPlayerByID(c *gin.Context) {
   var playerUUID = c.Param("id")

   ctx, client := getSpannerConnection(c)

   player, err := models.GetPlayerByUUID(ctx, client, playerUUID)
   if err != nil {
       c.IndentedJSON(http.StatusNotFound, gin.H{"message": "player not found"})
       return
   }

   c.IndentedJSON(http.StatusOK, player)
}

func createPlayer(c *gin.Context) {
   var player models.Player

   if err := c.BindJSON(&player); err != nil {
       c.AbortWithError(http.StatusBadRequest, err)
       return
   }

   ctx, client := getSpannerConnection(c)
   err := player.AddPlayer(ctx, client)
   if err != nil {
       c.AbortWithError(http.StatusBadRequest, err)
       return
   }

   c.IndentedJSON(http.StatusCreated, player.PlayerUUID)
}

यह सेवा सबसे पहले स्पैनर कनेक्शन सेट करती है. इसे सेवा के लेवल पर लागू किया जाता है, ताकि किसी सेवा के लिए सेशन पूल बनाया जा सके.

func setSpannerConnection() gin.HandlerFunc {
   ctx := context.Background()
   client, err := spanner.NewClient(ctx, configuration.Spanner.URL())

   if err != nil {
       log.Fatal(err)
   }

   return func(c *gin.Context) {
       c.Set("spanner_client", *client)
       c.Set("spanner_context", ctx)
       c.Next()
   }
}

Player और PlayerStats ऐसे स्ट्रक्चर हैं जो इस तरह से बताए गए हैं:

type Player struct {
   PlayerUUID      string `json:"playerUUID" validate:"omitempty,uuid4"`
   Player_name     string `json:"player_name" validate:"required_with=Password Email"`
   Email           string `json:"email" validate:"required_with=Player_name Password,email"`
   // not stored in DB
   Password        string `json:"password" validate:"required_with=Player_name Email"` 
   // stored in DB
   Password_hash   []byte `json:"password_hash"`                                       
   created         time.Time
   updated         time.Time
   Stats           spanner.NullJSON `json:"stats"`
   Account_balance big.Rat          `json:"account_balance"`
   last_login      time.Time
   is_logged_in    bool
   valid_email     bool
   Current_game    string `json:"current_game" validate:"omitempty,uuid4"`
}

type PlayerStats struct {
   Games_played spanner.NullInt64 `json:"games_played"`
   Games_won    spanner.NullInt64 `json:"games_won"`
}

प्लेयर को जोड़ने के लिए फ़ंक्शन, ReadWrite लेन-देन में डीएमएल इंसर्ट का इस्तेमाल करता है, क्योंकि प्लेयर को जोड़ना बैच इंसर्ट करने के बजाय एक स्टेटमेंट होता है. फ़ंक्शन इस तरह दिखता है:

func (p *Player) AddPlayer(ctx context.Context, client spanner.Client) error {
   // Validate based on struct validation rules
   err := p.Validate()
   if err != nil {
       return err
   }

   // take supplied password+salt, hash. Store in user_password
   passHash, err := hashPassword(p.Password)

   if err != nil {
       return errors.New("Unable to hash password")
   }

   p.Password_hash = passHash

   // Generate UUIDv4
   p.PlayerUUID = generateUUID()

   // Initialize player stats
   emptyStats := spanner.NullJSON{Value: PlayerStats{
       Games_played: spanner.NullInt64{Int64: 0, Valid: true},
       Games_won:    spanner.NullInt64{Int64: 0, Valid: true},
   }, Valid: true}

   // insert into spanner
   _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
       stmt := spanner.Statement{
           SQL: `INSERT players (playerUUID, player_name, email, password_hash, created, stats) VALUES
                   (@playerUUID, @playerName, @email, @passwordHash, CURRENT_TIMESTAMP(), @pStats)
           `,
           Params: map[string]interface{}{
               "playerUUID":   p.PlayerUUID,
               "playerName":   p.Player_name,
               "email":        p.Email,
               "passwordHash": p.Password_hash,
               "pStats":       emptyStats,
           },
       }

       _, err := txn.Update(ctx, stmt)
       return err
   })
   if err != nil {
       return err
   }
   // return empty error on success
   return nil
}

किसी खिलाड़ी को उसके यूयूआईडी के आधार पर वापस पाने के लिए, आसानी से पढ़ा जा सकता है. इससे प्लेयर के playerUUID, Player_name, ईमेल,और आंकड़े वापस आ जाते हैं.

func GetPlayerByUUID(ctx context.Context, client spanner.Client, uuid string) (Player, error) {
   row, err := client.Single().ReadRow(ctx, "players",
       spanner.Key{uuid}, []string{"playerUUID", "player_name", "email", "stats"})
   if err != nil {
       return Player{}, err
   }

   player := Player{}
   err = row.ToStruct(&player)

   if err != nil {
       return Player{}, err
   }
   return player, nil
}

डिफ़ॉल्ट रूप से, इस सेवा को एनवायरमेंट वैरिएबल का इस्तेमाल करके कॉन्फ़िगर किया जाता है. ./src/golang/profile-service/config/config.go फ़ाइल में दिया गया सेक्शन देखें.

func NewConfig() (Config, error) {
   *snip*
   // Server defaults
   viper.SetDefault("server.host", "localhost")
   viper.SetDefault("server.port", 8080)

   // Bind environment variable override
   viper.BindEnv("server.host", "SERVICE_HOST")
   viper.BindEnv("server.port", "SERVICE_PORT")
   viper.BindEnv("spanner.project_id", "SPANNER_PROJECT_ID")
   viper.BindEnv("spanner.instance_id", "SPANNER_INSTANCE_ID")
   viper.BindEnv("spanner.database_id", "SPANNER_DATABASE_ID")

   *snip*

   return c, nil
}

आप देख सकते हैं कि सेवा को localhost:8080 पर चलाने का डिफ़ॉल्ट तरीका है.

इस जानकारी के साथ सेवा को चलाने का समय आ जाता है.

प्रोफ़ाइल की सेवा चालू करें

go आदेश का इस्तेमाल करके सेवा को चलाएं. इससे डिपेंडेंसी डाउनलोड हो जाएगी और पोर्ट 8080 पर चल रही सेवा चालू हो जाएगी:

cd ~/spanner-gaming-sample/src/golang/profile-service
go run . &

कमांड आउटपुट:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /players                  --> main.createPlayer (4 handlers)
[GIN-debug] GET    /players                  --> main.getPlayerUUIDs (4 handlers)
[GIN-debug] GET    /players/:id              --> main.getPlayerByID (4 handlers)
[GIN-debug] GET    /players/:id/stats        --> main.getPlayerStats (4 handlers)
[GIN-debug] Listening and serving HTTP on localhost:8080

कर्ल निर्देश जारी करके सेवा की जांच करें:

curl http://localhost:8080/players \
    --include \
    --header "Content-Type: application/json" \
    --request "POST" \
    --data '{"email": "test@gmail.com","password": "s3cur3P@ss","player_name": "Test Player"}'

कमांड आउटपुट:

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: <date> 18:55:08 GMT
Content-Length: 38

"506a1ab6-ee5b-4882-9bb1-ef9159a72989"

खास जानकारी

इस चरण में, आपने वह प्रोफ़ाइल सेवा डिप्लॉय की है जो खिलाड़ियों को आपका गेम खेलने के लिए साइन अप करने की अनुमति देती है. साथ ही, आपने नया खिलाड़ी बनाने के लिए पोस्ट एपीआई कॉल जारी करके सेवा की जांच की.

अगले चरण

अगले चरण में, मैच बनाने वाली सेवा डिप्लॉय करें.

5. मैच बनाने वाली सेवा डिप्लॉय करना

सेवा की खास जानकारी

मैच बनाने वाली सेवा, Go में लिखा गया एक REST API है, जो जिन फ़्रेमवर्क का इस्तेमाल करता है.

9aecd571df0dcd7c.png

इस एपीआई में, गेम बनाए जाते हैं और बंद हो जाते हैं. जब कोई गेम बनाया जाता है, तो ऐसे 10 खिलाड़ी गेम को असाइन किए जाते हैं जो अभी गेम नहीं खेल रहे हैं.

जब कोई गेम बंद हो जाता है, तो एक विजेता रैंडम तरीके से चुना जाता है और हर खिलाड़ी को चुना जाता है games_played और games_Wonder के लिए आंकड़े बदल दिए गए हैं. साथ ही, हर खिलाड़ी को यह बताने के लिए अपडेट किया जाता है कि वह अब गेम नहीं खेल रहा है. इसलिए, वह आने वाले समय में गेम खेलने के लिए उपलब्ध है.

मैचमेकिंग सेवा के लिए, ./src/golang/matchmaking-service/main.go फ़ाइल, profile सेवा जैसे सेटअप और कोड का इस्तेमाल करती है. इसलिए, इसे यहां दोहराया नहीं गया है. यह सेवा दो प्राइमरी एंडपॉइंट को इस तरह दिखाती है:

func main() {
   router := gin.Default()
   router.SetTrustedProxies(nil)

   router.Use(setSpannerConnection())

   router.POST("/games/create", createGame)
   router.PUT("/games/close", closeGame)

   router.Run(configuration.Server.URL())
}

यह सेवा एक गेम निर्देश के साथ-साथ प्लेयर और PlayerStats निर्देश भी देती है:

type Game struct {
   GameUUID string           `json:"gameUUID"`
   Players  []string         `json:"players"`
   Winner   string           `json:"winner"`
   Created  time.Time        `json:"created"`
   Finished spanner.NullTime `json:"finished"`
}

type Player struct {
   PlayerUUID   string           `json:"playerUUID"`
   Stats        spanner.NullJSON `json:"stats"`
   Current_game string           `json:"current_game"`
}

type PlayerStats struct {
   Games_played int `json:"games_played"`
   Games_won    int `json:"games_won"`
}

कोई गेम बनाने के लिए, मैचमेकिंग सेवा, किसी भी क्रम में 100 खिलाड़ियों को चुनती है. ये ऐसे खिलाड़ी होते हैं जो फ़िलहाल कोई गेम नहीं खेल रहे हैं.

गेम बनाने और खिलाड़ियों को असाइन करने के लिए स्पैनर म्यूटेशन चुने जाते हैं, क्योंकि बड़े बदलावों के लिए डीएमएल के मुकाबले म्यूटेशन ज़्यादा बेहतर काम करते हैं.

// Create a new game and assign players
// Players that are not currently playing a game are eligble to be selected for the new game
// Current implementation allows for less than numPlayers to be placed in a game
func (g *Game) CreateGame(ctx context.Context, client spanner.Client) error {
   // Initialize game values
   g.GameUUID = generateUUID()

   numPlayers := 10

   // Create and assign
   _, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
       var m []*spanner.Mutation

       // get players
       query := fmt.Sprintf("SELECT playerUUID FROM (SELECT playerUUID FROM players WHERE current_game IS NULL LIMIT 10000) TABLESAMPLE RESERVOIR (%d ROWS)", numPlayers)
       stmt := spanner.Statement{SQL: query}
       iter := txn.Query(ctx, stmt)

       playerRows, err := readRows(iter)
       if err != nil {
           return err
       }

       var playerUUIDs []string

       for _, row := range playerRows {
           var pUUID string
           if err := row.Columns(&pUUID); err != nil {
               return err
           }

           playerUUIDs = append(playerUUIDs, pUUID)
       }

       // Create the game
       gCols := []string{"gameUUID", "players", "created"}
       m = append(m, spanner.Insert("games", gCols, []interface{}{g.GameUUID, playerUUIDs, time.Now()}))

       // Update players to lock into this game
       for _, p := range playerUUIDs {
           pCols := []string{"playerUUID", "current_game"}
           m = append(m, spanner.Update("players", pCols, []interface{}{p, g.GameUUID}))
       }

       txn.BufferWrite(m)

       return nil
   })

   if err != nil {
       return err
   }

   return nil
}

SQL के साथ प्लेयर का रैंडम चुनाव, GoogleSQL की TABLESPACE RESERVOIR क्षमता का इस्तेमाल करके किया जाता है.

गेम को बंद करना थोड़ा ज़्यादा मुश्किल है. इसमें गेम के खिलाड़ियों में से कोई एक विजेता चुनना, गेम खत्म होने का समय बताना, और हर खिलाड़ी को अपडेट करना शामिल है games_played और games_won के लिए आंकड़े.

यह जटिलता और ज़्यादा बदलाव होने की वजह से, गेम बंद करने के लिए म्यूटेशन को फिर से चुना जाता है.

func determineWinner(playerUUIDs []string) string {
   if len(playerUUIDs) == 0 {
       return ""
   }

   var winnerUUID string

   rand.Seed(time.Now().UnixNano())
   offset := rand.Intn(len(playerUUIDs))
   winnerUUID = playerUUIDs[offset]
   return winnerUUID
}

// Given a list of players and a winner's UUID, update players of a game
// Updating players involves closing out the game (current_game = NULL) and
// updating their game stats. Specifically, we are incrementing games_played.
// If the player is the determined winner, then their games_won stat is incremented.
func (g Game) updateGamePlayers(ctx context.Context, players []Player, txn *spanner.ReadWriteTransaction) error {
   for _, p := range players {
       // Modify stats
       var pStats PlayerStats
       json.Unmarshal([]byte(p.Stats.String()), &pStats)

       pStats.Games_played = pStats.Games_played + 1

       if p.PlayerUUID == g.Winner {
           pStats.Games_won = pStats.Games_won + 1
       }
       updatedStats, _ := json.Marshal(pStats)
       p.Stats.UnmarshalJSON(updatedStats)

       // Update player
       // If player's current game isn't the same as this game, that's an error
       if p.Current_game != g.GameUUID {
           errorMsg := fmt.Sprintf("Player '%s' doesn't belong to game '%s'.", p.PlayerUUID, g.GameUUID)
           return errors.New(errorMsg)
       }

       cols := []string{"playerUUID", "current_game", "stats"}
       newGame := spanner.NullString{
           StringVal: "",
           Valid:     false,
       }

       txn.BufferWrite([]*spanner.Mutation{
           spanner.Update("players", cols, []interface{}{p.PlayerUUID, newGame, p.Stats}),
       })
   }

   return nil
}

// Closing game. When provided a Game, choose a random winner and close out the game.
// A game is closed by setting the winner and finished time.
// Additionally all players' game stats are updated, and the current_game is set to null to allow
// them to be chosen for a new game.
func (g *Game) CloseGame(ctx context.Context, client spanner.Client) error {
   // Close game
   _, err := client.ReadWriteTransaction(ctx,
       func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
           // Get game players
           playerUUIDs, players, err := g.getGamePlayers(ctx, txn)

           if err != nil {
               return err
           }

           // Might be an issue if there are no players!
           if len(playerUUIDs) == 0 {
               errorMsg := fmt.Sprintf("No players found for game '%s'", g.GameUUID)
               return errors.New(errorMsg)
           }

           // Get random winner
           g.Winner = determineWinner(playerUUIDs)

           // Validate game finished time is null
           row, err := txn.ReadRow(ctx, "games", spanner.Key{g.GameUUID}, []string{"finished"})
           if err != nil {
               return err
           }

           if err := row.Column(0, &g.Finished); err != nil {
               return err
           }

           // If time is not null, then the game is already marked as finished. 
           // That's an error.
           if !g.Finished.IsNull() {
               errorMsg := fmt.Sprintf("Game '%s' is already finished.", g.GameUUID)
               return errors.New(errorMsg)
           }

           cols := []string{"gameUUID", "finished", "winner"}
           txn.BufferWrite([]*spanner.Mutation{
               spanner.Update("games", cols, []interface{}{g.GameUUID, time.Now(), g.Winner}),
           })

           // Update each player to increment stats.games_played 
           // (and stats.games_won if winner), and set current_game 
           // to null so they can be chosen for a new game
           playerErr := g.updateGamePlayers(ctx, players, txn)
           if playerErr != nil {
               return playerErr
           }

           return nil
       })

   if err != nil {
       return err
   }

   return nil
}

कॉन्फ़िगरेशन को दोबारा, एनवायरमेंट वैरिएबल की मदद से मैनेज किया जाता है. सेवा के लिए, इस बारे में ./src/golang/matchmaking-service/config/config.go में बताया गया है.

   // Server defaults
   viper.SetDefault("server.host", "localhost")
   viper.SetDefault("server.port", 8081)

   // Bind environment variable override
   viper.BindEnv("server.host", "SERVICE_HOST")
   viper.BindEnv("server.port", "SERVICE_PORT")
   viper.BindEnv("spanner.project_id", "SPANNER_PROJECT_ID")
   viper.BindEnv("spanner.instance_id", "SPANNER_INSTANCE_ID")
   viper.BindEnv("spanner.database_id", "SPANNER_DATABASE_ID")

प्रोफ़ाइल सेवा के साथ टकराव से बचने के लिए, यह सेवा डिफ़ॉल्ट रूप से localhost:8081 पर काम करती है.

इस जानकारी के साथ, मैचमेकिंग सेवा चलाने का समय आ गया है.

मैच बनाने वाली सेवा चलाएं

go आदेश का इस्तेमाल करके सेवा को चलाएं. इससे पोर्ट 8082 पर सेवा चालू हो जाएगी. इस सेवा में और प्रोफ़ाइल सेवा की कई एक जैसी डिपेंडेंसी हैं. इसलिए, नई डिपेंडेंसी डाउनलोड नहीं की जाएंगी.

cd ~/spanner-gaming-sample/src/golang/matchmaking-service
go run . &

कमांड आउटपुट:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /games/create             --> main.createGame (4 handlers)
[GIN-debug] PUT    /games/close              --> main.closeGame (4 handlers)
[GIN-debug] Listening and serving HTTP on localhost:8081

गेम बनाएं

गेम बनाने के लिए, सेवा की जांच करें. सबसे पहले, Cloud Shell में एक नया टर्मिनल खोलें:

90eceac76a6bb90b.png

इसके बाद, यह कर्ल निर्देश जारी करें:

curl http://localhost:8081/games/create \
    --include \
    --header "Content-Type: application/json" \
    --request "POST"

कमांड आउटपुट:

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: <date> 19:38:45 GMT
Content-Length: 38

"f45b0f7f-405b-4e67-a3b8-a624e990285d"

गेम बंद करें

curl http://localhost:8081/games/close \
    --include \
    --header "Content-Type: application/json" \
    --data '{"gameUUID": "f45b0f7f-405b-4e67-a3b8-a624e990285d"}' \
    --request "PUT"

कमांड आउटपुट:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: <date> 19:43:58 GMT
Content-Length: 38

"506a1ab6-ee5b-4882-9bb1-ef9159a72989"

खास जानकारी

इस चरण में, गेम बनाने और उस गेम में खिलाड़ी असाइन करने के लिए मैचमेकिंग सेवा डिप्लॉय की जाती है. यह सेवा किसी गेम को बंद करने का काम भी संभालती है, जिसमें किसी भी रैंडम विजेता को चुना जाता है और गेम के सभी खिलाड़ियों के गेम की जानकारी को अपडेट किया जाता है games_played और games_won के लिए आंकड़े.

अगले चरण

अब जब आपकी सेवाएं चालू हो रही हैं, खिलाड़ियों के लिए साइन अप करने और गेम खेलने के लिए तैयार करने का समय आ गया है!

6. खेलना शुरू करें

अब प्रोफ़ाइल और मैचमेकिंग सेवाएं काम कर रही हैं, इसलिए उपलब्ध कराए गए लोकस्ट जनरेटर का इस्तेमाल करके लोड जनरेट किया जा सकता है.

लोकस्ट, जनरेटर चलाने के लिए एक वेब इंटरफ़ेस की सुविधा देता है, लेकिन इस लैब में आपको कमांड लाइन (–हेडलेस विकल्प) का इस्तेमाल करना होगा.

साइन अप करने वाले खिलाड़ी

सबसे पहले, आपको प्लेयर जनरेट करने होंगे.

./generators/authentication_server.py फ़ाइल में प्लेयर बनाने के लिए Python कोड कुछ ऐसा दिखता है:

class PlayerLoad(HttpUser):
   def on_start(self):
       global pUUIDs
       pUUIDs = []

   def generatePlayerName(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))

   def generatePassword(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))

   def generateEmail(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32) + ['@'] +
           random.choices(['gmail', 'yahoo', 'microsoft']) + ['.com'])

   @task
   def createPlayer(self):
       headers = {"Content-Type": "application/json"}
       data = {"player_name": self.generatePlayerName(), "email": self.generateEmail(), "password": self.generatePassword()}

       with self.client.post("/players", data=json.dumps(data), headers=headers, catch_response=True) as response:
           try:
               pUUIDs.append(response.json())
           except json.JSONDecodeError:
               response.failure("Response could not be decoded as JSON")
           except KeyError:
               response.failure("Response did not contain expected key 'gameUUID'")

खिलाड़ी के नाम, ईमेल, और पासवर्ड बिना किसी क्रम के जनरेट किए जाते हैं.

साइन अप कर लिए गए प्लेयर को दूसरे टास्क के ज़रिए वापस लाया जाएगा, ताकि उन्हें रीड लोड किया जा सके.

   @task(5)
   def getPlayer(self):
       # No player UUIDs are in memory, reschedule task to run again later.
       if len(pUUIDs) == 0:
           raise RescheduleTask()

       # Get first player in our list, removing it to avoid contention from concurrent requests
       pUUID = pUUIDs[0]
       del pUUIDs[0]

       headers = {"Content-Type": "application/json"}

       self.client.get(f"/players/{pUUID}", headers=headers, name="/players/[playerUUID]")

नीचे दिया गया निर्देश ./generators/authentication_server.py फ़ाइल को कॉल करता है, जो 30 सेकंड (t=30s) के लिए नए प्लेयर जनरेट करेगी, जिसमें एक ही समय में दो थ्रेड (u=2) होंगे:

cd ~/spanner-gaming-sample
locust -H http://127.0.0.1:8080 -f ./generators/authentication_server.py --headless -u=2 -r=2 -t=30s

खिलाड़ी गेम से जुड़ रहे हैं

अब आपने खिलाड़ियों के लिए साइन अप कर लिया है, वे गेम खेलना शुरू करना चाहते हैं!

./generators/match_server.py फ़ाइल में गेम बनाने और बंद करने के लिए Python कोड ऐसा दिखता है:

from locust import HttpUser, task
from locust.exception import RescheduleTask

import json

class GameMatch(HttpUser):
   def on_start(self):
       global openGames
       openGames = []

   @task(2)
   def createGame(self):
       headers = {"Content-Type": "application/json"}

       # Create the game, then store the response in memory of list of open games.
       with self.client.post("/games/create", headers=headers, catch_response=True) as response:
           try:
               openGames.append({"gameUUID": response.json()})
           except json.JSONDecodeError:
               response.failure("Response could not be decoded as JSON")
           except KeyError:
               response.failure("Response did not contain expected key 'gameUUID'")


   @task
   def closeGame(self):
       # No open games are in memory, reschedule task to run again later.
       if len(openGames) == 0:
           raise RescheduleTask()

       headers = {"Content-Type": "application/json"}

       # Close the first open game in our list, removing it to avoid 
       # contention from concurrent requests
       game = openGames[0]
       del openGames[0]

       data = {"gameUUID": game["gameUUID"]}
       self.client.put("/games/close", data=json.dumps(data), headers=headers)

इस जनरेटर के चालू होने पर, यह 2:1 (ओपन:क्लोज़्ड) के अनुपात में गेम खोले और बंद कर देगा. यह निर्देश, जनरेटर को 10 सेकंड के लिए चलाएगा (-t=10s):

locust -H http://127.0.0.1:8081 -f ./generators/match_server.py --headless -u=1 -r=1 -t=10s

खास जानकारी

इस चरण में, गेम खेलने के लिए साइन अप करने वाले खिलाड़ियों को सिम्युलेट किया जाता है. इसके बाद, मैचमेकिंग सेवा का इस्तेमाल करके, खिलाड़ियों को गेम खेलने के लिए सिम्युलेशन चलाया जाता है. इन सिम्युलेशन ने हमारी सेवाओं के अनुरोध जारी करने के लिए, लोक्ट Python फ़्रेमवर्क का इस्तेमाल किया REST एपीआई.

खिलाड़ी बनाने और गेम खेलने में लगने वाले समय के साथ-साथ, एक साथ काम करने वाले उपयोगकर्ताओं की संख्या (-u) में बदलाव करें.

अगले चरण

सिम्युलेशन के बाद, स्पैनर में क्वेरी करके अलग-अलग आंकड़े देखे जा सकते हैं.

7. गेम के आंकड़े फिर से पाएं

अब हमारे पास ऐसे खिलाड़ियों को सिम्युलेट किया जाता है जो साइन अप करके गेम खेल सकते हैं. इसलिए, आपको अपने आंकड़े देखने चाहिए.

ऐसा करने के लिए, Spanner को क्वेरी अनुरोध जारी करने के लिए Cloud Console का इस्तेमाल करें.

b5e3154c6f7cb0cf.png

खुले बनाम बंद गेम की जांच करना

क्लोज़्ड गेम उसे माना जाता है जिसमें खत्म होने के टाइमस्टैंप में जानकारी अपने-आप भर जाती है, जबकि ओपन गेम के लिए खत्म होने का समय शून्य होता है. यह वैल्यू, गेम बंद होने पर सेट होती है.

इस तरह, इस क्वेरी से आपको यह पता चलेगा कि कितने गेम खेले जा सकते हैं और कितने गेम उपलब्ध हैं:

SELECT Type, NumGames FROM
(SELECT "Open Games" as Type, count(*) as NumGames FROM games WHERE finished IS NULL
UNION ALL
SELECT "Closed Games" as Type, count(*) as NumGames FROM games WHERE finished IS NOT NULL
)

नतीजा:

Type

NumGames

Open Games

0

Closed Games

175

खेल रहे बनाम नहीं खेलने वाले खिलाड़ियों की संख्या की जांच करना

अगर खिलाड़ी का current_game कॉलम सेट है, तो वह गेम खेल रहा है. अगर ऐसा नहीं है, तो वे अभी कोई गेम नहीं खेल रहे हैं.

इसलिए, यह तुलना करने के लिए कि फ़िलहाल कितने खिलाड़ी खेल रहे हैं और कितने नहीं खेल रहे हैं, इस क्वेरी का इस्तेमाल करें:

SELECT Type, NumPlayers FROM
(SELECT "Playing" as Type, count(*) as NumPlayers FROM players WHERE current_game IS NOT NULL
UNION ALL
SELECT "Not Playing" as Type, count(*) as NumPlayers FROM players WHERE current_game IS NULL
)

नतीजा:

Type

NumPlayers

Playing

0

Not Playing

310

सबसे अच्छे विजेताओं का पता लगाएं

गेम बंद होने पर, किसी एक खिलाड़ी को विजेता बनने के लिए रैंडम तरीके से चुना जाता है. गेम बंद करने पर, खिलाड़ी के games_won आंकड़े बढ़ जाते हैं.

SELECT playerUUID, stats
FROM players
WHERE CAST(JSON_VALUE(stats, "$.games_won") AS INT64)>0
LIMIT 10;

नतीजा:

playerUUID

आंकड़े

07e247c5-f88e-4bca-a7bc-12d2485f2f2b

{&quot;games_played&quot;:49,&quot;games_won&quot;:1}

09b72595-40af-4406-a000-2fb56c58fe92

{&quot;games_played&quot;:56,&quot;games_won&quot;:1}

1002385b-02a0-462b-a8e7-05c9b27223aa

{&quot;games_played&quot;:66,&quot;games_won&quot;:1}

13ec3770-7ae3-495f-9b53-6322d8e8d6c3

{&quot;games_played&quot;:44,&quot;games_won&quot;:1}

15513852-3f2a-494f-b437-fe7125d15f1b

{&quot;games_played&quot;:49,&quot;games_won&quot;:1}

17faec64-4f77-475c-8df8-6ab026cf6698

{&quot;games_played&quot;:50,&quot;games_won&quot;:1}

1abfcb27-037d-446d-bb7a-b5cd17b5733d

{&quot;games_played&quot;:63,&quot;games_won&quot;:1}

2109a33e-88bd-4e74-a35c-a7914d9e3bde

{&quot;games_played&quot;:56,&quot;games_won&quot;:2}

222e37d9-06b0-4674-865d-a0e5fb80121e

{&quot;games_played&quot;:60,&quot;games_won&quot;:1}

22ced15c-0da6-4fd9-8cb2-1ffd233b3c56

{&quot;games_played&quot;:50,&quot;games_won&quot;:1}

खास जानकारी

इस चरण में, आपने Spanner की क्वेरी करने के लिए Cloud Console का इस्तेमाल करके, खिलाड़ियों और गेम के अलग-अलग आंकड़ों की समीक्षा की है.

अगले चरण

अब यह स्टोरेज खाली करने का समय है!

8. जगह खाली की जा रही है (ज़रूरी नहीं)

स्टोरेज खाली करने के लिए, Cloud Console के Cloud Spanner सेक्शन में जाएं. इसके बाद, ‘cloudspanner-gaming' इंस्टेंस को मिटाएं, जिसे हमने "क्लाउड स्पैनर इंस्टेंस सेट अप करें" नाम के कोडलैब चरण में बनाया था.

9. बधाई हो!

बधाई हो, आपने Spanner पर सैंपल गेम डिप्लॉय कर लिया है

आगे क्या करना है?

इस लैब में, आपको golang ड्राइवर का इस्तेमाल करके स्पैनर के साथ काम करने से जुड़े अलग-अलग विषयों के बारे में बताया गया है. इससे आपको इन अहम कॉन्सेप्ट को समझने के लिए बेहतर जानकारी मिलेगी:

  • स्कीमा डिज़ाइन
  • डीएमएल बनाम म्यूटेशन
  • Golang के साथ काम करना

अपने गेम के लिए बैकएंड के तौर पर Spanner के साथ काम करने का एक और उदाहरण देखने के लिए, Cloud Spanner Game Trading Post कोडलैब पर एक नज़र डालना न भूलें!