在ASP.Net Core上具有自动重新生成的内存中缓存 [英] In-Memory Caching with auto-regeneration on ASP.Net Core

查看:90
本文介绍了在ASP.Net Core上具有自动重新生成的内存中缓存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想没有内置的方法可以实现这一目标:

I guess there is not built-in way to achieve that:

我有一些缓存的数据,这些数据必须始终是最新的(间隔为几十分钟).它的生成大约需要1-2分钟,因此有时会导致超时请求.

I have some cached data, that need to be always up to date (interval of few 10s of minutes). Its generation takes around 1-2 minutes, therefore it leads sometimes to timeout requests.

为了优化性能,我使用Cache.GetOrCreateAsync将其放入内存缓存中,因此我确定可以在40分钟内快速访问数据.但是,缓存过期仍需要时间.

For performances optimisation, I put it into memory cache, using Cache.GetOrCreateAsync, so I am sure to have fast access to the data during 40 minutes. However it still takes time when the cache expires.

我想拥有一种机制,可以在数据过期之前自动刷新数据,因此用户不会受到刷新的影响,并且在刷新过程中仍然可以访问旧数据".

I would like to have a mechanism that auto-refreshes the data before its expiration, so the users are not impacted from this refresh and can still access the "old data" during the refresh.

它实际上将添加一个过期前"过程,以避免数据过期达到其期限.

It would actually be adding a "pre-expiration" process, that would avoid data expiration to arrive at its term.

我认为这不是默认IMemoryCache缓存的功能,但是我可能错了吗? 是否存在?如果没有,您将如何开发此功能?

I feel that is not the functioning of the default IMemoryCache cache, but I might be wrong? Does it exist? If not, how would you develop this feature?

我正在考虑使用PostEvictionCallbacks,并将条目设置为在35分钟后删除,这将触发更新方法(它涉及DbContext).

I am thinking of using PostEvictionCallbacks, with an entry set to be removed after 35 minutes and that would trigger the update method (it involves a DbContext).

推荐答案

这是我的解决方法:

网络请求调用的部分("Create"方法只能在第一次调用).

The part called by the web request (the "Create" method should be called only the first time).

var allPlaces = await Cache.GetOrCreateAsync(CACHE_KEY_PLACES
    , (k) =>
    {
       k.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(40);
       UpdateReset();
       return GetAllPlacesFromDb();
    });

然后是魔术(这本可以通过计时器实现的,但不想在那里处理计时器)

And then the magic (This could have been implemented through a timer, but didn't want to handle timers there)

// This method adds a trigger to refresh the data from background
private void UpdateReset()
{
    var mo = new MemoryCacheEntryOptions();
    mo.RegisterPostEvictionCallback(RefreshAllPlacessCache_PostEvictionCallback);
    mo.AddExpirationToken(new CancellationChangeToken(new CancellationTokenSource(TimeSpan.FromMinutes(35)).Token));
    Cache.Set(CACHE_KEY_PLACES_RESET, DateTime.Now, mo);
}

// Method triggered by the cancellation token that triggers the PostEvictionCallBack
private async void RefreshAllPlacesCache_PostEvictionCallback(object key, object value, EvictionReason reason, object state)
{
    // Regenerate a set of updated data
    var places = await GetLongGeneratingData();
    Cache.Set(CACHE_KEY_PLACES, places, TimeSpan.FromMinutes(40));

    // Re-set the cache to be reloaded in 35min
    UpdateReset();
}

因此,缓存通过触发后驱逐方法的取消令牌获得了两个条目,第一个条目包含数据,在40分钟后过期,第二个条目在35分钟后过期. 此回调将在数据过期之前刷新数据.

So the cache gets two entries, the first one with the data, expiring after 40 minutes, the second one expiring after 35min via a cancellation token that triggers the post eviction method. This callback refreshes the data before it expires.

请记住,这将使网站保持唤醒状态,并且即使不使用也会占用内存.

Keep in mind that this will keep the website awake and using memory even if not used.

** *更新计时器* **

以下类被注册为单例.传递DbContextOptions而不是DbContext来创建具有正确作用域的DbContext.

The following class is registered as a singleton. DbContextOptions is passed instead of DbContext to create a DbContext with the right scope.

public class SearchService
{
    const string CACHE_KEY_ALLPLACES = "ALL_PLACES";
    protected readonly IMemoryCache Cache;
    private readonly DbContextOptions<AppDbContext> AppDbOptions;
    public SearchService(
            DbContextOptions<AppDbContext> appDbOptions,
            IMemoryCache cache)
    {
        this.AppDbOptions = appDbOptions;
        this.Cache = cache;
        InitTimer();
    }
    private void InitTimer()
    {
        Cache.Set<AllEventsResult>(CACHE_KEY_ALLPLACESS, new AllPlacesResult() { Result = new List<SearchPlacesResultItem>(), IsBusy = true });

        Timer = new Timer(TimerTickAsync, null, 1000, RefreshIntervalMinutes * 60 * 1000);
    }
    public Task LoadingTask = Task.CompletedTask;
    public Timer Timer { get; set; }
    public long RefreshIntervalMinutes = 10;
    public bool LoadingBusy = false;

    private async void TimerTickAsync(object state)
    {
        if (LoadingBusy) return;
        try
        {
            LoadingBusy = true;
            LoadingTask = LoadCaches();
            await LoadingTask;
        }
        catch
        {
            // do not crash the app
        }
        finally
        {
            LoadingBusy = false;
        }
    }
    private async Task LoadCaches()
    {
       try
       {
           var places = await GetAllPlacesFromDb();
           Cache.Set<AllPlacesResult>(CACHE_KEY_ALLPLACES, new AllPlacesResult() { Result = places, IsBusy = false });
       }
       catch{}
     }
     private async Task<List<SearchPlacesResultItem>> GetAllPlacesFromDb() 
     {
         // blablabla
     }

 }

注意: DbContext选项需要注册为单例,默认选项现在已确定范围(我相信允许更简单的多租户配置)

Note: DbContext options require to be registered as singleton, default options are now Scoped (I believe to allow simpler multi-tenancy configurations)

services.AddDbContext<AppDbContext>(o =>
    {
        o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        o.UseSqlServer(connectionString);
    }, 
    contextLifetime: ServiceLifetime.Scoped, 
    optionsLifetime: ServiceLifetime.Singleton);

这篇关于在ASP.Net Core上具有自动重新生成的内存中缓存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