我的第一个redis应用的方法代码如下:
public List<QuestionsLibrary> GetRedisQuestionsLibraryList()
{
var qlist = RedisHelper.ListRange<QuestionsLibrary>(RedisKey.RedisQuestionsLibraryList);
if (qlist == null || qlist.Count() <= 0)
{
List<QuestionsLibrary> list = new QuestionsLibraryService().GetNolock(m => true).ToList();
RedisHelper.ListRightPush(RedisKey.RedisQuestionsLibraryList, list);
return list;
}
else
{
return RedisHelper.ListRange<QuestionsLibrary>(RedisKey.RedisQuestionsLibraryList);
}
}
此方法在使用中通过对服务器的监察,发现cpu从原来的1%左右飙升到40-50%。而且在错误日志出现了大量有关redis超时提示。错误提示代码如下:
Timeout performing LRANGE Redis:QuestionsLibraryList, inst: 7, queue: 8, qu: 0, qs: 8, qc: 0, wr: 0, wq: 0, in: 2944, ar: 0, clientName: iZ25f3l6awvZ, serverEndpoint: 127.0.0.1:8383, keyHashSlot: 3349, IOCP: (Busy=1,Free=799,Min=8,Max=800), WORKER: (Busy=9,Free=791,Min=8,Max=800) (Please take a look at this article for some common client-side issues that can cause timeouts: http://stackexchange.github.io/StackExchange.Redis/Timeouts)
可以确认这个都是页面调用RedisHelper.ListRange这个方法引起的。从网上扒的资料来看,redis是单进程,单线程的。也就是说,短时间内网站向redis服务发出了大量的ListRange指令。由于指令是在队列中一条一条处理的。导致后面的指令得不到处理。
我又到服务器上打开cmd窗口,然后进入redis命令后输入:
SLOWLOG GET
然后发觉最近的超时命令都是LRANGE,而且每条都超过了10几秒。但我很疑惑,因为我的RedisHelper类是单例连接,使用的都是静态方法和静态属性,静态变量。难道是我使用的GetRedisQuestionsLibraryList() 不是静态方法的缘故?这不科学啊,虽然我这个方法是实例方法。可是这个方法中自redis服务中获取值的方法确实实实在在的静态方法啊。这令我很凌乱,在网上扒资料,并没有找到这块有用的东西。但问题还是要解决。因为没有理论佐证,只好进行尝试。于是我把此方法改为静态属性的形式代码:
protected static List<QuestionsLibrary> RedisQuestionsLibraryList
{
get
{
var qlist = RedisHelper.ListRange<QuestionsLibrary>(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList);
if (qlist == null || qlist.Count() <= 0)
{
List<QuestionsLibrary> list = new QuestionsLibraryService().GetNolock(m => true).ToList();
RedisHelper.ListRightPush(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList, list);
return list;
}
else
{
return qlist;
}
}
}
然后修改相关调用代码,编译,上传到服务器上进行测试。结果出现了大量的redis的Timeout performing LRANGE Redis超时提示。和原来的错误一样。果断修改为静态方法形式进行测试,代码如下:
protected static List<QuestionsLibrary> GetRedisQuestionsLibraryList()
{
var qlist = RedisHelper.ListRange<QuestionsLibrary>(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList);
if (qlist == null || qlist.Count() <= 0)
{
List<QuestionsLibrary> list = new QuestionsLibraryService().GetNolock(m => true).ToList();
RedisHelper.ListRightPush(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList, list);
return list;
}
else
{
return qlist;
}
}
protected static List<QuestionsLibrary> RedisQuestionsLibraryList = getRedisQuestionsLibraryList();
private static List<QuestionsLibrary> getRedisQuestionsLibraryList()
{
var qlist = RedisHelper.ListRange<QuestionsLibrary>(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList);
if (qlist == null || qlist.Count() <= 0)
{
List<QuestionsLibrary> list = new QuestionsLibraryService().GetNolock(m => true).ToList();
RedisHelper.ListRightPush(LMSoft.WebCommon.RedisKey.RedisQuestionsLibraryList, list);
return list;
}
else
{
return qlist;
}
}
上传上去后,观察网站cpu的使用。又观察错误日志代码。经过一个多小时的跟踪,这次没发现cpu使用大幅上扬的情况,也没发现超时错误。这算是解决了。可是我很无语,你说实例方法调用,会出现大量排队现象,怎么静态方法及静态属性这两种获取方法也出现大量redis排队现象。概念理解的不透么,也只能这样说了。
扒了扒资料也就找到一个似乎能解释通的。提到了静态变量的初始化及赋值。
1、任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项
2、如果没有编写静态构造函数,而这时类中包含带有初始值设定的静态字段,那么编译器会自动生成默认的静态构造函数
3、类的静态构造函数在给定应用程序域中至多执行一次:只有创建类的实例或者引用类的任何静态成员才激发静态构造函数
这意味着我上面所写的静态变量的赋值方法,其实在C#的编译器中是自动创建的静态构造函数中完成的赋值操作,并且只执行了一次。。
静态方法及静态属性按我以前的理解,它是所有该类的实例方法都可以使用的,并且在这里的话这个静态方法或者静态属性只调用一次Redis中的LRANGE命令。然而从测试的情况来看,它和实例方法一样,每次使用都要调用一次Redis中的LRANGE命令。我又对静态方法及静态属性的理论解释深入挖掘,发现了一个有意思的解释,就是它是一系列行为的组合。是一个action,也就是动作。那这也意味着每次调用的时候,它里面的一系列操作都要执行一遍。这样就解释通了。由于每次调用静态属性和静态方法,里面的操作都要执行,那自然对Redis的LRANGE命令进行了多次调用操作。然后导致了redis报超时错误。而静态变量由于赋值就进行了一次,也就是Redis的LRANGE命令只调用了一次,所以不会遇到由于大量调用造成超时的问题