練習 - 將應用程式連線至快取

已完成

既然已在 Azure 中建立 Redis 快取,讓我們建立應用程式來使用它。 請確定您有 Azure 入口網站的連接字串資訊。

注意

在右側有可用的整合式 Cloud Shell。 您可以使用該命令提示字元,以建立及執行我們在這裡建置的範例程式碼,或者,如果您已設定 .NET Core 開發環境,也可以在本機執行這些步驟。

建立主控台應用程式

我們會使用主控台應用程式,以便專注於 Redis 實作。

  1. 在 Cloud Shell 中,建立新的 .NET Core 主控台應用程式,並將其命名為 SportsStatsTracker

    dotnet new console --name SportsStatsTracker
    
  2. 將目前的目錄變更為新專案的資料夾。

    cd SportsStatsTracker
    

新增連接字串

將從 Azure 入口網站取得的連接字串新增至程式碼。 請勿將此類認證儲存在原始程式碼中。 為了讓此範例保持簡單,我們要使用組態檔。 對於 Azure 中的伺服器端應用程式,更理想的方法是使用 Azure Key Vault 與憑證。

  1. 建立新的 appsettings.json 檔案以新增至專案。

    touch appsettings.json
    
  2. 透過在專案資料夾中輸入 code . 以開啟程式碼編輯器。 如果您是在本機工作,我們建議您使用 Visual Studio Code。 這裡的步驟大致上與其使用方式一致。

  3. 在編輯器中選取 appsettings.json 檔案,然後新增下列文字。 將您的連接字串貼到設定的 [值] 中。

    {
      "CacheConnection": "[value-goes-here]"
    }
    
  4. 儲存變更。

    重要

    每當將程式碼貼上或變更為編輯器中的檔案時,請務必使用 [...] 功能表或快速鍵儲存 (Ctrl + S 適用於 Windows 與 Linux、Cmd + S 適用於 macOS )。

  5. 在編輯器中選取 SportsStatsTracker.csproj 檔案將其開啟。

  6. 將下列 <ItemGroup> 設定區塊新增至 <PropertyGroup> 元素下方的根 <Project> 元素。 此設定會在專案中包含新檔案,並將其複製到輸出資料夾。 此區塊中的指示會確保應用程式設定檔在編譯/建置應用程式時,位於輸出目錄中。

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
          ...
      </PropertyGroup>
    
      <ItemGroup>
         <None Update="appsettings.json">
           <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </None>
      </ItemGroup>
    
    </Project>
    
  7. 儲存檔案。

    重要

    如果未儲存檔案,則在稍後新增套件時會遺失變更。

新增 JSON 設定檔讀取支援

.NET Core 應用程式需要其他 NuGet 套件,才能讀取 JSON 設定檔。

在視窗的命令提示字元區段中,新增對 Microsoft.Extensions.Configuration.Json NuGet 套件的參考:

dotnet add package Microsoft.Extensions.Configuration.Json

新增程式碼來讀取設定檔

既然我們已新增必要程式庫來啟用讀取組態,因此需要在主控台應用程式內啟用該功能。

  1. 在編輯器中選取 Program.cs。 以下列程式碼來取代 檔案的內容:

    using Microsoft.Extensions.Configuration;
    
    namespace SportsStatsTracker
    {
        class Program
        {
            static void Main(string[] args)
            {
                var config = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .Build();
            }
        }
    }
    

    using 陳述式可讓我們存取程式庫以讀取設定,而 Main 方法中的程式碼會初始化設定系統以從 appsettings.json 檔案讀取。

從組態取得連接字串

Program.cs 中於 Main 方法結尾,使用新的 config 變數擷取連接字串,並且儲存在名為 connectionString 的新變數中。

Config 變數具有索引子,您可以在其中傳入字串,以便從 appSettings.json 檔案進行擷取。

string connectionString = config["CacheConnection"];

新增 Redis 快取 .NET 用戶端的支援

接下來,讓我們設定主控台應用程式,以使用適用於 .NET 的 StackExchange.Redis 用戶端。

  1. 使用 Cloud Shell 編輯器底部的命令提示字元,將 StackExchange.Redis NuGet 套件新增至專案。

    dotnet add package StackExchange.Redis
    
  2. 在編輯器中選取 Program.cs,然後為命名空間 StackExchange.Redis 新增 using

    using StackExchange.Redis;
    

安裝完成後,Redis 快取用戶端即可與專案搭配使用。

連線到快取

