访问频率限制——窗口相关算法

上一篇文章讲到了利用令牌桶(token bucket)和漏桶(leaky bucket)算法进行访问频率限制,这些非常通用,但是也有一些问题,怎么解决(更准确说应该是缓解)呢?

回到最初问题的来源,其实只要把所有的请求都记录下来,每次新来一个请求看最近一段时间有没有超标即可。然而,一般并不能这么粗暴的记录每一条记录的时间戳,然后统计,因为内存可能扛不住,CPU可能也扛不住。
于是,有了固定窗口计数(Fixed Window Counter)这样的算法。算法做的就是每小段时间用一个值记录起始时间,另一个值记录个数即可。比如,限制是每1分钟60次,那么就在每个整分点(11:11:00)这样开始计数,每次来请求就增加记录次数,如果超过60次,就意味着频率超标了。然后每个这样的记录的过期时间是1分钟,这样,如果一个用户访问几次之后,不再访问,内存中对应的记录值也会清掉。事实上Github好多实现就是用了这样的算法。
这样的做法,明显有个不合理的部分。那就是频率峰值可能不均分,例如,虽然设置的是1分钟访问5次,但假设访问都集中在11:00:40,这样子,11点11分确实只访问了5次。接下来,11:01:00数据清除,11:01:15秒有5次访问,也符合规范。然而, 11:00:4011:01:15这段时间(不足1分钟),却能访问10次!

中间这段时间,其实超标啦

看来这样的方法也不行,问题在于:

  1. 不能在周期边界暴力清空
  2. 随着时间推移,确实需要清掉一些旧的不用了的数据

基于这两点,于是有了最终比较符合需求的算法:滑动窗口计数(Sliding window counters)。我突然意识到这个方法,是因为前段时间瞄了一下TCP协议(真是牛逼的协议啊),然后网上一查,发现早就被别人提出来了(见文末参考文档)。

算法思路在于,不是单纯地记录每次请求的时间戳(开头说了,可能太耗空间),而是把请求按照一段一段计数。比如, 1分钟5次这样的要求,可以把一分钟划分成60秒,然后每秒只用一个数字表示这一秒有多少个请求。这样,请求再多,也只需要60个数字记录(因为分成了60份)。同理,1小时100次,可以把时间划分成分钟,进行记录。
然后, 每次有新请求来的时候,删除已经没用的数据(超过限定期限的,比如上面说的1小时),然后算出时间戳,计数增加。如果没有超过限制,那么,就设置整个数据(下面示例中的“user_A")的过期时间(如果以后用户都不访问了, 这些数据会在过期之后被回收掉)。

一个用户的记录示例:
"user_A": {"11:00:00": 5, "11:01:00": 3}

份数划分的意义何在呢?显然首先是为了省内存(同时统计也快),其次是为了控制误差。事实上,这个算法是有误差的。比如假设1小时被划分成60份, 也就是每分钟一份,11:00:40的访问会被记录在11点整上, 而12:00:10会被记录在12点整上,这两个整点其实在不同的时间段,也就是说考虑12点整的访问时,已经不用去考虑11点整的访问量了。而实际上,11:00:4012:00:10的相差是不足一个小时的,逻辑上它们的访问量应该算在同一个小时里。

不过,实际中,算法实现的时候,应该能接受这样的误差(如果不能接受,需要在清楚过期数据的时候,注意这样的边界问题,比如额外保留一份小时间段的数据。因为算法本身无法区分11:00:0011:00:40这样的访问)。
这个算法设置好合理的划分(如果用redis,不要超过128,因为效率),自己估算好最大误差值(很显然,极端情况下,依然可以在同一个时间段内允许两倍的访问),应该可以在实际运用中起到比较好的效果了。

说了这么多,我在Github上搜了一下,似乎还没有Golang对应的实现。接下来,我会想办法抽时间实现一下。

更新:
实现过程中,滑动窗口计数的时候,发现太多需要改动一段一段计数的地方。感觉用hash不如序列化然后用string存取快。

实现地址:
restrictor

参考文章:
https://blog.figma.com/an-alternative-approach-to-rate-limiting-f8a06cf7c94c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容