Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

이 문서에서는 Azure Redis Cache 기본 사항과 .NET Core Web API를 사용한 구현에 대해 설명합니다.

의제

  • 소개
  • 캐시란 무엇인가요?
  • 캐시 유형
  • 레디스 캐시
  • Azure Redis 캐시 설정
  • 단계별 구현

전제조건

  • 비주얼 스튜디오 2022
  • Azure 계정
  • .NET 코어 6

소개

캐싱은 애플리케이션의 성능과 확장성을 향상시키기 때문에 오늘날 소프트웨어 업계에서 매우 인기가 높습니다. 우리는 Gmail이나 Facebook과 같은 많은 웹 애플리케이션을 사용하고 있으며 반응 속도가 얼마나 좋은지 확인하고 있으며 훌륭한 사용자 경험을 갖고 있습니다. 인터넷을 사용하는 사용자가 많고, 애플리케이션에 엄청난 네트워크 트래픽과 수요가 있는 경우 애플리케이션의 성능과 응답성을 향상시키는 데 도움이 되는 많은 사항을 처리해야 합니다. 그렇기 때문에 캐싱의 해결책이 있고 이것이 바로 캐싱이 등장하는 이유입니다.

캐시란 무엇입니까?

캐시는 자주 액세스하는 데이터를 임시 저장소에 저장하는 데 사용되는 메모리 저장소로, 성능을 획기적으로 향상시키고 불필요한 데이터베이스 적중을 방지하며 자주 사용하는 데이터를 캐시에 저장합니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

위 이미지에서 볼 수 있듯이 두 가지 시나리오가 있습니다. 하나는 캐시를 사용하지 않고 다른 하나는 캐시를 사용하는 것입니다. 따라서 여기서 캐시를 사용하지 않는 경우 사용자가 데이터를 원한다고 가정하면 매번 데이터베이스에 도달하게 되며 사용자가 원하는 정적 데이터가 있고 모든 사용자에게 동일한 경우 시간 복잡성이 증가하고 성능이 저하됩니다. 캐시를 사용하지 않으면 각각 불필요한 데이터베이스에 접속하여 데이터를 가져옵니다. 반대편에. 캐시를 사용하는 것을 볼 수 있으며, 이 경우 모든 사용자에 대해 동일한 정적 및 동일한 데이터가 있는 경우 첫 번째 사용자만 데이터베이스에 접속하여 데이터를 가져와서 캐시 메모리에 저장하고 다른 두 사용자는 데이터를 가져오기 위해 불필요하게 데이터베이스를 사용하지 않고 캐시에서 이를 사용합니다.

캐시 유형

기본적으로 .NET Core가 지원하는 캐싱에는 두 가지 유형이 있습니다.

  1. 인메모리 캐싱
  2. 분산 캐싱

In-Memory Cache를 사용하면 데이터가 애플리케이션 서버 메모리에 저장됩니다. 필요할 때마다 거기에서 데이터를 가져와 필요할 때마다 사용합니다. 그리고 분산 캐싱에는 Redis 및 기타 여러 타사 메커니즘이 많이 있습니다. 하지만 이 섹션에서는 Redis Cache를 자세히 살펴보고 .NET Core에서 작동하는 방식을 살펴보겠습니다.

분산 캐싱

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

  • 기본적으로 분산 캐싱에서는 데이터가 여러 서버에 저장되고 공유됩니다.
  • 또한 멀티 테넌트 애플리케이션을 사용하면 여러 서버 간의 부하를 관리한 후 애플리케이션의 확장성과 성능을 쉽게 향상시킬 수 있습니다.
  • 미래에 하나의 서버가 충돌하고 다시 시작되면 우리가 원하는 경우 여러 서버가 우리의 필요에 따라 이루어지기 때문에 애플리케이션에 아무런 영향이 없다고 가정합니다.

Redis는 오늘날 많은 회사에서 애플리케이션의 성능과 확장성을 개선하기 위해 사용하는 가장 인기 있는 캐시입니다. 그래서 Redis와 그 사용법을 하나씩 살펴보도록 하겠습니다.

Redis 캐시

  • Redis는 데이터베이스로 사용되는 오픈 소스(BSD 라이선스) 인메모리 데이터 구조 저장소입니다.
  • 기본적으로 자주 사용되는 데이터와 일부 정적 데이터를 캐시 내부에 저장하고 사용자 요구 사항에 따라 사용하고 예약하는 데 사용됩니다.
  • Redis에는 List, Set, Hashing, Stream 등과 같이 데이터를 저장하는 데 사용할 수 있는 많은 데이터 구조가 있습니다.

