这几天,写了一个简单的登录案例,在里面就有一个功能,服务器一秒最多处理200个请求,超过的请求,会直接返回一个提示语,可以把这看作是一种熔断的处理。这个功能,只是我写的案例里,很小的一个功能,但是这个功能可以独立出来,再补充完善,完全可以做为一个项目来做,不仅可以用于安全的防护,也可以用于实现服务器的智能自动化管理的实现。关于这一点,我后面会提供一些思路。先来讲讲这个功能,我是怎么实现的。
要做这样一个功能,唯一的难点就是,如何统计服务器在一秒内收到了多少个请求。这个时候,思路就很重要了,想到点上了,就非常容易,没有想到点上,就会感觉无从下手。我的思路得:当一个请求过来,获取一下时间戳,把时间戳作为key存入一个map中,value就是服务器收到的请求的次数,每一个请求过来,就让value加1。这样,不就可以获取服务器在一秒内接收的请求数了么。当然,这是最简单的思路,还存在着很多的问题,所以,需要再做一些改进。而我最终的实现是:使用两个map存放数据。以一个小时为一个循环,一个map存放一个小时内前半个小时的数据,另一个map存放一个小时内后半个小时的数据。在前半个小时,对后半个小时的数据初始化,而在后半个小时,则对前半个小时的数据初始化,这样就避免了并发的问题。代码实现如下:
这里,key使用Integer,本是想减少内存的占用,但后来查资料,其实Integer和Long的内存占用是一样的。因为JVM对对象的内存分配,是强制为8的倍数,虽然Integer实际只有12字节,但因为要是8的倍数,所以也是分配16字节的内存。关于这方面的知识,大家可以自己网上找文章看看。然后,就是提供获取每秒内访问次数的方法:
每一次获取,值都自增1,因为value是一个AtomicInteger,调用它的incrementAndGet()方法,可以避免并发的问题。然后添加一个拦截器,拦截所有的请求,当然,可以过滤掉静态的资源,我这里简单地实现,没有过滤静态资源
然后再添加一个定时任务,定时对两个map初始化数据
在每个小时的1分的时候,清理后半小时的map的数据。31分的时候,清理前半小时的map的数据。当然,在清理数据的时候,可以对数据,进行一些统计,比如,半个小时内一共收到多少个请求,处理了几个,拒绝了几个等等。
看完了实现逻辑,是不是感觉非常简单呢?这只是最基本的实现,还可以再添加一些逻辑,这就是补充完善了。比如加入ip的判断,限定一个ip发送的请求,一秒内最多处理10个。关于这一点,我想起原先看过的一篇文章,说是有家公司,让开发做了个爬虫去爬别人的网站的数据,开发写好后,开始是启一个线程去跑,没什么问题,后来嫌太慢,就加线程跑,做爬虫的这边没什么问题,可是被爬的网站有事了,网站的负荷一直很高,影响了网站的正常用户体验了。于是就查问题,结果发现有个ip在频繁的调用他们的接口,这家公司呢,就直接报了警。后面的结果,不说也应该猜得到了,做爬虫的公司被查了,做爬虫的开发,也被警察蜀黍带走了。于是,就有网友戏称,这是面向监狱的编程。从某种意义上来说,做爬虫的被抓,有一些冤。如果被爬的网站做了IP限流的防护,也许就不会出现这样的结果。当然,做爬虫的知道人家做了防护,还要用技术手段突破别人的防护,那被抓就一点也不冤了。
做ip限流,如果要考虑性能的话,就没有那么简单了。想想看,ip何止千万,一个ip再按秒保存数据,即使只是保存10秒的数据,那1000万个IP就要保存1亿的数据,这就不是很好的解决方案了。当然,对ip取模,做分布式处理,是可以,但这应该是最后一招。在这之前,还有可以优化的手段。至于怎么优化,我这里就不细说了,大家可以自己思考一下,有些时候,正的不行,那就反着来。
现在,我这个案例中,限制的次数是写死的200,这个200是可以放到配置文件中的,随时可以通过修改配置文件来调整这个值。但这还不是最好的处理方法,想想看,如果是一台新的服务器,甚至要布署的服务也是第一次上线,怎么确定这一个值是多少?那么,能不能让程序自己来确定这个最大值呢?比如一开始设置成100,当服务器1秒钟收到的请求达到了100,这个时候,先会判断一下服务器的资源使用情况,如果服务器的资源使用情况很少,那程序就会把这个值设置成200。然后,到了200的时候,再判断一下,直到找到最大值为止。当然,这是一种理想的情况,但在实际中,受限于开发写的服务的处理逻辑,就可能会出现,服务器的资源使用一直很少,但服务器处理的请求数(也就是吞吐量)就是上不去。所以单纯地判断服务器的资源使用情况,很明显会出现判断错误的情况。这个时候,就还要加上处理请求的平均时间和吞吐量,通过一定的算法,来确定这个最大值。当最终确定了这个最大值后,后面再达到最大值时,就不用再判断资源使用情况了,否则就是性能的浪费,这个又要怎么实现?大家可以思考一下。
当程序可以自己确定这个最大值之后,接下来,又可以做什么呢?那就是实现自动化管理。像阿里巴巴,双十一,肯定是开了很多的服务器,但平时,他们会开这么多服务器吗?肯定不会。就是在平时的一天里,不同的时段,开启的服务器的数量,也肯定是不一样的。开5台服务器,每台都满负荷运转,和开10台服务器,每台都半负荷运转。如果给你选,你会选择哪种方案呢?肯定是选前一种,因为前一种是资源的充分利用。5台满负荷和10台半负荷,开销肯定是差很大的。开一台服务器,哪怕什么也不做,那也是要耗电的,也是有机器损耗的。什么时候要加开服务器,什么时候要关掉几台服务器,这些,肯定不是由人来控制的,而是系统在控制。系统又是如何来控制这些的呢?在前面,我们讲实现过程的时候,定时清理数据的逻辑,我说过,在清理的时候,可以做一些统计。这个统计,就包括统计系统的负荷。比如开了10台服务器,平均下来,每台的负荷都不到80%,那么就可以选择关掉1台服务器,如果平均每台的负荷,超过了95%,那么,就要选择加开一台服务器了。当然,这个80%、95%只是一个举例,根据服务器的数量,及实际的情况,是要通过一些算法来求得的。