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


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

- โดยพื้นฐานแล้ว ในการแคชแบบกระจาย ข้อมูล g จะถูกจัดเก็บและแบ่งปันระหว่างเซิร์ฟเวอร์หลายเครื่อง
- นอกจากนี้ ยังง่ายต่อการปรับปรุงความสามารถในการปรับขนาดและประสิทธิภาพของแอปพลิเคชันหลังจากจัดการโหลดระหว่างเซิร์ฟเวอร์หลายเครื่องเมื่อเราใช้แอปพลิเคชันที่มีผู้เช่าหลายราย
- สมมติว่า ในอนาคต หากเซิร์ฟเวอร์ตัวใดตัวหนึ่งขัดข้องและรีสตาร์ท แอปพลิเคชันจะไม่ได้รับผลกระทบใดๆ เนื่องจากเซิร์ฟเวอร์หลายตัวเป็นไปตามความต้องการของเราหากเราต้องการ
Redis เป็นแคชที่ได้รับความนิยมมากที่สุด ซึ่งหลายบริษัทใช้ในปัจจุบันเพื่อปรับปรุงประสิทธิภาพและความสามารถในการปรับขนาดของแอปพลิเคชัน ดังนั้น เราจะมาหารือเกี่ยวกับ Redis และการใช้งานของมันทีละรายการ
เรดดิสแคช
- Redis คือที่จัดเก็บโครงสร้างข้อมูลในหน่วยความจำแบบโอเพนซอร์ส (BSD ที่ได้รับอนุญาต) ซึ่งใช้เป็นฐานข้อมูล
- โดยพื้นฐานแล้ว มันถูกใช้เพื่อจัดเก็บข้อมูลที่ใช้บ่อยและข้อมูลคงที่บางส่วนภายในแคช และใช้และสงวนไว้ตามความต้องการของผู้ใช้
- มีโครงสร้างข้อมูลมากมายใน Redis ที่เราสามารถใช้ได้ เช่น รายการ, ตั้งค่า, แฮชชิ่ง, สตรีม และอื่นๆ อีกมากมายเพื่อจัดเก็บข้อมูล
การติดตั้งแคช Redis
ขั้นตอนที่ 1 ดาวน์โหลดเซิร์ฟเวอร์ Redis โดยใช้ URL ต่อไปนี้
https://github.com/microsoftarchive/redis/releases/tag/win-3.0.504
ขั้นตอนที่ 2 แตกไฟล์ zip แล้วเปิด Redis Server และ Redis CLI ในภายหลัง