Azure Redis 캐시 설정

1단계

Azure Portal에 로그인하세요.

2단계

마켓플레이스에서 Azure Cache for Redis를 검색하여 엽니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

3단계

만들기를 클릭하고 기타 정보를 제공하세요.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

4단계

그런 다음 이전에 만든 캐시 내부의 액세스 키 섹션으로 이동하여 .NET Core Web API 내부에 필요한 기본 연결 문자열을 복사합니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

단계적 구현

1단계

Visual Studio를 열고 새 .NET Core Web API 프로젝트를 만듭니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

2단계

새 프로젝트를 구성하세요.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

3단계

추가 세부정보를 제공하세요.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

4단계

프로젝트 구조.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

5단계

제품 세부정보 클래스를 만듭니다.

namespace AzureRedisCacheDemo.Models {
 public class ProductDetails {
 public int Id {
 get;
 set;
 }
 public string ProductName {
 get;
 set;
 }
 public string ProductDescription {
 get;
 set;
 }
 public int ProductPrice {
 get;
 set;
 }
 public int ProductStock {
 get;
 set;
 }
 }
}

6단계

다음으로 Data 폴더 안에 Db Context Class를 추가하세요.

using AzureRedisCacheDemo.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
namespace AzureRedisCacheDemo.Data {
 public class DbContextClass: DbContext {
 public DbContextClass(DbContextOptions < DbContextClass > options): base(options) {}
 public DbSet < ProductDetails > Products {
 get;
 set;
 }
 }
}

7단계

그런 다음 초기에 일부 데이터를 삽입하는 데 사용하는 Seed Data 클래스를 추가하세요.

using AzureRedisCacheDemo.Models;
using Microsoft.EntityFrameworkCore;
namespace AzureRedisCacheDemo.Data
{
 public class SeedData
 {
 public static void Initialize(IServiceProvider serviceProvider)
 {
 using (var context = new DbContextClass(
 serviceProvider.GetRequiredService<DbContextOptions<DbContextClass>>()))
 {
 if (context.Products.Any())
 {
 return;
 }
 context.Products.AddRange(
 new ProductDetails
 {
 Id = 1,
 ProductName = "IPhone",
 ProductDescription = "IPhone 14",
 ProductPrice = 120000,
 ProductStock = 100
 },
 new ProductDetails
 {
 Id = 2,
 ProductName = "Samsung TV",
 ProductDescription = "Smart TV",
 ProductPrice = 400000,
 ProductStock = 120
 });
 context.SaveChanges();
 }
 }
 }
}

8단계

appsettings.json 파일 내에서 Azure Redis Cache 연결 문자열을 구성합니다.

{
 "Logging": {
 "LogLevel": {
 "Default": "Information",
 "Microsoft.AspNetCore": "Warning"
 }
 },
 "AllowedHosts": "*",
 "RedisURL": "<valuefromportal>"
}

9단계

연결 목적으로 사용하는 Helper 폴더 내에 구성 관리자 및 연결 도우미 클래스를 만듭니다.

구성 관리자

namespace AzureRedisCacheDemo.Helper {
 static class ConfigurationManager {
 public static IConfiguration AppSetting {
 get;
 }
 static ConfigurationManager() {
 AppSetting = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
 }
 }
}

연결 도우미

using StackExchange.Redis;
namespace AzureRedisCacheDemo.Helper {
 public class ConnectionHelper {
 static ConnectionHelper() {
 ConnectionHelper.lazyConnection = new Lazy < ConnectionMultiplexer > (() => {
 return ConnectionMultiplexer.Connect(ConfigurationManager.AppSetting["RedisURL"]);
 });
 }
 private static Lazy < ConnectionMultiplexer > lazyConnection;
 public static ConnectionMultiplexer Connection {
 get {
 return lazyConnection.Value;
 }
 }
 }
}

10단계

다음으로 저장소 내부에 IProductService를 추가하세요.

using AzureRedisCacheDemo.Models;
namespace AzureRedisCacheDemo.Repositories {
 public interface IProductService {
 public Task < List < ProductDetails >> ProductListAsync();
 public Task < ProductDetails > GetProductDetailByIdAsync(int productId);
 public Task < bool > AddProductAsync(ProductDetails productDetails);
 public Task < bool > UpdateProductAsync(ProductDetails productDetails);
 public Task < bool > DeleteProductAsync(int productId);
 }
}

11단계

다음으로 ProductService 클래스를 생성하고 그 안에 IProductService 인터페이스를 구현합니다.

