基础数据与进程内缓存

前言

就目前的大环境来说,说到缓存,可能大部分小伙伴第一反应就会是redis。很多人往往忽视了进程内缓存这一利器。

分布式缓存和进程内缓存的优势和劣势不用多说,大家都略知一二。

我个人还是更倾向于将基础数据,这些变动少的东西,丢到进程内缓存而不是放到redis中,然后定时去更新。

碰到的一些业务情景对这些基础数据的实时要求并不会太高,可以容忍20〜30分钟的延迟,即允许这一小段时间内的脏读,而不影响系统的整体结果。针对不同的业务,就要视情况而定了。

目前的做法是基于.NET Core的HostedService,在程序启动的时候先把数据加载到缓存中,同时有个定时器,每隔一个时间刷新一次。

具体实现

首先是刷新缓存的后台任务

public class RefreshCachingBgTask : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private readonly IEasyCachingProviderFactory _providerFactory;        
    private Timer _timer;
    private bool _refreshing;
    
    public RefreshCachingBgTask(ILoggerFactory loggerFactory, IEasyCachingProviderFactory providerFactory)
    {            
        this._logger = loggerFactory.CreateLogger<RefreshCachingBgTask>();
        this._providerFactory = providerFactory;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Refresh caching backgroud taks begin ...");
        
        _timer = new Timer(async x =>
        {
            if (_refreshing)
            {
                _logger.LogInformation($"Latest manipulation is still working ...");
                return;
            }
            _refreshing = true;
            await RefreshAsync();
            _refreshing = false;
        }, null, TimeSpan.Zero, TimeSpan.FromSeconds(20));

        return Task.CompletedTask;
    }

    private async Task RefreshAsync()
    {
        _logger.LogInformation($"Refresh caching begin at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");

        try
        {
            var cachingProvider = _providerFactory.GetCachingProvider("m1");

            // mock query data from database or others 
            var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            var random = new Random().NextDouble();

            // only once
            var dict = new Dictionary<string, string>()
            {
                { ConstValue.Time_Cache_Key, time },
                { ConstValue.Random_Cache_Key, random.ToString() }
            };
            await cachingProvider.SetAllAsync(dict, TimeSpan.FromDays(30));

            //// one by one
            //await cachingProvider.SetAsync(Time_Cache_Key, time, TimeSpan.FromSeconds(10));
            //await cachingProvider.SetAsync(Random_Cache_Key, random.ToString(), TimeSpan.FromSeconds(10));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Refresh caching error ...");                
        }

        _logger.LogInformation($"Refresh caching end at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Refresh caching backgroud taks end ...");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

注: 因为是演示,所有这里设置的时间比较短,正常来说,这里是要设置一个超长的缓存时间,以便在获取这个缓存的时候,永远能取到值。

Startup中注册EasyCaching和刷新缓存的后台任务

public void ConfigureServices(IServiceCollection services)
{
    services.AddEasyCaching(options=> 
    {
        options.UseInMemory("m1");
    });

    // register backgroud task
    services.AddHostedService<RefreshCachingBgTask>();

    // others ..
}

然后是控制器中的使用

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IEasyCachingProviderFactory _providerFactory;  

    public ValuesController(IEasyCachingProviderFactory providerFactory)
    {
        this._providerFactory = providerFactory;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        var provider = _providerFactory.GetCachingProvider("m1");

        var time = provider.Get<string>(ConstValue.Time_Cache_Key);

        // do something based on time ...

        var random = provider.Get<string>(ConstValue.Random_Cache_Key);

        // do something based on random ...

        return new string[] { time.Value, random.Value };            
    }        
}

效果大致如下:

当然,可能有人会提出问题,如果在程序启动的时候,缓存没能正确的写入,比如从数据库读数据的时候引发了异常,或者其他原因导致没能写进去。

这里也给出下面几个解决方案:

  1. 引入Polly进行重试操作
  2. 在读的时候,如果失败,重新load一次数据,这里一定要加互斥锁,避免同一时间n个请求同时去load数据

文中示例代码:

RefreshCaching

posted @ 2019-03-26 08:44  Catcher8  阅读(...)  评论(... 编辑 收藏