การใช้งาน Redis Cache โดยใช้ .NET Core API
ขั้นตอนที่ 1 สร้างเว็บแอปพลิเคชัน .NET Core API
ขั้นตอนที่ 2 ติดตั้งแพ็คเกจ NuGet ต่อไปนี้ ซึ่งจำเป็นต้องมีทีละขั้นตอนในแอปพลิเคชันของเรา
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Swashbuckle.AspNetCore
- StackExchange.Redis
ขั้นตอนที่ 3 สร้างโฟลเดอร์ Model และสร้างคลาสผลิตภัณฑ์หนึ่งคลาสภายในนั้นพร้อมรายละเอียด
namespace RedisCacheDemo.Model
{
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
public int Stock { get; set; }
}
}
ขั้นตอนที่ 4 ถัดไป สร้างคลาส DbContextClass สำหรับการดำเนินการฐานข้อมูล ตามที่ฉันแสดงด้านล่าง
using Microsoft.EntityFrameworkCore;
using RedisCacheDemo.Model;
namespace RedisCacheDemo.Data {
public class DbContextClass: DbContext {
public DbContextClass(DbContextOptions < DbContextClass > options): base(options) {}
public DbSet < Product > Products {
get;
set;
}
}
} ขั้นตอนที่ 5 ตอนนี้ เราจะสร้างอินเทอร์เฟซ ICacheService และคลาส CacheService สำหรับการใช้งานที่เกี่ยวข้องกับแคช Redis
using System;
namespace RedisCacheDemo.Cache
{
public interface ICacheService
{
/// <summary>
/// Get Data using key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T GetData<T>(string key);
/// <summary>
/// Set Data with Value and Expiration Time of Key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expirationTime"></param>
/// <returns></returns>
bool SetData<T>(string key, T value, DateTimeOffset expirationTime);
/// <summary>
/// Remove Data
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
object RemoveData(string key);
}
}
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
namespace RedisCacheDemo.Cache {
public class CacheService: ICacheService {
private IDatabase _db;
public CacheService() {
ConfigureRedis();
}
private void ConfigureRedis() {
_db = ConnectionHelper.Connection.GetDatabase();
}
public T GetData < T > (string key) {
var value = _db.StringGet(key);
if (!string.IsNullOrEmpty(value)) {
return JsonConvert.DeserializeObject < T > (value);
}
return default;
}
public bool SetData < 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;
}
public object RemoveData(string key) {
bool _isKeyExist = _db.KeyExists(key);
if (_isKeyExist == true) {
return _db.KeyDelete(key);
}
return false;
}
}
} ขั้นตอนที่ 6 สร้างคลาส ProductController และสร้างวิธีการต่อไปนี้ดังที่แสดงด้านล่าง
using Microsoft.AspNetCore.Mvc;
using RedisCacheDemo.Cache;
using RedisCacheDemo.Data;
using RedisCacheDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RedisCacheDemo.Controllers {
[Route("api/[controller]")]
[ApiController]
public class ProductController: ControllerBase {
private readonly DbContextClass _dbContext;
private readonly ICacheService _cacheService;
public ProductController(DbContextClass dbContext, ICacheService cacheService) {
_dbContext = dbContext;
_cacheService = cacheService;
}
[HttpGet("products")]
public IEnumerable < Product > Get() {
var cacheData = _cacheService.GetData < IEnumerable < Product >> ("product");
if (cacheData != null) {
return cacheData;
}
var expirationTime = DateTimeOffset.Now.AddMinutes(5.0);
cacheData = _dbContext.Products.ToList();
_cacheService.SetData < IEnumerable < Product >> ("product", cacheData, expirationTime);
return cacheData;
}
[HttpGet("product")]
public Product Get(int id) {
Product filteredData;
var cacheData = _cacheService.GetData < IEnumerable < Product >> ("product");
if (cacheData != null) {
filteredData = cacheData.Where(x => x.ProductId == id).FirstOrDefault();
return filteredData;
}
filteredData = _dbContext.Products.Where(x => x.ProductId == id).FirstOrDefault();
return filteredData;
}
[HttpPost("addproduct")]
public async Task < Product > Post(Product value) {
var obj = await _dbContext.Products.AddAsync(value);
_cacheService.RemoveData("product");
_dbContext.SaveChanges();
return obj.Entity;
}
[HttpPut("updateproduct")]
public void Put(Product product) {
_dbContext.Products.Update(product);
_cacheService.RemoveData("product");
_dbContext.SaveChanges();
}
[HttpDelete("deleteproduct")]
public void Delete(int Id) {
var filteredData = _dbContext.Products.Where(x => x.ProductId == Id).FirstOrDefault();
_dbContext.Remove(filteredData);
_cacheService.RemoveData("product");
_dbContext.SaveChanges();
}
}
} ขั้นตอนที่ 7 เพิ่มสตริงการเชื่อมต่อ SQL Server และ Redis URL ภายใน appsetting.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"RedisURL": "127.0.0.1:6379",
"ConnectionStrings": {
"DefaultConnection": "Data Source=Server;Initial Catalog=RedisCache;User Id=sa;Password=***;"
}
} ขั้นตอนที่ 8 จากนั้น ลงทะเบียน ICacheService ภายในวิธี Configure Service ของ Startup Class และยังเพิ่มการกำหนดค่าบางอย่างที่เกี่ยวข้องกับ Swagger เพื่อทดสอบตำแหน่งข้อมูล API ของเรา
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using RedisCacheDemo.Cache;
using RedisCacheDemo.Data;
namespace RedisCacheDemo {
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration {
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddControllers();
services.AddScoped < ICacheService, CacheService > ();
services.AddDbContext < DbContextClass > (options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "RedisCacheDemo", Version = "v1"
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "RedisCacheDemo v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
} ขั้นตอนที่ 9 สร้างคลาส ConfigurationManger หนึ่งคลาสเพื่อกำหนดการตั้งค่าแอปตรงนั้น
using Microsoft.Extensions.Configuration;
using System.IO;
namespace RedisCacheDemo {
static class ConfigurationManager {
public static IConfiguration AppSetting {
get;
}
static ConfigurationManager() {
AppSetting = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
}
}
} ขั้นตอนที่ 10 ถัดไป สร้างคลาสตัวช่วยการเชื่อมต่อสำหรับการเชื่อมต่อ Redis
using StackExchange.Redis;
using System;
namespace RedisCacheDemo.Cache {
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;
}
}
}
} ขั้นตอนที่ 11 ดำเนินการย้ายและอัปเดตฐานข้อมูลสำหรับการสร้าง DB โดยใช้คำสั่งต่อไปนี้ใน Package Manager Console
add-migration “FirstMigration”
update-database ดังนั้น เมื่อคุณป้อนและดำเนินการคำสั่งนี้ มันจะสร้างบางสิ่งที่เกี่ยวข้องกับการย้ายและสร้างฐานข้อมูลภายใน SQL Server เมื่อคุณใส่ไว้ใน Connection String ใน appsetting.json
ขั้นตอนที่ 12 สุดท้าย ให้เรียกใช้แอปพลิเคชันและเพิ่มข้อมูลโดยใช้ Swagger UI จากนั้นตรวจสอบว่าแคชทำงานอย่างไรภายในผลิตภัณฑ์และจุดสิ้นสุดของผลิตภัณฑ์
โดยพื้นฐานแล้ว ฉันเพิ่มแคชลงในผลิตภัณฑ์และผลิตภัณฑ์ปลายทางในคอนโทรลเลอร์ อย่างที่คุณเห็นเมื่อผู้ใช้ต้องการดึงข้อมูลผลิตภัณฑ์ทั้งหมด จากนั้นก่อนอื่นมันจะตรวจสอบว่ามีข้อมูลอยู่ใน Redis Cache หรือไม่ และหากมีอยู่ในแคชก็ส่งคืนข้อมูลนั้นให้กับผู้ใช้ และหากข้อมูลไม่อยู่ในแคชก็จะดึงข้อมูลจากฐานข้อมูลและตั้งค่านั้นลงในแคชด้วย ดังนั้นในครั้งต่อไปผู้ใช้จะได้รับสิ่งนั้นจากแคชเท่านั้นและหลีกเลี่ยงการกดปุ่มฐานข้อมูลโดยไม่จำเป็น
นอกจากนี้ เมื่อผู้ใช้ต้องการดึงข้อมูลโดยใช้รหัสผลิตภัณฑ์ ดังที่คุณเห็นในตัวควบคุมในจุดสิ้นสุดที่สองของผลิตภัณฑ์ เราจะดึงข้อมูลจากแคชของผลิตภัณฑ์ทั้งหมด แล้วกรองโดยใช้รหัสผลิตภัณฑ์ หากมีอยู่ เราจะส่งคืนผู้ใช้จากแคช ถ้าไม่เช่นนั้น เราจะดึงข้อมูลจากฐานข้อมูลและส่งคืนให้กับผู้ใช้หลังจากใช้ตัวกรอง
ดังที่คุณเห็นภายในการอัปเดต ลบ และโพสต์ตำแหน่งข้อมูลของตัวควบคุมผลิตภัณฑ์ เราจะใช้วิธีการลบเพื่อลบข้อมูลหมายเลขผลิตภัณฑ์ออกจากแคช มีหลายสถานการณ์และการใช้แคชหน่วยความจำที่คุณสามารถใช้ได้ตามความต้องการและข้อกำหนดของคุณ ฉันแค่อยากจะแนะนำพื้นฐานของ Redis Cache และวิธีการทำงานภายใน .NET Core ที่ฉันกล่าวถึงที่นี่
นอกจากนี้ยังมีสถานการณ์หนึ่งที่คุณต้องดูแลขณะใช้แคช สมมติว่าผู้ใช้สองคนกำลังใช้แอปพลิเคชันของคุณ จากนั้นสถานการณ์ต่อไปนี้จะเกิดขึ้น
- เมื่อผู้ใช้รายแรกส่งคำขอเพื่อดึงข้อมูลของผลิตภัณฑ์ทั้งหมด คำขอแรกจะเกิดขึ้น จากนั้นจะตรวจสอบว่ามีข้อมูลอยู่ในแคชหรือไม่ หากมีข้อมูลอยู่ภายในแคช มันจะดึงข้อมูลจากฐานข้อมูลและตั้งค่าเป็นแคชด้วย
- ในขณะเดียวกัน ผู้ใช้คนที่สองส่งคำขอเพื่อรับรายละเอียดผลิตภัณฑ์ สิ่งที่เกิดขึ้นคือคำขอยังเข้าถึงฐานข้อมูลก่อนที่จะดำเนินการตามคำขอของผู้ใช้คนแรก และเนื่องจากผู้ใช้คนที่สองนั้นยังเข้าถึงฐานข้อมูลเพื่อดึงรายละเอียดผลิตภัณฑ์
- ดังนั้นจึงมีวิธีแก้ปัญหาหนึ่งสำหรับการใช้กลไกการล็อคดังที่แสดงด้านล่าง
สร้างอ็อบเจ็กต์ส่วนตัวของการล็อคที่ด้านบนของคลาส
private static object _lock = new object() ถัดไป แก้ไขวิธีการ Get ตามที่ฉันแสดงด้านล่าง
public IEnumerable < Product > Get() {
var cacheData = _cacheService.GetData < IEnumerable < Product >> ("product");
if (cacheData != null) {
return cacheData;
}
lock(_lock) {
var expirationTime = DateTimeOffset.Now.AddMinutes(5.0);
cacheData = _dbContext.Products.ToList();
_cacheService.SetData < IEnumerable < Product >> ("product", cacheData, expirationTime);
}
return cacheData;
} อย่างที่คุณเห็น อันดับแรกเราจะตรวจสอบว่ามีข้อมูลอยู่ในแคชหรือไม่ หากมีข้อมูลให้ส่งคืน ถัดไป หากไม่มีค่าอยู่ในแคช Redis เราจะใช้การล็อคตรงนั้น จากนั้นคำขอจะถูกล็อคและป้อนลงในส่วนดึงรายละเอียดผลิตภัณฑ์จากฐานข้อมูล จากนั้นตั้งค่าเป็นแคชและส่งคืนข้อมูลด้วย แล้วจะเกิดอะไรขึ้นเมื่อผู้ใช้คนที่สองส่งคำขอก่อนที่คำขอของผู้ใช้จะเสร็จสมบูรณ์ ดังนั้น ในกรณีนั้น คำขอที่สองอยู่ในคิว และหลังจากเสร็จสิ้นคำขอของผู้ใช้คนแรก คำขอที่สองก็เข้ามาในรูปภาพ
นอกจากนี้ คุณยังสามารถดูรายละเอียดสำคัญซึ่งมีอยู่แล้วใน Redis โดยใช้ Redis CLI ดังที่แสดงด้านล่าง

คุณจะเห็นได้ว่ามีคำสั่งมากมายที่ให้ข้อมูลเกี่ยวกับคีย์ที่มีอยู่ใน Redis Cache แก่เรา
ทั้งหมดนี้เกี่ยวกับ Redis Cache ใน .NET Core ฉันหวังว่าคุณจะเข้าใจสิ่งที่เกี่ยวข้องกับเรื่องนั้น
ขอให้มีความสุขกับการเขียนโค้ด!