using AzureRedisCacheDemo.Data;
using AzureRedisCacheDemo.Models;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace AzureRedisCacheDemo.Repositories {
 public class ProductService: IProductService {
 private readonly DbContextClass dbContextClass;
 public ProductService(DbContextClass dbContextClass) {
 this.dbContextClass = dbContextClass;
 }
 public async Task < List < ProductDetails >> ProductListAsync() {
 return await dbContextClass.Products.ToListAsync();
 }
 public async Task < ProductDetails > GetProductDetailByIdAsync(int productId) {
 return await dbContextClass.Products.Where(ele => ele.Id == productId).FirstOrDefaultAsync();
 }
 public async Task < bool > AddProductAsync(ProductDetails productDetails) {
 await dbContextClass.Products.AddAsync(productDetails);
 var result = await dbContextClass.SaveChangesAsync();
 if (result > 0) {
 return true;
 } else {
 return false;
 }
 }
 public async Task < bool > UpdateProductAsync(ProductDetails productDetails) {
 var isProduct = ProductDetailsExists(productDetails.Id);
 if (isProduct) {
 dbContextClass.Products.Update(productDetails);
 var result = await dbContextClass.SaveChangesAsync();
 if (result > 0) {
 return true;
 } else {
 return false;
 }
 }
 return false;
 }
 public async Task < bool > DeleteProductAsync(int productId) {
 var findProductData = dbContextClass.Products.Where(_ => _.Id == productId).FirstOrDefault();
 if (findProductData != null) {
 dbContextClass.Products.Remove(findProductData);
 var result = await dbContextClass.SaveChangesAsync();
 if (result > 0) {
 return true;
 } else {
 return false;
 }
 }
 return false;
 }
 private bool ProductDetailsExists(int productId) {
 return dbContextClass.Products.Any(e => e.Id == productId);
 }
 }
}

12단계

IRedisCache 인터페이스를 생성합니다.

namespace AzureRedisCacheDemo.Repositories.AzureRedisCache {
 public interface IRedisCache {
 T GetCacheData < T > (string key);
 bool SetCacheData < T > (string key, T value, DateTimeOffset expirationTime);
 object RemoveData(string key);
 }
}

13단계

그런 다음 RedisCache 클래스를 생성하고 앞서 생성한 인터페이스 메소드를 내부에 구현하세요.

using AzureRedisCacheDemo.Helper;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace AzureRedisCacheDemo.Repositories.AzureRedisCache
{
 public class RedisCache : IRedisCache
 {
 private IDatabase _db;
 public RedisCache()
 {
 ConfigureRedis();
 }
 private void ConfigureRedis()
 {
 _db = ConnectionHelper.Connection.GetDatabase();
 }
 public T GetCacheData<T>(string key)
 {
 var value = _db.StringGet(key);
 if (!string.IsNullOrEmpty(value))
 {
 return JsonConvert.DeserializeObject<T>(value);
 }
 return default;
 }
 public object RemoveData(string key)
 {
 bool _isKeyExist = _db.KeyExists(key);
 if (_isKeyExist == true)
 {
 return _db.KeyDelete(key);
 }
 return false;
 }
 public bool SetCacheData<T>(string key, T value, DateTimeOffset expirationTime)
 {
 TimeSpan expiryTime = expirationTime.DateTime.Subtract(DateTime.Now);
 var isSet = _db.StringSet(key, JsonConvert.SerializeObject(value), expiryTime);
 return isSet;
 }
 }
}

14단계

새로운 제품 컨트롤러를 생성하세요.

