手头一个项目里有个功能模块,任务并发量比较大,处理并发数据请求太多了而且还有其他的更新写入操作数据库有些扛不住,运行一段时间数据量达到一定数量就开始查询超时直到业务歇菜,为了解决这个数据库搞并发性能问题 就引入了redis服务,来解决数据前置入库数据处理问题
业务需求简单介绍
其实需求很简单,我们会并发收到很多数据,但收到的都是一行数据库数据的子数据(子数据非常多),我们只要读取子数据的唯一号进行写入数据库进行归档即可,早期这块实时写入对数据库影响太大了,实际需求只需要在最后一次收到子数据后在操作入库就可以了(当超过一定时间没有收到对应的子数据即可算作收取完毕,还顺便解决了后续二次数据更新的问题)
用到Redis的功能
- 滑动更新延长有效期,这一步可以避免数据库 大量写入问题(也没必要)
- 数据键值超时后通知回调,通知程序某一条数据可以进行入库处理
核心就是:专门对付那些频繁来的数据请求,Redis给数据库前面加个 “缓冲垫”。一旦收到重复请求,它就不着急通知数据库干活,先拖着,把后面来的重复请求都挡一挡。一直等到最后一个重复请求超时了,在通知程序往数据库里面归档。这么干,那些没用的数据库 I/O 操作就少多了,数据库的压力自然就小了,整个项目运作起来也顺畅多了。
目前项目里用 Redis 缓存超时通知模块都好几年了,除了部署麻烦点一直也没啥大毛病。不过,就为了实现这么一个小功能,每次还得专门单独给它装个 Redis,总觉得有点大材小用。本着精益求精的精神,就动了脑筋,琢磨着能不能干脆把这个小功能直接集成到程序里头。
说干就干,一通研究下来,嘿,还真让咱给鼓捣出了差不多的效果。在本地测试的时候,看起来还挺像那么回事儿,运行得挺顺溜。但话说回来,在实际的高并发环境里,这玩意儿性能咋样、稳不稳定,目前还没真正测过。
实现代码如下
public class MemoryCacheExtension
{
/// <summary>
/// 线程并发锁 线程安全考虑
/// </summary>
readonly static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
/// <summary>
/// 读取内存缓存注册服务
/// </summary>
readonly IMemoryCache _memoryCache = Ioc.Default.GetService<IMemoryCache>();
/// <summary>
/// 动态缓存更新/添加
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="timeOffset"></param>
/// <returns></returns>
public async Task SlidingItemAsync(object key, object value, TimeSpan timeOffset)
{
await _semaphoreSlim.WaitAsync();
try
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(timeOffset);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(timeOffset)
.RegisterPostEvictionCallback(new PostEvictionDelegate(delegate (object _key, object _value, EvictionReason reason, object state)
{
if (OnExpirationEvent is not null)
{
if (reason == EvictionReason.TokenExpired)
{
OnExpirationEvent(_key, _value);
}
}
}))
.AddExpirationToken(new CancellationChangeToken(cancellationTokenSource.Token));
// 添加或更新缓存项
_memoryCache.Set(key, value, cacheEntryOptions);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// 超时失效事件
/// </summary>
public event Action<object, object> OnExpirationEvent;
}