Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

はじめに

.NET Core のキャッシュとその仕組みについて説明します。したがって、以下のことを 1 つずつ見ていきます。

  • キャッシングの概要
  • キャッシュとは
  • キャッシュの種類
  • キャッシュの実装

キャッシュはアプリケーションのパフォーマンスとスケーラビリティを向上させるため、現在ソフトウェア業界で非常に人気があります。私たちは Gmail や Facebook などの多くのウェブ アプリケーションを使用しており、それらの応答性を確認しており、優れたユーザー エクスペリエンスを実現しています。インターネットを使用するユーザーが多く、アプリケーションのネットワーク トラフィックと需要が膨大な場合、アプリケーションのパフォーマンスと応答性を向上させるために、多くのことに対処する必要があります。したがって、キャッシングという解決策があり、それがキャッシングが登場する理由です。

それでは、一つずつ始めてみましょう。

キャッシュとは何ですか?

キャッシュは、頻繁にアクセスされるデータを一時ストレージに保存するために使用されるメモリ ストレージです。これにより、パフォーマンスが大幅に向上し、不要なデータベース ヒットが回避され、頻繁に使用されるデータが必要なときにいつでもバッファに保存されます。

Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

上の画像にあるように、2 つのシナリオがあり、1 つはキャッシュを使用しないシナリオ、もう 1 つはキャッシュを使用するシナリオです。ここで、キャッシュを使用しない場合、その場合、ユーザーがデータを必要とすると、ユーザーは毎回データベースにアクセスすることになり、ユーザーが必要とする静的データがある場合、時間の複雑さが増加し、パフォーマンスが低下します。これはすべてのユーザーで同じです。その場合、キャッシュを使用しないと、それぞれが不要なデータベースにアクセスしてデータを取得することになります。一方、ご覧のとおり、キャッシュを使用します。この場合、すべてのユーザーに同じ静的で同じデータがある場合、最初のユーザーのみがデータベースにアクセスしてデータをフェッチし、キャッシュ メモリに保存します。その後、他の 2 人のユーザーは不必要にデータベースにアクセスしてデータをフェッチすることなく、キャッシュからそのデータを使用します。

キャッシュの種類

基本的に、.NET Core がサポートするキャッシュには 2 種類があります。