讓我們新增程式碼以連線到快取。

  1. 在編輯器中選取 Program.cs

  2. 藉由將連接字串傳遞給 ConnectionMultiplexer.Connect,使用它來建立 ConnectionMultiplexer。 將傳回值命名為快取

  3. 因為建立的連線是「可處置」的,請將其包裝於 using 區塊中。 您的程式碼應該看似如下。

    string connectionString = config["CacheConnection"];
    
    using (var cache = ConnectionMultiplexer.Connect(connectionString))
    {
    
    }
    

注意

與 Azure Cache for Redis 的連線會由 ConnectionMultiplexer 類別所管理。 此類別應該在整個用戶端應用程式中共用並重複使用。 我們想要對每個作業建立新連線。 相反地,我們想要將它儲存為類別中的欄位,對每個作業重複使用。 這裡我們只會在 Main 方法中使用該連線,但在生產應用程式中,該連線應該儲存於類別欄位或 singleton 中。

將值新增至快取

既然我們已經有連線,讓我們將值新增至快取。

  1. 在建立連線之後,請於 using 區塊內,使用 GetDatabase 方法來擷取 IDatabase 執行個體:

     IDatabase db = cache.GetDatabase();
    
  2. 呼叫 IDatabase 物件上的 StringSet,將金鑰 "test:key" 設為值 "some value"。

StringSet 的傳回值是 bool,會指出是否已新增金鑰。

  1. StringSet 的傳回值顯示在主控台上:

     bool setValue = db.StringSet("test:key", "some value");
     Console.WriteLine($"SET: {setValue}");
    

從快取中取得值

  1. 接下來,請使用 StringGet 來擷取值。 此方法會採用金鑰來擷取並傳回值。

  2. 輸出傳回值:

     string? getValue = db.StringGet("test:key");
     Console.WriteLine($"GET: {getValue}");
    
  3. 您的程式碼看起來應該類似:

     using System;
     using Microsoft.Extensions.Configuration;
     using System.IO;
     using StackExchange.Redis;
    
     namespace SportsStatsTracker
     {
         class Program
         {
             static void Main(string[] args)
             {
                 var config = new ConfigurationBuilder()
                     .SetBasePath(Directory.GetCurrentDirectory())
                     .AddJsonFile("appsettings.json")
                     .Build();
    
                 string connectionString = config["CacheConnection"];
    
                 using (var cache = ConnectionMultiplexer.Connect(connectionString))
                 {
                     IDatabase db = cache.GetDatabase();
    
                     bool setValue = db.StringSet("test:key", "some value");
                     Console.WriteLine($"SET: {setValue}");
    
                     string? getValue = db.StringGet("test:key");
                     Console.WriteLine($"GET: {getValue}");
                 }
             }
         }
     }
    
  4. 執行應用程式以查看結果。 將 dotnet run 輸入編輯器下方的終端機視窗。 確定您位於專案資料夾中,否則它將找不到您的程式碼來建置及執行。

     dotnet run
    

提示

若程式未執行您想要的動作而是進行編譯,可能是因為您還沒有在編輯器中儲存變更。 在終端機與編輯器視窗之間切換之前,請務必先儲存變更。

使用方法的非同步版本

我們已經能夠從快取中取得和設定值,但是我們使用的是這些方法較舊的同步版本。 在伺服器端應用程式中,這些方法不是對於執行緒的有效率使用方式。 相反地,我們想要使用「非同步」版本。 您可以輕鬆地找出它們 - 它們的結尾都是 Async

若要讓這些方法方便使用,我們可以使用 C# asyncawait 關鍵字。 如果您使用自己的 .NET Core 開發環境,而不是整合式 Cloud Shell,則必須至少使用 C# 7.1,才能將這些關鍵字套用至 Main 方法。

套用非同步關鍵字

