Share via


자습서: Azure Functions 및 Azure Cache for Redis를 사용하여 write-behind 캐시 만들기

이 자습서의 목표는 Azure Cache for Redis 인스턴스를 쓰기 숨김 캐시로 사용하는 데 있습니다. 이 자습서의 쓰기 숨김 패턴은 캐시에 쓰기가 SQL 데이터베이스(Azure SQL Database 서비스의 인스턴스)에 해당하는 쓰기를 트리거하는 방법을 보여 줍니다.

Azure Functions용 Redis 트리거를 사용하여 이 기능을 구현합니다. 이 시나리오에서는 Azure Cache for Redis를 사용하여 인벤토리 및 가격 정보를 저장하는 동시에 해당 정보를 SQL 데이터베이스에 백업하는 방법을 알아봅니다.

캐시에 작성된 새 항목이나 새 가격은 모두 데이터베이스의 SQL 테이블에 반영됩니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • 데이터베이스, 트리거 및 연결 문자열을 구성합니다.
  • 트리거가 작동하는지 확인합니다.
  • 함수 앱에 코드를 배포합니다.

필수 구성 요소

새 SQL 데이터베이스를 만들고 구성합니다.

이 예제의 SQL 데이터베이스는 지원 데이터베이스입니다. Azure Portal을 통해 또는 원하는 자동화 방법을 통해 SQL 데이터베이스를 만들 수 있습니다.

SQL 데이터베이스를 만드는 방법에 대한 자세한 내용은 빠른 시작: 단일 데이터베이스 만들기 - Azure SQL Database를 참조하세요.

이 예제에서는 포털을 사용합니다.

  1. 데이터베이스 이름을 입력하고 새로 만들기를 선택하여 데이터베이스를 보관할 새 서버를 만듭니다.

    Azure SQL 리소스 만들기를 보여 주는 스크린샷.

  2. SQL 인증 사용을 선택하고 관리자 로그인 및 암호를 입력합니다. 이러한 자격 증명을 저장하거나 기록해 두어야 합니다. 프로덕션 환경에서 서버를 배포하는 경우 대신 Microsoft Entra 인증을 사용합니다.

    Azure SQL 리소스에 대한 인증 정보 스크린샷.

  3. 네트워킹 탭으로 이동하여 공용 엔드포인트를 연결 방법으로 선택합니다. 표시되는 두 방화벽 규칙에 대해 를 선택합니다. 이 엔드포인트를 사용하면 Azure 함수 앱에서 액세스할 수 있습니다.

    Azure SQL 리소스에 대한 네트워킹 설정의 스크린샷.

  4. 유효성 검사가 완료되면 검토 + 만들기를 선택한 다음 만들기를 선택합니다. SQL 데이터베이스에서 배포를 시작합니다.

  5. 배포가 완료되면 Azure Portal의 리소스로 이동하여 쿼리 편집기 탭을 선택합니다. 기록할 데이터를 보관하는 인벤토리라는 새 테이블을 만듭니다. 다음 SQL 명령을 사용하여 두 필드가 포함된 새 테이블을 만듭니다.

    • ItemName은 각 항목의 이름을 나열합니다.
    • Price는 항목의 가격을 저장합니다.
    CREATE TABLE inventory (
        ItemName varchar(255),
        Price decimal(18,2)
        );
    

    Azure SQL 리소스의 쿼리 편집기에서 테이블 만들기를 보여 주는 스크린샷.

  6. 명령이 실행을 완료하면 Tables 폴더를 확장하여 새 테이블이 만들어졌는지 확인합니다.

Redis 트리거 구성

먼저 이전 자습서에서 사용한 것과 동일한 VS Code 프로젝트의 복사본을 만듭니다. 이전 자습서의 폴더를 RedisWriteBehindTrigger와 같은 새 이름으로 복사하고 VS Code에서 엽니다.

둘째, RedisBindings.csRedisTriggers.cs 파일을 삭제합니다.

이 예제에서는 pub/sub 트리거를 사용하여 keyevent 알림을 트리거합니다. 이 예제의 목표는 다음과 같습니다.

  • SET 이벤트가 발생할 때마다 트리거됩니다. SET 이벤트는 새 키가 캐시 인스턴스에 기록되거나 키 값이 변경될 때 발생합니다.
  • SET 이벤트가 트리거되면 캐시 인스턴스에 액세스하여 새 키 값을 찾습니다.
  • SQL 데이터베이스의 인벤토리 테이블에 키가 이미 있는지 확인합니다.
    • 그렇다면 해당 키 값을 업데이트합니다.
    • 그렇지 않다면 키와 해당 값을 사용하여 새 행을 작성합니다.

트리거를 구성하려면 다음을 수행합니다.

  1. System.Data.SqlClient NuGet 패키지를 가져와서 SQL 데이터베이스와의 통신을 사용합니다. VS Code 터미널로 이동하여 다음 명령을 사용합니다.

      dotnet add package System.Data.SqlClient
    
  2. RedisFunction.cs라는 새 파일을 만듭니다. RedisBindings.csRedisTriggers.cs 파일을 삭제했는지 확인합니다.

  3. 다음 코드를 복사한 후 RedisFunction.cs에 붙여넣어 기존 코드를 바꿉니다.

