12/08/2018, 16:08

In-memory caching trong ASP.NET Core

Caching có thể cải thiện đáng kể hiệu suất và khả năng mở rộng của một ứng dụng bằng việc giảm công việc được yêu cầu để generate nội dung. Caching làm việc tốt nhất với những dữ liệu thay đổi không thường xuyên. Caching tạo một copy của dữ liệu và trả về nhanh hơn rất nhiều so với dữ liệu gốc. Bạn ...

Caching có thể cải thiện đáng kể hiệu suất và khả năng mở rộng của một ứng dụng bằng việc giảm công việc được yêu cầu để generate nội dung. Caching làm việc tốt nhất với những dữ liệu thay đổi không thường xuyên. Caching tạo một copy của dữ liệu và trả về nhanh hơn rất nhiều so với dữ liệu gốc. Bạn nên viết và test ứng dụng của mình không bao giờ phụ thuộc trên dữ liệu được cache.

Giống như các ngôn ngữ lập trình khác, ASP.NET Core hỗ trợ một vài phương thức cache khác nhau. Đơn giản nhất là cache dựa trên IMemoryCache, cái mà đại diện cache được lưu trữ trong bộ nhớ của web server. Những ứng dụng mà chạy trên hệ thống có nhiều server (Ví dụ như server đám mây Azure) thì nên đảm bảo rằng những sessions đó được dính lại (sticky) khi sử dụng in-memory cache. Dích sessions đảm bảo rằng tất cả những requests sau đó của một client đi đến cùng một server. Không dính (Non-sticky) sessions trong một hệ thống như trên yêu cầu cache phân tán để tránh vấn để thống nhất của cache. Về một số ứng dụng, cache phân tán có thể hỗ trợ mức độ mở rộng cao hơn in-memory cache.

IMemoryCache sẽ thu hồi cache entries dưới sức ép của bộ nhớ trừ khi ưu tiên của cache được set là CacheItemPriority.NeverRemove. Bạn có thể set CacheItemPriority để điều chỉnh cache items sẽ được gỡ bỏ. In-memory cache có thể lưu trữ bất kì object nào; còn cache phân tán được giới hạn trong mảng byte[].

In-memory caching là một service cái mà được tham chiếu từ ứng dụng của bạn sử dụng Dependency Injection(DI). Gọi AddMemoryCache trong ConfigureServices:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache();
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}

Khởi tạo IMmemoryCache trong contructor của Controller:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }
 }

IMemoryCache yêu cầu NuGet packge "Microsoft.Extensions.Caching.Memory"

Code bên dưới sử dụng TryGetValue để check nếu thời gian hiện tại là trong cache hay không. Nếu item là chưa được cache thì sẽ tạo mới một entry bằng việc sử dụng Set method.

public IActionResult CacheTryGetValueSet()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

Thời gian hiện tại và thời gian được cache hiển thị trên giao diện:

@model DateTime?

<div>
    <h2>Actions</h2>
    <ul>
        <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
        <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
    </ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

Giá trị được cache sẽ giữ lại trong khi có những requests vẫn chưa quá hạn (không gỡ bỏ do sức ép của bộ nhớ). Hình ảnh bên dưới hiển thị thời gian hiện tại và một thời gian cũ hơn nhận được từ cache.

Code dưới đây sử dụng GetOrCreate và GetOrCreateAsync để cache dữ liệu.

public IActionResult CacheGetOrCreate()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

public async Task<IActionResult> CacheGetOrCreateAsync()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return Task.FromResult(DateTime.Now);
    });

    return View("Cache", cacheEntry);
}

Code gọi hàm Get để lấy thời gian đã được cache:

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

Ví dụ sau:

  • Set thời gian hết hạn tuyệt đối. Cái này là thời gian tối đa mà entry có thể được cache, đồng thời ngăn chặn item trở nên quá cũ khi trượt quá hạn trong khi lại được liên tục được gia hạn.
  • Set một thời gian trượt quá hạn. Requests mà truy cập item được cache này sẽ reset đồng hồ trượt quá hạn.
  • Set độ ưu tiên cache tới CacheItemPriority.NeverRemove.
  • Set một PostEvictionDelegate cái mà sẽ được gọi sau khi entry bị gỡ bỏ khỏi cache. Callback được được chạy trên một thread khác với code mà gỡ bỏ item từ cache
public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Pin to cache.
        .SetPriority(CacheItemPriority.NeverRemove)
        // Add eviction callback
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry()
{
    return View("Callback", new CallbackViewModel
    {
        CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
        Message = _cache.Get<string>(CacheKeys.CallbackMessage)
    });
}

public IActionResult RemoveCallbackEntry()
{
    _cache.Remove(CacheKeys.CallbackEntry);
    return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeContro((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

Ví dụ bên dưới chỉ ra làm cách nào để hết hạn một cache entry nếu một dependent entry hết hạn. Một CancellationChangeToken được thêm vào item đã được cache. Khi Cancel được gọi trên CancellationTokenSource, cả hai cache entries sẽ bị gỡ bỏ

public IActionResult CreateDependentEntries()
{
    var cts = new CancellationTokenSource();
    _cache.Set(CacheKeys.DependentCTS, cts);

    using (var entry = _cache.CreateEntry(CacheKeys.Parent))
    {
        // expire this entry if the dependant entry expires.
        entry.Value = DateTime.Now;
        entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

        _cache.Set(CacheKeys.Child,
            DateTime.Now,
            new CancellationChangeToken(cts.Token));
    }

    return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()
{
    return View("Dependent", new DependentViewModel
    {
        ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
        ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
        Message = _cache.Get<string>(CacheKeys.DependentMessage)
    });
}

public IActionResult RemoveChildEntry()
{
    _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
    return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Parent entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

Việc sử dụng CancellationTokenSource cho phép nhiều cache entries được gõ bỏ như một nhóm. Với việc sử dụng pattern trong code ở trên cache entries được tạo bên trong block sẽ kế thừa trigger và expiration settings.

  • Introduction to in-memory caching in ASP.NET Core
  • Code sample
0