现在,因为种种因素,你必须对一个请求或者方法进行频率上的访问限制。
比如, 你对外提供了一个API接口,注册用户每秒钟最多可以调用100次,非注册用户每秒钟最多可以调用10次。
比如, 有一个非常吃服务器资源的方法,在同一时刻不能超过10个人调用这个方法,否则服务器满载。
比如, 有一些特殊的页面,访客并不能频繁的访问或发言。
比如, 秒杀活动等进行。
比如 ,防范DDOS,当达到一定频率后调用脚本iis服务器ip黑名单,防火墙黑名单。
如上种种的举例,也就是说,如何从一个面向切面的方式对调用的方法进行频率上的限制。而对频率限制,服务器层面都有最直接的解决方法,现在我说的则是代码层面上的频率管控。
本文给出两个示例,一个是基于单机环境的实现,第二个则是基于分布式的Redis实现。
以第一个API接口需求为例,先说下单机环境下的实现。
按照惯性思维,我们自然会想到缓存的过期策略这种方法,但是严格来讲就HttpRuntime.Cache而言,通过缓存的过期策略来对请求进行频率的并发控制是不合适的。
HttpRuntime.Cache 是应用程序级别的Asp.Net的缓存技术,通过这个技术可以申明多个缓存对象,可以为每个对象设置过期时间,当过期时间到达后该缓存对象就会消失(也就是当你访问该对象的时候为Null)
为什么这样说呢?比如对某个方法(方法名:GetUserList)我们要进行1秒钟最多10次的限制,现在我们就新建一个int型的Cache对象,然后设置1秒钟后过期消失。那么每当访问GetUserList方法前,我们就先判断这个Cache对象的值是否大于10,如果大于10就不执行GetUserList方法,如果小于10则允许执行。每当访问该对象的时候如果不存在或者过期就新建,这样周而复始,则该对象永远不可能超过10。
if ((int)HttpRuntime.Cache["GetUserListNum"] > 10) //大于10请求失败 { Console.WriteLine("禁止请求"); } else { HttpRuntime.Cache["GetUserListNum"] = (int)HttpRuntime.Cache["GetUserListNum"] + 1; //否则该缓存对象的值+1 Console.WriteLine("允许请求"); }
这样的思想及实现相对来说非常简单,但是基于这样的一个模型设定,那么就会出现这种情况:
如上图,每个点代表一次访问请求,我在0秒的时候 新建了一个名字为GetUserListNum的缓存对象。
在0~0.5秒期间 我访问了3次,在0.5~1秒期间,我们访问了7次。此时,该对象消失,然后我们接着访问,该对象重置为0.
在第1~1.5秒期间,还是访问了7次,在第1.5秒~2秒期间访问了3次。
基于这种简单缓存过期策略的模型,在这2秒钟内,我们虽然平均每秒钟都访问了10次,满足这个规定,但是如果我们从中取一个期间段,0.5秒~1.5秒期间,也是1秒钟,但是却实实在在的访问了14次!远远超过了我们设置的 1秒钟最多访问10次的 限制。
那么如何科学的来解决上面的问题呢?我们可以通过模拟会话级别的信号量这一手段,这也就是我们今天的主题了。
什么是信号量?
仅就以代码而言, static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5); 它的意思就代表在多线程情况下,在任何一时刻,只能同时5个线程去访问。
4容器4