บทความนี้จะกล่าวถึงพื้นฐาน Azure Redis Cache และการใช้งานโดยใช้ .NET Core Web API
วาระการประชุม
- บทนำ
- แคชคืออะไร
- ประเภทของแคช
- เรดดิสแคช
- การตั้งค่าแคช Azure Redis
- การใช้งานทีละขั้นตอน
ข้อกำหนดเบื้องต้น ป>
- วิชวลสตูดิโอ 2022
- บัญชี Azure
- .NET Core 6
บทนำ
การแคชเป็นที่นิยมอย่างมากในปัจจุบันในอุตสาหกรรมซอฟต์แวร์ เนื่องจากจะช่วยปรับปรุงประสิทธิภาพและความสามารถในการปรับขนาดของแอปพลิเคชัน เราใช้เว็บแอปพลิเคชันมากมาย เช่น Gmail และ Facebook และดูว่าแอปพลิเคชันเหล่านี้ตอบสนองได้ดีเพียงใด และเราได้รับประสบการณ์การใช้งานที่ยอดเยี่ยม มีผู้ใช้อินเทอร์เน็ตจำนวนมาก และหากแอปพลิเคชันมีปริมาณการใช้งานและความต้องการเครือข่ายจำนวนมาก เราจำเป็นต้องดูแลหลายสิ่งหลายอย่างที่ช่วยเราปรับปรุงประสิทธิภาพและการตอบสนองของแอปพลิเคชัน ด้วยเหตุนี้ จึงมีวิธีแก้ปัญหาการแคช และนั่นเป็นเหตุผลว่าทำไมการแคชจึงเข้ามาอยู่ในภาพ
แคชคืออะไร
แคชคือที่เก็บข้อมูลหน่วยความจำที่ใช้ในการจัดเก็บข้อมูลที่เข้าถึงบ่อยลงในที่จัดเก็บข้อมูลชั่วคราว ซึ่งจะช่วยปรับปรุงประสิทธิภาพได้อย่างมาก และหลีกเลี่ยงการโดนฐานข้อมูลโดยไม่จำเป็นและจัดเก็บข้อมูลที่ใช้บ่อยไว้ในแคช


ดังที่คุณเห็นในภาพด้านบน มีสองสถานการณ์ สถานการณ์หนึ่งไม่ได้ใช้แคช และอีกสถานการณ์หนึ่งใช้แคช ดังนั้นเมื่อเราไม่ใช้แคช ในกรณีนี้ สมมติว่าผู้ใช้ต้องการข้อมูล จากนั้นพวกเขาจะเข้าถึงฐานข้อมูลแต่ละครั้ง และจะเพิ่มความซับซ้อนของเวลาและลดประสิทธิภาพในกรณีที่มีข้อมูลคงที่บางอย่างที่ผู้ใช้ต้องการและเป็นข้อมูลเดียวกันสำหรับผู้ใช้ทุกคน เมื่อเราไม่ใช้แคช แต่ละแคชจะเข้าสู่ฐานข้อมูลที่ไม่จำเป็นเพื่อดึงข้อมูล อีกด้านหนึ่ง คุณจะเห็นว่าเราใช้แคช และในกรณีนั้น หากมีข้อมูลคงที่และเหมือนกันสำหรับผู้ใช้ทุกคน จะมีเพียงผู้ใช้รายแรกเท่านั้นที่จะเข้าถึงฐานข้อมูลและดึงข้อมูลและจัดเก็บไว้ในหน่วยความจำแคช จากนั้นผู้ใช้อีกสองคนก็ใช้สิ่งนั้นจากแคชโดยไม่ต้องเข้าถึงฐานข้อมูลเพื่อดึงข้อมูลโดยไม่จำเป็น
ประเภทของแคช
โดยพื้นฐานแล้ว การแคชที่ .NET Core รองรับมีอยู่สองประเภท
- การแคชในหน่วยความจำ
- แคชแบบกระจาย
เมื่อเราใช้ In-Memory Cache ข้อมูลจะถูกจัดเก็บไว้ในหน่วยความจำของแอปพลิเคชันเซิร์ฟเวอร์ เมื่อใดก็ตามที่เราต้องการ เราจะดึงข้อมูลจากสิ่งนั้นและนำไปใช้ทุกที่ที่เราต้องการ และใน Distributed Caching มีกลไกของบุคคลที่สามมากมาย เช่น Redis และอื่นๆ อีกมากมาย แต่ในส่วนนี้ เราจะมาดูรายละเอียดเกี่ยวกับ Redis Cache และวิธีการทำงานใน .NET Core
แคชแบบกระจาย ป>

