记一次使用Redis超时

前言

前一段时间,在一个单据创建时候,在系统请求高时,发现会有偶尔Redis超时的异常,在该单据创建的时候会有一些逻辑判断和验证,而且出问题的是新模块创建的单据,具体说一下这个单据的上游,是一个模块拆分成一个系统(只是在上游进行拆分,数据也不在一个数据库中,导致下游的单据没办法区分新旧模块,所以只能根据上游的单据号判断是否新旧模块,新旧模块要并行运行一段,逐步替代旧模块).

Redis超时的异常,指向下游的单据创建和审核时,就是在验证单据号前缀的逻辑,这个验证逻辑是上游模块提供的,而且使用的默契还蛮多,其他模块都没有发生Redis超时的问题,而且不是每天都会有超时异常, 在排查代码后,没发现问题后,只能实现和上游一样验证代码(不使用Redis,改为全局静态的字典),Redis超时的问题,就这样解决了?

使用全局静态变量的方式,确实不会在超时Redis异常,但也带来新的问题,就是如果配置发生变化了,要重新发布系统才可以,这可有点不太科学呀! 原先是配置发生改变的话,会先删除Redis的缓存,重新从数据库获取,在使用Redis缓存起来,后面在验证单号时再去使用.

Redis异常代码

public List<string> GetSNPrefixList()
{
    lock (_locker)   //先上锁
    {
        var cache = _cacheManager.GetCache<string, List<string>>(_cacheName);
        return cache.Get(_cacheName, _ => GetList());  //没有缓存,从数据库获取,Get内部也会使用lock
    }
}


//真正验证单号前缀
public bool IsNewSN(string sn)
{
	//具体验证逻辑
}

在排查使用其他使用代码,发现一个不太正常的调用:

dto.Names = string.Join(" ", product.XXXX.Select(c =>
{
    string item = c.Name;
    //其他的处理逻辑代码
    //这里进行代码简化

    //假如product xxxx集合中存在多个元素,这里就会调用多次验证单号,在GetSNPrefixList时,有两次加锁操作,其实这里不需要调用多次    if (IsNewSN(product.ProderSN) && !c.Remark.IsNullOrEmpty())
    {
        item += $"({c.Remark})";
    }

    return item;
}));

正确代码

//改为异步操作,移除加锁操作
public async Task<List<string>> GetSNPrefixListAsync()
{
 return await _cacheManager.GetCache<string, List<string>>(_cacheName)
	 .GetAsync(_cacheKey, _ => GetList());
}
 
public async Task<bool> IsNewSN(string sn)
{
	//具体验证逻辑
}
var isNewSN = await IsNewSN(product.ProderSN); //获取是否为新单号,这里只调用一次
dto.Names = string.Join(" ", product.Select(c =>
{
    string item = c.Name;
    //其他的处理逻辑

    if (isNewSN && !c.Remark.IsNullOrEmpty()) 
    {
        item += $"({c.Remark})";
    }

    return item;
}));

这次Redis超时异常,其实主要是调用问题,在使用C# Linq中的Select中瞬时多次调用验证单号,在获取Redis缓存时,又先进行加锁了操作,其实Get和GetAsync中是有加锁操作的,最终才导致超时,后面对代码进行了代码,后面再也没有看到获取Redis缓存超时异常日志了.

秋风 2025-07-20