using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Redis;
using System.Data.SqlClient;

public class WriteBehindDemo
{
    private readonly ILogger<WriteBehindDemo> logger;

    public WriteBehindDemo(ILogger<WriteBehindDemo> logger)
    {
        this.logger = logger;
    }
    
    public string SQLAddress = System.Environment.GetEnvironmentVariable("SQLConnectionString");

    //This example uses the PubSub trigger to listen to key events on the 'set' operation. A Redis Input binding is used to get the value of the key being set.
    [Function("WriteBehind")]
    public void WriteBehind(
        [RedisPubSubTrigger(Common.connectionString, "__keyevent@0__:set")] Common.ChannelMessage channelMessage,
        [RedisInput(Common.connectionString, "GET {Message}")] string setValue)
    {
        var key = channelMessage.Message; //The name of the key that was set
        var value = 0.0;

        //Check if the value is a number. If not, log an error and return.
        if (double.TryParse(setValue, out double result))
        {
            value = result; //The value that was set. (i.e. the price.)
            logger.LogInformation($"Key '{channelMessage.Message}' was set to value '{value}'");
        }
        else
        {
            logger.LogInformation($"Invalid input for key '{key}'. A number is expected.");
            return;
        }        

        // Define the name of the table you created and the column names.
        String tableName = "dbo.inventory";
        String column1Value = "ItemName";
        String column2Value = "Price";        
        
        logger.LogInformation($" '{SQLAddress}'");
        using (SqlConnection connection = new SqlConnection(SQLAddress))
            {
                connection.Open();
                using (SqlCommand command = new SqlCommand())
                {
                    command.Connection = connection;

                    //Form the SQL query to update the database. In practice, you would want to use a parameterized query to prevent SQL injection attacks.
                    //An example query would be something like "UPDATE dbo.inventory SET Price = 1.75 WHERE ItemName = 'Apple'".
                    command.CommandText = "UPDATE " + tableName + " SET " + column2Value + " = " + value + " WHERE " + column1Value + " = '" + key + "'";
                    int rowsAffected = command.ExecuteNonQuery(); //The query execution returns the number of rows affected by the query. If the key doesn't exist, it will return 0.

                    if (rowsAffected == 0) //If key doesn't exist, add it to the database
                 {
                         //Form the SQL query to update the database. In practice, you would want to use a parameterized query to prevent SQL injection attacks.
                         //An example query would be something like "INSERT INTO dbo.inventory (ItemName, Price) VALUES ('Bread', '2.55')".
                        command.CommandText = "INSERT INTO " + tableName + " (" + column1Value + ", " + column2Value + ") VALUES ('" + key + "', '" + value + "')";
                        command.ExecuteNonQuery();

                        logger.LogInformation($"Item " + key + " has been added to the database with price " + value + "");
                    }

                    else {
                        logger.LogInformation($"Item " + key + " has been updated to price " + value + "");
                    }
                }
                connection.Close();
            }

            //Log the time that the function was executed.
            logger.LogInformation($"C# Redis trigger function executed at: {DateTime.Now}");
    }
}

Important

이 예제는 자습서용으로 간소화되었습니다. 프로덕션용으로는 매개 변수화된 SQL 쿼리를 사용하여 SQL 삽입 공격을 방지하는 것이 좋습니다.

연결 문자열 구성

SQL 데이터베이스에 대한 연결 문자열을 포함하려면 local.settings.json 파일을 업데이트해야 합니다. SQLConnectionStringValues 섹션에 항목을 추가합니다. 파일은 다음 예제와 비슷합니다.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "redisConnectionString": "<redis-connection-string>",
    "SQLConnectionString": "<sql-connection-string>"
  }
}

Redis 연결 문자열을 찾으려면 Azure Cache for Redis 리소스의 리소스 메뉴로 이동합니다. 문자열은 설정액세스 키 영역에 있습니다.

SQL 데이터베이스 연결 문자열을 찾으려면 SQL 데이터베이스 리소스의 리소스 메뉴로 이동합니다. 설정에서 연결 문자열을 선택한 다음 ADO.NET 탭을 선택합니다. 문자열은 ADO.NET(SQL 인증) 영역에 있습니다.

암호는 자동으로 붙여넣어지지 않기 때문에 SQL 데이터베이스 연결 문자열의 암호를 수동으로 입력해야 합니다.

Important

이 예제는 자습서용으로 간소화되었습니다. 프로덕션 용도의 경우 Azure Key Vault를 사용하여 연결 문자열 정보를 저장하거나 SQL 인증에 Azure EntraID를 사용하는 것이 좋습니다.