using AzureRedisCacheDemo.Models;
using AzureRedisCacheDemo.Repositories;
using AzureRedisCacheDemo.Repositories.AzureRedisCache;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace AzureRedisCacheDemo.Controllers
{
 [Route("api/[controller]")]
 [ApiController]
 public class ProductsController : ControllerBase
 {
 private readonly IProductService _productService;
 private readonly IRedisCache _redisCache;
 public ProductsController(IProductService productService, IRedisCache redisCache)
 {
 _productService = productService;
 _redisCache = redisCache;
 }
 /// <summary>
 /// Product List
 /// </summary>
 /// <returns></returns>
 [HttpGet]
 public async Task<ActionResult<List<ProductDetails>>> ProductListAsync()
 {
 var cacheData = _redisCache.GetCacheData<List<ProductDetails>>("product");
 if (cacheData != null)
 {
 return new List<ProductDetails>(cacheData);
 }
 var productList = await _productService.ProductListAsync();
 if(productList != null)
 {
 var expirationTime = DateTimeOffset.Now.AddMinutes(5.0);
 _redisCache.SetCacheData<List<ProductDetails>>("product", productList, expirationTime);
 return Ok(productList);
 }
 else
 {
 return NoContent();
 }
 }
 /// <summary>
 /// Get Product By Id
 /// </summary>
 /// <param name="productId"></param>
 /// <returns></returns>
 [HttpGet("{productId}")]
 public async Task<ActionResult<ProductDetails>> GetProductDetailsByIdAsync(int productId)
 {
 var cacheData = _redisCache.GetCacheData<List<ProductDetails>>("product");
 if (cacheData != null)
 {
 ProductDetails filteredData = cacheData.Where(x => x.Id == productId).FirstOrDefault();
 return new ActionResult<ProductDetails>(filteredData);
 }
 var productDetails = await _productService.GetProductDetailByIdAsync(productId);
 if(productDetails != null)
 {
 return Ok(productDetails);
 }
 else
 {
 return NotFound();
 }
 }
 /// <summary>
 /// Add a new product
 /// </summary>
 /// <param name="productDetails"></param>
 /// <returns></returns>
 [HttpPost]
 public async Task<IActionResult> AddProductAsync(ProductDetails productDetails)
 {
 var isProductInserted = await _productService.AddProductAsync(productDetails);
 _redisCache.RemoveData("product");
 if (isProductInserted)
 {
 return Ok(isProductInserted);
 }
 else
 {
 return BadRequest();
 }
 }
 /// <summary>
 /// Update product details
 /// </summary>
 /// <param name="productDetails"></param>
 /// <returns></returns>
 [HttpPut]
 public async Task<IActionResult> UpdateProductAsync(ProductDetails productDetails)
 {
 var isProductUpdated = await _productService.UpdateProductAsync(productDetails);
 _redisCache.RemoveData("product");
 if (isProductUpdated)
 {
 return Ok(isProductUpdated);
 }
 else
 {
 return BadRequest();
 }
 }
 /// <summary>
 /// Delete product by id
 /// </summary>
 /// <param name="productId"></param>
 /// <returns></returns>
 [HttpDelete]
 public async Task<IActionResult> DeleteProductAsync(int productId)
 {
 var isProductDeleted = await _productService.DeleteProductAsync(productId);
 _redisCache.RemoveData("product");
 if (isProductDeleted)
 {
 return Ok(isProductDeleted);
 }
 else
 {
 return BadRequest();
 }
 }
 }
}

15단계

Program 클래스 내에 몇 가지 서비스를 등록하세요.

using AzureRedisCacheDemo.Data;
using AzureRedisCacheDemo.Models;
using AzureRedisCacheDemo.Repositories;
using AzureRedisCacheDemo.Repositories.AzureRedisCache;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using System;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddScoped < IProductService, ProductService > ();
builder.Services.AddDbContext < DbContextClass > (o => o.UseInMemoryDatabase("RedisCacheDemo"));
builder.Services.AddScoped < IRedisCache, RedisCache > ();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
using(var scope = app.Services.CreateScope()) {
 var services = scope.ServiceProvider;
 var context = services.GetRequiredService < DbContextClass > ();
 SeedData.Initialize(services);
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
 app.UseSwagger();
 app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

16단계

마지막으로 애플리케이션을 실행하면 API 엔드포인트가 포함된 Swagger UI를 볼 수 있습니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

17단계

제품 엔드포인트 가져오기를 누르고 Azure Portal 내에서 Redis CLI를 열면 처음 엔드포인트에 도달할 때 제품 목록이 저장되는 것을 볼 수 있습니다.

.NET 6 Web API를 사용하여 Azure Redis Cache 구현:단계별 가이드

이 경우 먼저 데이터가 캐시에 존재하는지 확인합니다. 그렇지 않은 경우 데이터베이스에서 데이터를 가져와 캐시에 저장합니다. 우리는 이미 컨트롤러 내부에 이와 관련된 코드를 작성했습니다. 따라서 다음번에는 캐시에서 데이터를 가져옵니다. 컨트롤러 내부에 디버거를 넣으면 어떻게 작동하는지 쉽게 이해할 수 있습니다.

GitHub URL

https://github.com/Jaydeep-007/AzureRedisCacheDemo/tree/master/AzureRedisCacheDemo

결론

여기에서는 Azure에서의 캐시 소개와 구성을 살펴보았습니다. 또한 .NET Core Web API를 사용한 단계별 구현입니다.