若要將 async 關鍵字套用至 Main 方法。 我們必須做兩件事。

  1. async 關鍵字新增至 Main 方法簽章。

  2. 將傳回型別從 void 變更為 Task

    using Microsoft.Extensions.Configuration;
    using StackExchange.Redis;
    
    namespace SportsStatsTracker
    {
       class Program
       {
          static async Task Main(string[] args)
          {
             ...
    

以非同步的方式取得及設定值

我們可以保留同步方法, 並新增對 StringSetAsyncStringGetAsync 方法的呼叫以將另一個值新增到快取。 將 [計數器] 設定為 100 的值。

  1. 使用 StringSetAsyncStringGetAsync 方法來設定及擷取名為 counter 的金鑰。 將值設定為 100

  2. 套用 await 關鍵字,以從每個方法取得結果。

  3. 將結果輸出至主控台視窗,就像您對於同步版本所執行的操作一樣:

    // Simple get and put of integral data types into the cache
    setValue = await db.StringSetAsync("counter", "100");
    Console.WriteLine($"SET: {setValue}");
    
    getValue = await db.StringGetAsync("counter");
    Console.WriteLine($"GET: {getValue}");
    
  4. 再次執行應用程式。 應用程式應該仍可運作,並且現在有兩個值。

遞增值

  1. 使用 StringIncrementAsync 方法以遞增您的計數器值。 傳遞數字 50 以新增至計數器:

    • 請注意,此方法會採用金鑰以及longdouble

    • 根據傳遞的參數,它會傳回 longdouble

  2. 將方法的結果輸出至主控台。

    long newValue = await db.StringIncrementAsync("counter", 50);
    Console.WriteLine($"INCR new value = {newValue}");
    

其他作業

最後,讓我們嘗試使用 ExecuteAsync 支援再執行一些方法。

  1. 執行 PING 來測試伺服器連線。 它應該會回應 PONG

  2. 執行 FLUSHDB 以清除資料庫值。 它應該會回應 OK

    var result = await db.ExecuteAsync("ping");
    Console.WriteLine($"PING = {result.Type} : {result}");
    
    result = await db.ExecuteAsync("flushdb");
    Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
    

最終程式碼應該看似如下:

using Microsoft.Extensions.Configuration;
using StackExchange.Redis;

namespace SportsStatsTracker
{
   class Program
   {
      static async Task Main(string[] args)
      {
         var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

         string connectionString = config["CacheConnection"];

         using (var cache = ConnectionMultiplexer.Connect(connectionString))
         {
            IDatabase db = cache.GetDatabase();

            bool setValue = db.StringSet("test:key", "some value");
            Console.WriteLine($"SET: {setValue}");

            string getValue = db.StringGet("test:key");
            Console.WriteLine($"GET: {getValue}");

            setValue = await db.StringSetAsync("counter", "100");
            Console.WriteLine($"SET: {setValue}");

            getValue = await db.StringGetAsync("counter");
            Console.WriteLine($"GET: {getValue}");

            long newValue = await db.StringIncrementAsync("counter", 50);
            Console.WriteLine($"INCR new value = {newValue}");  

            var result = await db.ExecuteAsync("ping");
            Console.WriteLine($"PING = {result.Type} : {result}");

            result = await db.ExecuteAsync("flushdb");
            Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
         }
      }
   }
}

再次執行應用程式時,您應該會看到下列輸出:

SET: True
GET: some value
SET: True
GET: 100
INCR new value = 150
PING = SimpleString : PONG
FLUSHDB = SimpleString : OK

挑戰

挑戰嘗試將物件類型序列化至快取。 以下是基本步驟。

  1. 建立新的 class 與一些公用屬性。 您可以發明自己的項目 ("Person" 或 "Car" 很熱門),或者使用上一個單元提供的 "GameStats" 範例。

  2. 使用 dotnet add package 來新增 Newtonsoft.Json NuGet 套件的支援。

  3. Newtonsoft.Json 命名空間新增 using

  4. 建立其中一個物件。

  5. 使用 JsonConvert.SerializeObject 將該物件序列化,然後使用 StringSetAsync 將其推送至快取。

  6. 使用 StringGetAsync 從快取中取回該物件,然後使用 JsonConvert.DeserializeObject<T> 將它還原序列化。

既然已在 Azure 中建立 Redis 快取,讓我們建立應用程式來使用它。 請確定您有 Azure 入口網站的連線資訊。

注意

在右側有可用的整合式 Cloud Shell。 您可以使用該命令提示字元來建立及執行我們在這裡建置的範例程式碼;若您已設定 Node.js 開發環境,也可以在本機執行這些步驟。

建立主控台應用程式

我們會使用主控台應用程式,以便專注於 Redis 實作。

  1. 在 Cloud Shell 中,建立名為 redisapp 的新目錄並在該處將新的 Node.js 應用程式初始化。

     mkdir redisapp
     cd redisapp
     npm init -y
     touch app.js
    
  2. 應用程式會使用下列 npm 套件:

    • redis:用於連線到 Redis 的最常用 JavaScript 套件。
    • bluebird:用於將 redis 套件中的回呼樣式方法轉換為可等候的 Promises。
    • dotenv:從 .env 檔案載入環境變數,那是我們將儲存 Redis 連線資訊的地方。

    現在讓我們安裝它們。 執行此命令以將它們新增到我們的應用程式:

    npm install redis bluebird dotenv
    

新增設定

讓我們將從 Azure 入口網站取得的連線資訊新增到 .env 設定檔。

  1. 在專案中建立新 .env 檔案:

    touch .env
    
  2. 透過在專案資料夾中輸入 code . 以開啟程式碼編輯器。 如果您是在本機工作,我們建議您使用 Visual Studio Code。 這裡的步驟大致上與其使用方式一致。

  3. 在編輯器中選取 .env 檔案,然後貼上下列文字:

    REDISHOSTNAME=
    REDISKEY=
    REDISPORT=
    
  4. 在每個對應行的等號後面貼上主機名稱、主要金鑰與連接埠。 完成的檔案看起來如以下範例所示:

    REDISHOSTNAME=myredishost.redis.cache.windows.net
    REDISKEY=K21mLSMN++z8d1FvIeMGy3VOAgoOmqaNYCqeE44eMDc=
    REDISPORT=6380
    
  5. 使用 Ctrl+S (Windows 與 Linux) 或 Cmd+S (macOS) 儲存檔案。

設定實作

現在可以為我們的應用程式撰寫程式碼了。

  1. 在編輯器中選取 app.js

  2. 首先,新增我們的 require 陳述式。 在檔案頂端貼上下列程式碼。

    var Promise = require("bluebird");
    var redis = require("redis");
    
  3. 接著,載入我們的 .env 設定並使用 bluebird 的 promisifyAll 函式將 redis 套件的函式與方法轉換為可等候的 Promises。 貼上下列程式碼:

    require("dotenv").config();
    Promise.promisifyAll(redis);
    
  4. 現在我們會將 Redis 用戶端初始化。 貼上先前單元中的未定案程式碼 (使用 process.env 來存取我們的主機名稱、連接埠與金鑰) 以建立用戶端:

    const client = redis.createClient(
       process.env.REDISPORT,
       process.env.REDISHOSTNAME,
       {
          password: process.env.REDISKEY,
          tls: { servername: process.env.REDISHOSTNAME }
       }
    );
    

使用用戶端來使用快取

我們可以開始撰寫程式碼以與我們的 Redis 快取互動了。

  1. 首先,我們會在檔案底部新增 async 函式包裝函式以包含我們的主程式碼。 我們需要此包裝函式才能 await 我們正在使用的非同步函式呼叫。 我們會在此單元中新增之程式碼的其餘部分會位於此包裝函式中。

    (async () => {
    
       // The rest of the code you'll paste in goes here.
    
    })();
    
  2. 使用 setAsync 方法新增值到快取,並使用 getAsync 讀回該值:

    console.log("Adding value to the cache");
    await client.setAsync("myKey", "myValue");
    
    console.log("Reading value back:");
    console.log(await client.getAsync("myKey"));
    
  3. 使用 pingAsync 傳送 ping 到快取:

    console.log("Pinging the cache");
    console.log(await client.pingAsync());
    
  4. 使用 flushdbAsync 刪除快取中的所有金鑰:

    await client.flushdbAsync();
    
  5. 最後,使用 quitAsync 關閉連線:

    await client.quitAsync();
    
  6. 儲存檔案。 已完成的應用程式看起來應該像下面這樣:

    var Promise = require("bluebird");
    var redis = require("redis");
    
    require("dotenv").config();
    
    Promise.promisifyAll(redis);
    
    const client = redis.createClient(
    process.env.REDISPORT,
    process.env.REDISHOSTNAME,
    {
       password: process.env.REDISKEY,
       tls: { servername: process.env.REDISHOSTNAME }
    }
    );
    
    (async () => {
      console.log("Adding value to the cache");
      await client.setAsync("myKey", "myValue");
      console.log("Reading value back:");
      console.log(await client.getAsync("myKey"));
      console.log("Pinging the cache");
      console.log(await client.pingAsync());
      await client.flushdbAsync();
      await client.quitAsync();
    })();
    
  7. 執行應用程式。 在 Cloud Shell 中,執行下列命令。

    node app.js
    

    您會看見下列結果。

    Adding value to the cache
    Reading value back:
    myValue
    Pinging the cache
    PONG