프로젝트 빌드 및 실행

  1. VS Code에서 실행 및 디버그 탭으로 이동하여 프로젝트를 실행합니다.

  2. Azure Portal에서 Azure Cache for Redis 인스턴스로 돌아가서 콘솔 단추를 선택해 Redis 콘솔을 입력합니다. 다음의 몇 가지 SET 명령을 사용해 보세요.

    • SET apple 5.25
    • SET bread 2.25
    • SET apple 4.50
  3. VS Code로 돌아가면 트리거가 등록됩니다. 트리거가 작동하는지 확인하려면 다음을 수행합니다.

    1. Azure Portal에서 SQL 데이터베이스로 이동합니다.

    2. 리소스 메뉴에서 쿼리 편집기를 선택합니다.

    3. 새 쿼리의 경우 다음 SQL 명령으로 쿼리를 만들어 인벤토리 테이블에서 상위 100개 항목을 확인합니다.

      SELECT TOP (100) * FROM [dbo].[inventory]
      

      Azure Cache for Redis 인스턴스에 기록된 항목이 여기에 표시되는지 확인합니다.

    캐시 인스턴스에서 SQL로 복사된 정보를 보여 주는 스크린샷.

함수 앱에 코드 배포

이 자습서는 이전 자습서를 기반으로 합니다. 자세한 내용은 Azure 함수에 코드 배포를 참조하세요.

  1. VS Code에서 Azure 탭으로 이동합니다.

  2. 구독을 찾아 확장합니다. 그런 다음 함수 앱 섹션을 찾아 확장합니다.

  3. 함수 앱을 길게 누르거나 마우스 오른쪽 단추로 클릭한 다음 함수 앱에 배포를 선택합니다.

연결 문자열 정보 추가

이 자습서는 이전 자습서를 기반으로 합니다. redisConnectionString에 대한 자세한 내용은 연결 문자열 정보 추가를 참조하세요.

  1. Azure Portal에서 함수 앱으로 이동합니다. 리소스 메뉴에서 환경 변수를 선택합니다.

  2. 앱 설정 창에서 새 필드로 SQLConnectionString을 입력합니다. 에 연결 문자열을 입력합니다.

  3. 적용을 선택합니다.

  4. 개요 블레이드로 이동하고 다시 시작을 선택하여 새 연결 문자열 정보로 앱을 다시 시작합니다.

배포 확인

배포가 완료되면 Azure Cache for Redis 인스턴스로 돌아가서 SET 명령을 사용해 더 많은 값을 작성합니다. SQL 데이터베이스에도 표시되는지 확인합니다.

함수 앱이 제대로 작동하는지 확인하려면 포털의 앱으로 이동하여 리소스 메뉴에서 로그 스트림을 선택합니다. 여기에서 실행 중인 트리거와 SQL 데이터베이스에 대해 수행되는 해당 업데이트가 표시됩니다.

삭제하지 않고 SQL 데이터베이스 테이블을 지우려면 다음 SQL 쿼리를 사용하면 됩니다.

TRUNCATE TABLE [dbo].[inventory]

리소스 정리

이 문서에서 만든 리소스를 계속 사용하려면 리소스 그룹을 유지합니다.

그렇지 않고 리소스 사용을 완료하는 경우 요금이 부과되지 않도록 하려면 만든 Azure 리소스 그룹을 삭제하면 됩니다.

Important

리소스 그룹을 삭제하면 다시 되돌릴 수 없습니다. 리소스 그룹을 삭제하는 경우 그 안의 모든 리소스가 영구적으로 삭제됩니다. 잘못된 리소스 그룹 또는 리소스를 자동으로 삭제하지 않도록 해야 합니다. 유지하려는 리소스가 포함된 기존 리소스 그룹 내에서 리소스를 만든 경우 리소스 그룹을 삭제하는 대신 각 리소스를 개별적으로 삭제할 수 있습니다.

리소스 그룹을 삭제하려면

  1. Azure Portal에 로그인한 다음, 리소스 그룹을 선택합니다.

  2. 삭제하려는 리소스 그룹을 선택합니다.

    리소스 그룹이 많은 경우 필드 필터링... 상자를 사용하여 이 문서에 대해 만든 리소스 그룹의 이름을 입력합니다. 결과 목록에서 리소스 그룹을 선택합니다.

    작업 창에서 삭제할 리소스 그룹 목록을 보여 주는 스크린샷.

  3. 리소스 그룹 삭제를 선택합니다.

  4. 리소스 그룹 삭제를 확인하는 메시지가 표시됩니다. 리소스 그룹의 이름을 입력하여 확인한 다음, 삭제를 선택합니다.

    삭제를 확인하기 위해 리소스 이름이 필요한 양식을 보여 주는 스크린샷.

잠시 후, 리소스 그룹 및 모든 해당 리소스가 삭제됩니다.

요약

이 자습서와 Azure Cache for Redis에서 Azure Functions 트리거 시작에서는 Azure Cache for Redis를 사용하여 Azure 함수 앱을 트리거하는 방법을 보여 줍니다. 또한 Azure SQL Database에서 Azure Cache for Redis를 쓰기 숨김 캐시로 사용하는 방법도 보여 줍니다. Azure Functions와 함께 Azure Cache for Redis를 사용하는 것은 여러 가지 통합/성능 문제를 해결할 수 있는 강력한 조합입니다.