基础数据与进程内缓存
前言
就目前的大环境来说,说到缓存,可能大部分小伙伴第一反应就会是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 };
}
}
效果大致如下:
当然,可能有人会提出问题,如果在程序启动的时候,缓存没能正确的写入,比如从数据库读数据的时候引发了异常,或者其他原因导致没能写进去。
这里也给出下面几个解决方案:
- 引入Polly进行重试操作
- 在读的时候,如果失败,重新load一次数据,这里一定要加互斥锁,避免同一时间n个请求同时去load数据
文中示例代码:
如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
声明:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我微信。