练习:将应用连接至缓存
在 Azure 中创建 Redis 缓存后,让我们创建一个使用该缓存的应用程序。 请确保从 Azure 门户获取连接字符串信息。
注意
集成式 Cloud Shell 位于右侧。 可使用此命令提示符来创建并运行本课程要生成的示例代码;如果已设置 .NET Core 开发环境,可在本地执行这些步骤。
创建控制台应用程序
我们将使用一个控制台应用程序,以便可以专注于 Redis 的实现。
在 Cloud Shell 中创建新的 .NET Core 控制台应用程序,并将其命名为
SportsStatsTracker
。dotnet new console --name SportsStatsTracker
将当前目录更改为新项目的文件夹。
cd SportsStatsTracker
添加连接字符串
将从 Azure 门户获取的连接字符串添加到代码中。 切勿在源代码中存储此类凭证。 为使此示例保持简洁,我们将使用一个配置文件。 对于 Azure 中的服务器端应用程序,更好的方法是配合证书使用 Azure Key Vault。
创建要添加到项目的新 appsettings.json 文件。
touch appsettings.json
在项目文件夹中输入
code .
,打开代码编辑器。 如果在本地操作,则建议使用 Visual Studio Code。 此处所述步骤与其用法大致相符。在编辑器中选择“appsettings.json”文件并添加以下文本。 将连接字符串粘贴到设置的 value 中。
{ "CacheConnection": "[value-goes-here]" }
保存更改。
重要
每次在编辑器中将代码粘贴到文件或更改文件中的代码后,请务必使用“...”菜单或快捷键(Windows 和 Linux 上为 Ctrl+S,macOS 上为 Cmd+S)保存。
在编辑器中,选择“SportsStatsTracker.csproj”文件以打开它。
将以下
<ItemGroup>
配置块添加到<PropertyGroup>
元素下方的根<Project>
元素中。 此配置将在项目中包含新文件,并将其复制到输出文件夹。 此块中的指令可以确保在编译/生成应用时将应用配置文件放在输出目录中。<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> ... </PropertyGroup> <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> </Project>
保存文件。
重要
如果未保存文件,之后在添加包时会丢失该更改。
添加读取 JSON 配置文件的支持
.NET Core 应用程序需要使用其他的 NuGet 包来读取 JSON 配置文件。
在窗口的命令提示符部分,添加对 Microsoft.Extensions.Configuration.Json NuGet 包的引用:
dotnet add package Microsoft.Extensions.Configuration.Json
添加用于读取配置文件的代码
现已添加用于启用读取配置所需的库,我们需要在控制台应用程序中启用该功能。
在编辑器中,选择“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 客户端。
使用 Cloud Shell 编辑器底部的命令提示符,将 StackExchange.Redis NuGet 包添加到项目。
dotnet add package StackExchange.Redis
在编辑器中选择“Program.cs”,并为命名空间 StackExchange.Redis 添加
using
using StackExchange.Redis;
完成安装后,Redis 缓存客户端可与项目一起使用。
连接到缓存
添加用于连接缓存的代码。
在编辑器中选择“Program.cs”。
通过在
ConnectionMultiplexer.Connect
中传递连接字符串来创建ConnectionMultiplexer
。 将返回的值命名为 cache。由于创建的连接可释放,因此请将其包装在
using
块中。 代码应如下所示。string connectionString = config["CacheConnection"]; using (var cache = ConnectionMultiplexer.Connect(connectionString)) { }
注意
与用于 Redis 的 Azure 缓存的连接由 ConnectionMultiplexer
类管理。 应在整个客户端应用程序中共享和重复使用此类。 我们不需要为每个操作创建新连接。 我们应该将连接作为字段存储在类中,并为每个操作重复使用它。 此处我们只需在 Main 方法中使用连接,但在生产应用程序中,应将连接存储在某个类字段或单一实例中。
将值添加到缓存
创建连接后,让我们将值添加到缓存。
创建连接后,在
using
块中使用GetDatabase
方法来检索IDatabase
实例:IDatabase db = cache.GetDatabase();
针对
IDatabase
对象调用StringSet
,将键“test:key”设置为值“some value”。
StringSet
的返回值是 bool
,指示是否已添加该键。
在控制台上显示
StringSet
的返回值:bool setValue = db.StringSet("test:key", "some value"); Console.WriteLine($"SET: {setValue}");
从缓存中获取值
接下来,使用
StringGet
检索值。 此方法会提取要检索的键,并返回值。输出返回的值:
string? getValue = db.StringGet("test:key"); Console.WriteLine($"GET: {getValue}");
代码应如下所示:
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}"); } } } }
运行应用程序以查看结果。 在编辑器下面的终端窗口中,输入
dotnet run
。 请务必位于项目文件夹中,否则无法找到要生成和运行的代码。dotnet run
提示
如果程序没有按预期运行,但能进行编译,这可能是因为未保存编辑器中的更改。 在终端和编辑器窗口之间切换时,请始终记住保存更改。
使用方法的异步版本
我们可以获取和设置缓存中的值,但使用的是这些方法的早期同步版本。 在服务器端应用程序中,这些方法不能有效地使用线程。 现在我们改用异步版本。 你可以轻松识别这些版本,它们以 Async 结尾。
为了方便使用这些方法,可以使用 C# async
和 await
关键字。 如果使用自己的 .NET Core 开发环境而不是集成的 Cloud Shell,则必须至少使用 C# 7.1 才能将这些关键字应用于 Main 方法。
应用 async 关键字
若要将 async
关键字应用到 Main 方法, 需要执行两项操作。
将
async
关键字添加到 Main 方法签名。将返回类型从
void
更改为Task
。using Microsoft.Extensions.Configuration; using StackExchange.Redis; namespace SportsStatsTracker { class Program { static async Task Main(string[] args) { ...
以异步方式获取和设置值
可以将同步方法保留在原处。 让我们添加对 StringSetAsync
和 StringGetAsync
方法的调用,以便将另一个值添加到缓存。 将“counter”的值设置为“100”。
使用
StringSetAsync
和StringGetAsync
方法来设置和检索名为“counter”的键。 将值设置为 100。应用
await
关键字,从每个方法获取结果。将结果输出到控制台窗口 - 就像使用同步版本时所做的那样:
// 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}");
再次运行应用程序。 它仍会正常运行,但现在提供了两个值。
递增值
使用
StringIncrementAsync
方法递增 counter 值。 传递要添加到计数器的数字 50:可以看到,该方法采用了该键,以及
long
或double
。根据传递的参数,该方法将返回
long
或double
。
将方法的结果输出到控制台。
long newValue = await db.StringIncrementAsync("counter", 50); Console.WriteLine($"INCR new value = {newValue}");
其他操作
最后,让我们尝试使用 ExecuteAsync
支持执行其他的几个方法。
执行“PING”来测试服务器连接。 该命令应以“PONG”做出响应。
执行“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
质询
作为质询,请尝试在缓存中序列化对象类型。 基本步骤如下。
创建包含一些公共属性的新
class
。 可以自行创建一个类(“Person”或“Car”比较常见),或使用上一单元中提供的“GameStats”示例。使用
dotnet add package
添加对 Newtonsoft.Json NuGet 包的支持。为
Newtonsoft.Json
命名空间添加using
。创建一个对象。
使用
JsonConvert.SerializeObject
将它序列化,并使用StringSetAsync
将它推送到缓存。使用
StringGetAsync
从缓存中取回它,然后使用JsonConvert.DeserializeObject<T>
将它反序列化。
在 Azure 中创建 Redis 缓存后,让我们创建一个使用该缓存的应用程序。 请务必从 Azure 门户获取连接信息。
注意
集成式 Cloud Shell 位于右侧。 可使用此命令提示符来创建并运行本课程要生成的示例代码;如果已设置 Node.js 开发环境,可在本地执行这些步骤。
创建控制台应用程序
我们将使用一个控制台应用程序,以便可以专注于 Redis 的实现。
在 Cloud Shell 中,创建一个名为
redisapp
的新目录,并在该目录下初始化新的 Node.js 应用。mkdir redisapp cd redisapp npm init -y touch app.js
我们的应用将使用以下 npm 包:
- redis:最常用于连接到 Redis 的 JavaScript 包。
- bluebird:用于将
redis
包中回调样式的方法转换为可等待承诺。 - dotenv:从
.env
文件加载环境变量,我们将在该文件中存储 Redis 连接信息。
现在来安装这些变量。 运行以下命令,将其添加到应用中:
npm install redis bluebird dotenv
添加配置
接下来将从 Azure 门户获取的连接信息添加到 .env
配置文件中。
向项目创建新的 .env 文件:
touch .env
在项目文件夹中输入
code .
,打开代码编辑器。 如果在本地操作,则建议使用 Visual Studio Code。 此处所述步骤与其用法大致相符。在编辑器中选择 .env 文件,再粘贴以下文本:
REDISHOSTNAME= REDISKEY= REDISPORT=
在每个相应的行的等号后分别粘贴主机名、主密钥和端口。 完整文件将如以下示例所示:
REDISHOSTNAME=myredishost.redis.cache.windows.net REDISKEY=K21mLSMN++z8d1FvIeMGy3VOAgoOmqaNYCqeE44eMDc= REDISPORT=6380
在 Windows 和 Linux 上通过按 Ctrl + S 保存文件,在 macOS 上通过按 Cmd + S 进行保存。
设置实现
现在是时候为应用程序编写代码了。
在编辑器中选择“app.js”。
首先,我们将添加
require
语句。 在文件顶部粘贴以下代码。var Promise = require("bluebird"); var redis = require("redis");
接下来,我们将加载
.env
配置,并使用 bluebird 的promisifyAll
函数将redis
包的函数和方法转换为可等待的承诺。 粘贴以下代码。require("dotenv").config(); Promise.promisifyAll(redis);
现在我们将初始化 Redis 客户端。 粘贴来自上一单元的样本代码(使用
process.env
访问主机名、端口和密钥)以创建客户端:const client = redis.createClient( process.env.REDISPORT, process.env.REDISHOSTNAME, { password: process.env.REDISKEY, tls: { servername: process.env.REDISHOSTNAME } } );
通过该客户端来使用缓存
我们已准备好编写代码来与 Redis 缓存交互。
首先,在文件底部添加
async
函数包装器来包含主要代码。 需要使用此包装器才能通过await
控制要使用的异步函数调用。 要在本单元中添加的代码其余部分都将包含在此包装器中。(async () => { // The rest of the code you'll paste in goes here. })();
通过
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"));
通过
pingAsync
向缓存发送 ping 操作:console.log("Pinging the cache"); console.log(await client.pingAsync());
使用
flushdbAsync
删除缓存中的所有密钥:await client.flushdbAsync();
最后,使用
quitAsync
关闭连接:await client.quitAsync();
保存文件。 完成的应用程序应如下所示:
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(); })();
运行该应用程序。 在 Cloud Shell 中执行以下命令。
node app.js
你将看到以下结果。
Adding value to the cache Reading value back: myValue Pinging the cache PONG