- โดยพื้นฐานแล้ว ข้อมูลแคชแบบกระจายจะถูกจัดเก็บและแบ่งปันระหว่างเซิร์ฟเวอร์หลายเครื่อง
- นอกจากนี้ ยังง่ายต่อการปรับปรุงความสามารถในการปรับขนาดและประสิทธิภาพของแอปพลิเคชันหลังจากจัดการโหลดระหว่างเซิร์ฟเวอร์หลายเครื่องเมื่อเราใช้แอปพลิเคชันที่มีผู้เช่าหลายราย
- สมมติว่าในอนาคต หากเซิร์ฟเวอร์ตัวใดตัวหนึ่งขัดข้องและรีสตาร์ท แอปพลิเคชันจะไม่ได้รับผลกระทบใดๆ เนื่องจากเซิร์ฟเวอร์หลายตัวเป็นไปตามความต้องการของเราหากเราต้องการ
Redis เป็นแคชที่ได้รับความนิยมมากที่สุดซึ่งหลายบริษัทใช้ในปัจจุบันเพื่อปรับปรุงประสิทธิภาพและความสามารถในการปรับขนาดของแอปพลิเคชัน ดังนั้น เราจะมาหารือเกี่ยวกับ Redis และการใช้งานของมันทีละรายการ
เรดดิสแคช ป>
- Redis คือที่จัดเก็บโครงสร้างข้อมูลในหน่วยความจำแบบโอเพนซอร์ส (BSD ที่ได้รับอนุญาต) ซึ่งใช้เป็นฐานข้อมูล
- โดยพื้นฐานแล้ว มันถูกใช้เพื่อจัดเก็บข้อมูลที่ใช้บ่อยและข้อมูลคงที่บางส่วนภายในแคช และใช้และสงวนไว้ตามความต้องการของผู้ใช้
- มีโครงสร้างข้อมูลมากมายใน Redis ที่เราสามารถใช้ได้ เช่น รายการ ตั้งค่า การแฮช สตรีม และอื่นๆ อีกมากมายเพื่อจัดเก็บข้อมูล
การตั้งค่าแคช Azure Redis
ขั้นตอนที่ 1 ป>
เข้าสู่ระบบพอร์ทัล Azure
ขั้นตอนที่ 2 ป>
ค้นหา Azure Cache สำหรับ Redis ในตลาดกลางแล้วเปิด

ขั้นตอนที่ 3 ป>
คลิกที่สร้างและระบุข้อมูลอื่นๆ




ขั้นตอนที่ 4 ป>
หลังจากนั้นไปที่ส่วนคีย์การเข้าถึงภายในแคชที่เราสร้างไว้ก่อนหน้านี้ และคัดลอกสตริงการเชื่อมต่อหลักที่เราต้องการภายใน .NET Core Web API

การใช้งานทีละขั้นตอน
ขั้นตอนที่ 1 ป>
เปิด Visual Studio และสร้างโปรเจ็กต์ .NET Core Web API ใหม่

ขั้นตอนที่ 2 ป>
กำหนดค่าโครงการใหม่
ขั้นตอนที่ 3 ป>
ระบุรายละเอียดเพิ่มเติม
ขั้นตอนที่ 4 ป>
โครงสร้างโครงการ
ขั้นตอนที่ 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 ป>
ถัดไป เพิ่ม Db Context Class ภายในโฟลเดอร์ Data
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 ป>
กำหนดค่าสตริงการเชื่อมต่อ Azure Redis Cache ภายในไฟล์ appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"RedisURL": "<valuefromportal>"
} ขั้นตอนที่ 9 ป>
สร้างคลาส Configuration Manager และ Connection Helper ภายในโฟลเดอร์ 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 ป>
ลงทะเบียนบริการบางอย่างภายในคลาสโปรแกรม
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 ป>
สุดท้าย ให้เรียกใช้แอปพลิเคชัน แล้วเราจะเห็น UI ที่ผยองพร้อมกับจุดสิ้นสุดของ API ของเรา
ขั้นตอนที่ 17 ป>
กดปุ่มรับตำแหน่งข้อมูลผลิตภัณฑ์แล้วเปิด Redis CLI ภายใน Azure Portal จากนั้นคุณจะเห็นรายการผลิตภัณฑ์จะถูกจัดเก็บเมื่อเราไปถึงตำแหน่งข้อมูลในครั้งแรก
ในกรณีนี้ อันดับแรก เราจะตรวจสอบว่าข้อมูลมีอยู่ในแคชหรือไม่ ถ้าไม่เช่นนั้นเราจะรับข้อมูลจากฐานข้อมูลและบันทึกลงในแคชด้วย เราได้เขียนโค้ดที่เกี่ยวข้องกับสิ่งนั้นภายในคอนโทรลเลอร์แล้ว ดังนั้นครั้งต่อไปที่เรารับข้อมูลจากแคช หากคุณใส่ดีบักเกอร์ไว้ในคอนโทรลเลอร์ คุณจะเข้าใจได้ง่ายว่าสิ่งต่างๆ จะทำงานอย่างไร
URL ของ GitHub ป>
https://github.com/Jaydeep-007/AzureRedisCacheDemo/tree/master/AzureRedisCacheDemo
บทสรุป
ที่นี่เรามาดูการแนะนำแคชและการกำหนดค่าบน Azure นอกจากนี้ การใช้งานทีละขั้นตอนโดยใช้ .NET Core Web API