Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Bài viết thảo luận về cơ bản Azure Redis Cache và triển khai với .NET Core Web API.

Mục lục

  • Giới thiệu
  • Cache là gì?
  • Các loại Cache
  • Redis Cache
  • Thiết lập Azure Redis Cache
  • Triển khai từng bước

Điều kiện tiên quyết

  • Visual Studio 2022
  • Azure Account
  • .NET Core 6

Giới thiệu

Caching phổ biến trong ngành phần mềm vì cải thiện hiệu suất và scalability. Các ứng dụng web như Gmail, Facebook phản hồi nhanh nhờ caching. Với traffic lớn, caching là giải pháp bắt buộc.

Cache là gì?

Cache là bộ nhớ lưu trữ dữ liệu truy cập thường xuyên vào lưu trữ tạm thời, cải thiện hiệu suất đáng kể, tránh hit database không cần thiết.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Các loại Cache

.NET Core hỗ trợ 2 loại:

  1. In-Memory Caching: dữ liệu lưu trong bộ nhớ application server.
  2. Distributed Caching: dữ liệu lưu và chia sẻ giữa nhiều server (Redis, v.v.).

Distributed Caching

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

  • Dữ liệu lưu và chia sẻ giữa nhiều server.
  • Dễ dàng cải thiện scalability và performance khi quản lý tải giữa nhiều server (multi-tenant).
  • Nếu một server crash/restart, ứng dụng không bị ảnh hưởng.

Redis Cache

  • Redis là Open Source (BSD Licensed) in-memory Data Structure store, dùng như database.
  • Lưu trữ dữ liệu thường dùng, static data trong cache.
  • Hỗ trợ nhiều cấu trúc dữ liệu: List, Set, Hashing, Stream, v.v.

Thiết lập Azure Redis Cache

Bước 1 Đăng nhập Azure Portal.

Bước 2 Tìm "Azure Cache for Redis" trong Marketplace và mở.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Bước 3 Click Create và điền thông tin.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Bước 4 Vào section Access keys trong cache vừa tạo, copy Primary connection string để dùng trong .NET Core Web API.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Triển khai từng bước

Bước 1 Mở Visual Studio, tạo project .NET Core Web API mới.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Bước 2-4 Cấu hình project, chi tiết, cấu trúc project.

Bước 5 Tạo class ProductDetails:

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; }
    }
}

Bước 6 Thêm DbContextClass trong folder Data:

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

Bước 7 Thêm class SeedData để insert dữ liệu ban đầu:

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();
            }
        }
    }
}

Bước 8 Cấu hình Azure Redis Cache connection string trong appsettings.json:

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

Bước 9 Tạo ConfigurationManager và ConnectionHelper trong folder Helper:

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

// ConnectionHelper
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; } }
    }
}

Bước 10 Tạo IProductService trong Repositories:

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

Bước 11 Tạo ProductService triển khai interface:

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(e => e.Id == productId).FirstOrDefaultAsync(); }
        public async Task<bool> AddProductAsync(ProductDetails productDetails) { await dbContextClass.Products.AddAsync(productDetails); var result = await dbContextClass.SaveChangesAsync(); return result > 0; }
        public async Task<bool> UpdateProductAsync(ProductDetails productDetails) { var isProduct = ProductDetailsExists(productDetails.Id); if (isProduct) { dbContextClass.Products.Update(productDetails); var result = await dbContextClass.SaveChangesAsync(); return result > 0; } 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(); return result > 0; } return false; }
        private bool ProductDetailsExists(int productId) { return dbContextClass.Products.Any(e => e.Id == productId); }
    }
}

Bước 12 Tạo interface 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);
    }
}

Bước 13 Tạo class RedisCache triển khai interface:

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) 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;
        }
    }
}

Bước 14 Tạo ProductsController:

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; }

        [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(); }
        }

        [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();
        }

        [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();
        }

        [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();
        }

        [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();
        }
    }
}

Bước 15 Đăng ký service trong Program.cs:

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);
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddDbContext<DbContextClass>(o => o.UseInMemoryDatabase("RedisCacheDemo"));
builder.Services.AddScoped<IRedisCache, RedisCache>();
builder.Services.AddControllers();
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);
}
if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

Bước 16 Chạy ứng dụng, xem Swagger UI với API endpoints.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Bước 17 Gọi endpoint get product, mở Redis CLI trong Azure Portal → thấy product list được lưu khi gọi endpoint lần đầu.

Triển khai Azure Redis Cache với .NET 6 Web API: Hướng dẫn từng bước

Lần đầu check cache, nếu không có thì lấy từ DB và lưu cache. Lần sau lấy từ cache. Đặt breakpoint trong controller để hiểu luồng xử lý.

GitHub URL

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

Kết luận

Đã xem giới thiệu cache và cấu hình trên Azure. Cũng như triển khai từng bước với .NET Core Web API.