练习:将应用连接至缓存

已完成

在 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”文件并添加以下文本。 将连接字符串粘贴到设置的 value 中。

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

    重要

    每次在编辑器中将代码粘贴到文件或更改文件中的代码后,请务必使用“...”菜单或快捷键(Windows 和 Linux 上为 Ctrl+S,macOS 上为 Cmd+S)保存。

  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。 将返回的值命名为 cache

  3. 由于创建的连接可释放,因此请将其包装在 using 块中。 代码应如下所示。

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

注意

与用于 Redis 的 Azure 缓存的连接由 ConnectionMultiplexer 类管理。 应在整个客户端应用程序中共享和重复使用此类。 我们不需要为每个操作创建新连接。 我们应该将连接作为字段存储在类中,并为每个操作重复使用它。 此处我们只需在 Main 方法中使用连接,但在生产应用程序中,应将连接存储在某个类字段或单一实例中。

将值添加到缓存

创建连接后,让我们将值添加到缓存。

  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 关键字

若要将 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 方法的调用,以便将另一个值添加到缓存。 将“counter”的值设置为“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 方法递增 counter 值。 传递要添加到计数器的数字 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 包中回调样式的方法转换为可等待承诺。
    • 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. 在 Windows 和 Linux 上通过按 Ctrl + S 保存文件,在 macOS 上通过按 Cmd + S 进行保存。

设置实现

现在是时候为应用程序编写代码了。

  1. 在编辑器中选择“app.js”。

  2. 首先,我们将添加 require 语句。 在文件顶部粘贴以下代码。

    var Promise = require("bluebird");
    var redis = require("redis");
    
  3. 接下来,我们将加载 .env 配置,并使用 bluebird 的 promisifyAll 函数将 redis 包的函数和方法转换为可等待的承诺。 粘贴以下代码。

    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