연습 - 캐시에 앱 연결

완료됨

이제 Azure에서 Redis Cache를 만들었으므로 이를 사용할 애플리케이션을 만들어 보겠습니다. Azure Portal에서 연결 문자열 정보가 있는지 확인합니다.

참고

통합 Cloud Shell은 오른쪽에서 사용할 수 있습니다. 이 명령 프롬프트를 사용하여 여기서 우리가 작성하려는 예제 코드를 만들고 실행해도 되고, .NET Core 개발 환경을 설정한 경우 로컬로 다음 단계를 수행해도 됩니다.

콘솔 애플리케이션 만들기

Redis 구현에 집중할 수 있도록 콘솔 애플리케이션을 사용해 보겠습니다.

  1. Cloud Shell에서 새 .NET Core 콘솔 애플리케이션을 만들고 이름을 SportsStatsTracker로 지정합니다.

    dotnet new console --name SportsStatsTracker
    
  2. 현재 디렉터리를 새 프로젝트의 폴더로 변경합니다.

    cd SportsStatsTracker
    

연결 문자열 추가

Azure Portal에서 얻은 연결 문자열을 코드에 추가하겠습니다. 이와 같은 자격 증명을 절대로 소스 코드에 저장하지 마세요. 이 샘플이 복잡해지지 않도록, 구성 파일을 사용하겠습니다. Azure의 서버 쪽 애플리케이션에 접근하는 보다 효과적인 방법은 인증서와 함께 Azure Key Vault를 사용하는 것입니다.

  1. 프로젝트에 추가할 새 appsettings.json 파일을 만듭니다.

    touch appsettings.json
    
  2. 프로젝트 폴더에 code .를 입력하여 코드 편집기를 엽니다. 로컬로 작업하는 경우 Visual Studio Code를 사용하는 것이 좋습니다. 여기에 나오는 단계는 대부분 사용법과 일치합니다.

  3. 편집기에서 appsettings.json 파일을 선택하고 다음 텍스트를 추가합니다. 연결 문자열을 설정의 에 붙여넣습니다.

    {
      "CacheConnection": "[value-goes-here]"
    }
    
  4. 변경 내용을 저장합니다.

    Important

    편집기에서 파일에 코드를 붙여넣거나 코드를 변경할 때마다 ... 메뉴 또는 바로 가기 키(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. 파일을 저장합니다.

    Important

    파일을 저장하지 않으면 나중에 패키지를 추가할 때 변경 내용이 손실됩니다.

JSON 구성 파일을 읽도록 지원 추가

.NET Core 애플리케이션에서 JSON 구성 파일을 읽으려면 다른 NuGet 패키지가 필요합니다.

창의 명령 프롬프트 섹션에서 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.csMain 메서드 끝부분에서 새 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 메서드에만 사용할 예정이지만, 프로덕션 애플리케이션에서는 클래스 필드 또는 싱글톤에 저장해야 합니다.

캐시에 값 추가

연결이 설정되었으니, 캐시에 값을 추가하겠습니다.

  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 키워드를 사용하면 이러한 메서드를 좀 더 쉽게 작업할 수 있습니다. 통합 Cloud Shell 대신 고유한 .NET Core 개발 환경을 사용하는 경우 Main 메서드에 이러한 키워드를 적용하려면 C# 7.1 이상을 사용해야 합니다.

비동기 키워드 적용

Main 메서드에 async 키워드를 적용합니다. 두 가지 작업을 수행해야 합니다.

  1. Main 메서드 서명에 async 키워드를 추가합니다.

  2. 반환 형식을 void에서 Task로 변경합니다.

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

비동기적으로 값을 가져오고 설정

동기 메서드는 그대로 유지할 수 있습니다. StringSetAsyncStringGetAsync 메서드 호출을 추가하여 캐시에 다른 값을 추가해 보겠습니다. 카운터 값을 100으로 설정합니다.

  1. StringSetAsyncStringGetAsync 메서드를 사용하여 카운터라는 키를 설정하고 검색합니다. 값을 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을 전달하여 카운터에 추가합니다.

    • 이 메서드는 and 키와 long 또는 double 키를 사용합니다.

    • 전달된 매개 변수에 따라 long 또는 double을 반환합니다.

  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를 만듭니다. 자신만의 클래스를 만들어도 되고(인기 있는 클래스는 "사람" 또는 "자동차"), 이전 단원에 제공된 "GameStats" 예제를 사용해도 됩니다.

  2. dotnet add package를 사용하여 Newtonsoft.Json NuGet 패키지에 대한 지원을 추가합니다.

  3. Newtonsoft.Json 네임스페이스에 대한 using을 추가합니다.

  4. 개체 중 하나를 만듭니다.

  5. JsonConvert.SerializeObject를 사용하여 개체를 직렬화하고 StringSetAsync를 사용하여 캐시로 푸시합니다.

  6. StringGetAsync를 사용하여 캐시에서 다시 가져온 다음, JsonConvert.DeserializeObject<T>를 사용하여 역직렬화합니다.

이제 Azure에서 Redis Cache를 만들었으므로 이를 사용할 애플리케이션을 만들어 보겠습니다. Azure Portal에서 연결 정보가 있는지 확인합니다.

참고

통합 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: Redis 연결 정보를 저장하는 .env 파일에서 환경 변수를 로드합니다.

    지금 설치해보겠습니다. 이 명령을 실행하여 앱에 추가합니다.

    npm install redis bluebird dotenv
    

구성 추가

Azure Portal에서 가져온 연결 정보응 .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