<オル>
  • メモリ内キャッシュ
  • 分散キャッシュ
  • インメモリ キャッシュを使用すると、データはアプリケーション サーバーのメモリに保存され、必要なときにいつでもそこからデータをフェッチし、必要な場所で使用します。また、分散キャッシュには、Redis やその他の多くのサードパーティ メカニズムが存在します。ただし、このセクションでは、Redis キャッシュの詳細と、.NET Core での Redis キャッシュの動作について説明します。

    分散キャッシュ

    Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

    • 基本的に、分散キャッシュでは、データは複数のサーバー間で保存および共有されます
    • また、マルチテナント アプリケーションを使用する場合、複数のサーバー間の負荷を管理した後、アプリケーションのスケーラビリティとパフォーマンスを簡単に向上させることができます
    • 将来、1 つのサーバーがクラッシュして再起動しても、必要に応じて複数のサーバーが必要になるため、アプリケーションには影響がないとします。

    Redis は最も人気のあるキャッシュであり、現在多くの企業でアプリケーションのパフォーマンスとスケーラビリティを向上させるために使用されています。したがって、Redis とその使用法について 1 つずつ説明していきます。

    Redis キャッシュ

    • Redis は、データベースとして使用されるオープンソース (BSD ライセンス) インメモリ データ構造ストアです。
    • 基本的に、頻繁に使用されるデータと一部の静的データをキャッシュ内に保存し、ユーザーの要件に応じて使用および予約するために使用されます。
    • Redis には、List、Set、Hashing、Stream など、データの保存に使用できる多くのデータ構造が存在します。

    Redis キャッシュのインストール

    ステップ 1. 次の URL を使用して Redis サーバーをダウンロードします。

    https://github.com/microsoftarchive/redis/releases/tag/win-3.0.504

    ステップ 2. zip ファイルを解凍し、後で Redis サーバーと Redis CLI を開きます。

    Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

    .NET Core API を使用した Redis Cache の実装

    ステップ 1. .NET Core API Web アプリケーションを作成します

    ステップ 2. 次の NuGet パッケージをインストールします。アプリケーションでは段階的に手順を実行する必要があります。

    • Microsoft.EntityFrameworkCore
    • Microsoft.EntityFrameworkCore.Design
    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Tools
    • スワッシュバックル.AspNetCore
    • StackExchange.Redis

    ステップ 3. Model フォルダーを作成し、その中に詳細を含む 1 つの Product Class を作成します。

    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. ここで、Redis Cache 関連の使用のために ICacheService インターフェイスと CacheService クラスを作成します。

    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. 次に、Startup クラスの Configure Service メソッド内で ICacheService を登録し、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 クラスを 1 つ作成します

    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 作成のための移行とデータベースの更新を実行します。

    add-migration “FirstMigration”
    update-database

    したがって、このコマンドを入力して実行すると、移行に関連するものがいくつか生成され、appsetting.json

    の接続文字列内に指定したとおりに SQL Server 内にデータベースが作成されます。

    ステップ 12. 最後に、アプリケーションを実行し、Swagger UI を使用してデータを追加し、製品および製品エンドポイント内でキャッシュがどのように機能するかを確認します。

    基本的に、製品にキャッシュを追加し、コントローラーに製品エンドポイントを追加しました。ご覧のとおり、ユーザーがすべての製品のデータを取得したい場合、まず Redis キャッシュ内にデータが存在するかどうかを確認し、キャッシュ内にデータが存在する場合はそのデータをユーザーに返し、データがキャッシュ内に存在しない場合はデータベースからデータをフェッチし、さらにキャッシュに設定します。したがって、次回ユーザーはキャッシュからのみ取得し、不必要にデータベースにアクセスすることを避けます。

    また、製品の 2 番目のエンドポイントのコントローラーに見られるように、ユーザーが製品 ID を使用してデータをフェッチしたい場合、すべての製品のキャッシュからデータをフェッチし、製品 ID を使用してフィルタリングします。存在する場合は、キャッシュからユーザーに戻ります。そうでない場合は、データベースから取得し、フィルターを適用した後にユーザーに返します。

    プロダクト コントローラーの更新、削除、ポスト エンドポイントの内部でわかるように、remove メソッドを使用してプロダクト キー データをキャッシュから削除します。ニーズや要件に応じて使用できるメモリ キャッシュのシナリオと用途は数多くあります。ここで紹介したいのは、Redis キャッシュの基本と、ここで説明した .NET Core 内での Redis キャッシュの仕組みについてです。

    また、キャッシュの使用中に注意する必要があるシナリオが 1 つあります。 2 人のユーザーがアプリケーションを使用しているとします。その後、次のシナリオが発生します。

    • 最初のユーザーがすべての製品のデータを取得するリクエストを送信すると、最初のリクエストが送信され、データがキャッシュ内に存在するかどうかが確認されます。データがキャッシュ内に存在する場合、データベースからデータを取得し、キャッシュに設定します。
    • その間、2 番目のユーザーは製品の詳細を取得するリクエストを送信します。何が起こったのかというと、最初のユーザーのリクエストが完了する前にリクエストもデータベースにヒットし、そのため 2 番目のユーザーも商品の詳細を取得するためにデータベースにヒットすることになります。
    • したがって、これに対する解決策の 1 つは、以下に示すようにロック メカニズムを使用することです。

    このプライベート ロック オブジェクトをクラスの最上位に作成します。

    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 キャッシュに存在しない場合は、そこにロックを適用します。その後、リクエストはロックされてセクションに入力され、データベースから製品の詳細を取得し、それをキャッシュに設定してデータを返します。では、ユーザーのリクエストが完了する前に 2 番目のユーザーがリクエストを送信するとどうなるでしょうか?したがって、この場合、2 番目のリクエストがキュー内にあり、最初のユーザー リクエストが完了した後に 2 番目のリクエストが登場します。

    また、以下に示すように、Redis CLI を使用して Redis 内にすでに存在する主要な詳細を確認することもできます。

    Redis キャッシュを .NET Core API に統合してパフォーマンスとスケーラビリティを実現

    ここでは、Redis キャッシュに存在するキーに関する情報を提供するコマンドが多数あることがわかります。

    これは、.NET Core の Redis キャッシュに関するすべてです。これに関連する内容をご理解いただければ幸いです。

    コーディングを楽しんでください!


    1. Redis ZRANGE –Ascランク範囲でソートされたセットの要素を取得する方法

      このチュートリアルでは、特定の範囲の間でスコアの昇順でランク付けされた、並べ替えられた設定値の1つ以上の要素を取得する方法について学習します。このために、Redis ZRANGEを使用します コマンド。 ZRANGEコマンド ZRANGEコマンドは、指定された範囲で定義された、指定されたキーに保管されているソート済み設定値の1つ以上の要素を返します。並べ替えられたセットの要素は、スコアの昇順です。スコアが等しい要素には、辞書式順序の昇順が使用されます。 範囲は、開始(包括的)オフセットと終了(包括的)オフセットによって定義されます。これらのオフセットはゼロベースのインデックスであり、

    2. Redis LINSERT –前にリストに要素を挿入する方法|ピボット要素の後

      このチュートリアルでは、keyに格納されているリスト値のピボット要素の周りに新しい要素を挿入する方法について学習します。このために、Redis LINSERTを使用します コマンド。 LINSERTコマンド このコマンドは、特定の要素(ピボット)の前または後に、指定されたキーに格納されているリスト値に新しい要素を挿入します。空のリストとして解釈されるため、キーが存在しない場合は0を返し、キーは存在するがピボット要素がリストに存在しない場合は-1を返します。 redis LINSERTコマンドの構文は次のとおりです:- 構文:- redis host:post> LINSERT