Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

การรวม Redis Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

บทนำ

เราจะหารือเกี่ยวกับการแคชใน .NET Core และวิธีการทำงาน ดังนั้นเราจึงดูสิ่งต่อไปนี้ทีละรายการ

  • บทนำของการแคช
  • แคชคืออะไร
  • ประเภทของแคช
  • การใช้งานแคช

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

เรามาเริ่มกันทีละรายการ

การแคชคืออะไร

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

การรวม Redis Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

การรวม Redis Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

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

ประเภทของแคช

โดยพื้นฐานแล้ว การแคชที่ .NET Core รองรับมีอยู่สองประเภท

  1. การแคชในหน่วยความจำ
  2. แคชแบบกระจาย

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

แคชแบบกระจาย

การรวม Redis Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

  • โดยพื้นฐานแล้ว ในการแคชแบบกระจาย ข้อมูล 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 Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

การใช้งาน 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 Caching เข้ากับ .NET Core API เพื่อประสิทธิภาพและความสามารถในการปรับขนาด

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

ทั้งหมดนี้เกี่ยวกับ Redis Cache ใน .NET Core ฉันหวังว่าคุณจะเข้าใจสิ่งที่เกี่ยวข้องกับเรื่องนั้น

ขอให้มีความสุขกับการเขียนโค้ด!