请求量限制方法-使用本地Cache记录当前请求量[坑]

  有个需求:需要限制每个账户请求服务器的次数(该次数可以配置在DB,xml文件或其他)。单位:X次/分钟。若1分钟内次数<=X 则允许访问,1分钟内次数>X则不再允许访问。   这类需求很常见的,像请求Baidu Map Api服务接口等,都有这类限制,只是单位不同。

  一般来说,接口的请求在服务器端都会有记录的,比如记在DB中,记录 账户、请求时间、请求信息、请求操作、服务器响应信息 等。所以逻辑上完全可以获取请求当前时间段的请求量(执行记录 的count()操作,在DB中为Sql : SELECT COUNT(*) FROM RequestRecordTable where Account = .. And ReqTime .. )。但这样不现实:为这么一个小功能竟然还要耗时去计算,完全浪费计算资源、内存资源,一旦请求量、记录数大增,那么性能肯定很差。

  于是就想到了缓存:1.把该账户的最大请求量放在缓存里,永不过期。2.把该账户的1分钟以来的请求量放在缓存中,1分钟过期自动释放。  3.每来一次请求,则当前请求量+1,后与该账户的最大请求量比对,<=则允许,>则超过次数。为了简单起见,使用.Net 本地缓存。详见代码(该账户的最大请求量已获取到,若获取失败则读配置:

 1  /// <summary>
 2         /// 验证该账户当前请求量是否超过限制
 3         /// </summary>
 4         /// <param name="account">账户</param>
 5         /// <param name="maxNumLimit">最大请求量,已获取到</param>
 6         /// <returns>true-超过,false-未超过</returns>
 7         public bool VerifyMaxNumLimit(string account, int maxNumLimit)
 8         {
 9             string currentSendNumCacheKey = string.Format("SysCode_{0}_CurrentSendNum", account);
10             object currentSendNumObj = HttpContext.Current.Cache[currentSendNumCacheKey];
11             int currentSendNum = 0;
12             if (currentSendNumObj != null)  //缓存存在
13             {
14                 currentSendNum = (int)currentSendNumObj;
15                 currentSendNum++;
16                 HttpContext.Current.Cache[currentSendNumCacheKey] = currentSendNum;
17             }
18             else   //缓存失效,已到期或者缓存问题。重新写入:目前请求次数 1次,过期时间 1分钟
19             {
20                 currentSendNum = 1;
21                 HttpContext.Current.Cache.Insert(currentSendNumCacheKey, 1,null, DateTime.Now.AddMinutes(1),Cache.NoSlidingExpiration);
22             }
23 
24             return currentSendNum > maxNumLimit;
25         }

乍一看,完全没有问题,大功告成。可是,当你本地自己测试时,却发现 :前几次不超过限制量次数的调用接口都是成功的,但是一旦超过该请求量以后,哪怕是5分钟后,也无法在此请求。究其原因:VerifyMaxNumLimit 此后一直返回true=> 1分钟后缓存没有清除,仍然存在。

最终原因是什么??

1.是15行:  HttpContext.Current.Cache[currentSendNumCacheKey] = currentSendNum;

这句代码看似只是修改缓存值。可实际上,这句代码等效于:HttpContext.Current.Cache.Insert(currentSendNumCacheKey, currentSendNum)!即覆盖缓存,并将缓存设置为永不过期(除非IIS应用程序池回收,或内存不足等外部情况)=》缓存不失效=》当前请求量不断++,所以 return currentSendNum > maxNumLimit   一直是true.

2.即使把1解决了,还有一个不易发现的BUG,不容易看出来,是多个线程使用并修改统一资源=》诱发并发问题:当同账号的请求1和请求2同时来临,可能请求1的line 16 与请求2的line21 是先后执行的。就出现了脏读写。

 

所以,需要解决:1.找到一个可修改缓存值又不修改缓存失效时间的方法(本地缓存HttpContext.Current.Cache 似乎无法实现,放弃,2.加锁或做成单例。

我公司正好有Redis组件,满足上述要去并且本身封装时即为线程安全的。所以最终我用了Redis来记录账户当前请求量。具体代码其实类似 上述代码段,只是把和HttpContext.Current.Cache  有关的地方全部改为Redis 相关语句。

至此结束,实现请求量限制。

 

经验:关于HttpContext.Current.Cache,有些代码看似平淡但是内部有说不清楚的作用。

以后还是要多写写代码,多积累经验,遇到的坑多了,才会吃一堑长一智。当然,若能得到高人指点或者自己之前在其他地方见过这个坑的相关介绍,那么能避免再好不过,少走弯路节省时间。   至于,多个请求同时请求、并发、共享资源的事情要小心,最好加锁(加锁会导致效率变差),这也是设计时要考虑好的,不然出了BUG难以调试重现出来。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。