在我们做社区的时候,经常会出现发水帖的同学。对于这种恶意刷帖的,我们的运营同学很是头疼,而且这种还不能在网关进行ip之类的过滤,只能基于单个单个用户进行处理,我们经常策略就是:每分钟发帖次数不能超过2个,超过后就关小黑屋10分钟。
对于这种“黑恶”请求,我们必须要做到是关小黑屋,当然有的系统架构比较大的,在网关层面就已经进行关了,我们这里是会在业务层来做,因为咱业务不是很大,当然同学们也可以把这个移植到网关层,这样不用穿透到我们业务侧,最少能够减少我们机房内部网络流量。
、
以我们场景为例子,使用Redis来做分布式锁和原子计数器,时间内叠加,判断叠加值是否超过阈值。
这个方案,在很多人设计的时候,都会考虑,看起来也没有太大问题,主要流程是:
//将我们用户请求量叠加1
$request_nums = Redis::incr('user:1:request:nums',1);
//第一次叠加,设置key的过期时间
if ($request_nums == 1){
Redis::expire('user:1:request:nums',300);
}
if($request_nums > 10){
//加入小黑屋,下次再进来就要锁定判断
}
问题:咋一看是没有问题,每次计算都在我的区间内,能够保证一个区间内的请求量是没问题的,而且还是要我们Redis的原子计数器,但是这里有一个问题是,一个用户两个时间段内都没有问题,但是跨时间段这个点是没有考虑的。
那么有办法解决这个时间推移问题造成时间段计算量不精准的问题吗?
答案是肯定有,我接下来是使用了Redis的有序集合来做。
大致流程:
//将我们时间戳写入我们redis的有序集合里面
Redis::zadd('user:1:request:nums',1561456435,'1561456435.122');
//设置key的过期时间为10分钟
Redis::expire('user:1:request:nums',300);
//删除我们10分钟以前的数据
Redis::ZREMRANGEBYSCORE('user:1:request:nums',0,1561456135);
//获取里面剩下请求个数
$request_nums=(int)Redis::zcard(self::TIMELINE_ELEVEL_KEY);
if($request_nums >= 10){
//加入小黑屋,下次再进来就要锁定判断
}
...
因为我们不是单纯记录数值,而是会将请求时间记录下来,那么随着时间推移,我们的请求数统计是不会断代的。