Rate limiter + Datadog

感觉这个可能有用:

例如我们有这样一个表capture event的发生时间:

12:05:00

10

12:05:01

15

12:05:02

14

12:05:03

16

因为一小时有60分钟,所以存分钟为精度,然后一个list存每分钟发生的次数。

12:05

10,15,14,16...

12:06

Senario

  • 根据请求的特征进行限制(feature选取, 例如:ip,user_id, etc)

  • 无需太小的粒度,例如,半分钟内允许2次=》每分钟内4次。

Service:就一个service

Storage:这个是重点

  • 数据不用持久化,例如你要limit5分钟内的,那么5分钟前的数据可以扔掉。

  • 这个系统写和读都要搞笑,所以选in memory storage就ok啦

感觉这里有两种可考虑的存法:用memcache

以event+feature+timestamp作为key,然后呢,我们按精度分bucket来存。例如我要限制每分钟,每小时,每天的访问次数。就存3份,每份用比这个单位小时间来存,例如,我们精度选1分钟,那么就按照秒来存。每个格子代表1秒钟,总共60个格子;每小时60分钟,小时的bucket我们存60个格子,每个表示1分钟;每天24个小时,所以有24个格子,每格是1小时。

我们还需要把格子的ttl设成对应的长度,然后让cache处理过时的数据。

每次我们查询是否over limit的时候,就把规定的时间精度里的格子加一次,看是否超过,超过了,就alert。因为只有60个格子,所以linear加一次也不慢。另外一个需要注意的地方是读和写要atomic,别一边读一遍改了,这样就会出现race condition。但这也得看我们的limit是不是很hard的limit,如果可以容错,那么这个也不是特别大的问题。

听说cache可以bulk提取一堆key的value,因为memcache啥的始终要经过网络,虽然是内网访问速度快,但也是有延迟的。所以可以把key都生成好,然后bulk request一次把60个值都拿过来。

另外一种是用redis来存:

感觉可以用event+feature来做key,然后把发生的次数按秒钟append到list后面。读取的时候可以一条list,一次拿过来,那样的话就一次网络访问好了。看有没有over limit还是一条拿过来,逐个加和来看。这样存的话,感觉比较像算法题里的做法359 Logger Rate Limiter的rockset面试变种,得purge链表里的旧数据,可以在读或写的时候执行一下来purge。

下面是datadog/google analytic/scuba这类可以查看某段时间什么发生了多少次,有time serial的系统

Senario

  • 用户对某个链接访问次数

  • 可以按最近X小时/X天/X月 etc

  • 假设被请求约2k的QPS

Service:就一个service

Storage:这个是重点

  • 这个系统是一个写很多,读很少的系统。因为每次用户点一下就要写一条记录

  • 而且这个需要持久化,因为要按不同的时间来查数目,(L家的系统设计是查top k)

  • 假设用noSql来存

    • key就是你要记录次数的东西,可以是网址,exception,某metric

    • value就访问/发生次数

  • 我们还是想rate limiter那样分级存储。例如今天的数据每钟写一条,昨天的我们就存少一点,每5分钟写一条,上个月的,每天存一条以此类推。

  • 我们这里tradeoff是,减低精度,省去存储空间

  • 我们要定期跑脚本把旧数据瘦身,例如把昨天的集中一下,写成一条

具体存成这样:(越是靠近现在的存得越仔细)

时间

间隔

次数

2016/02/10 15:

1h

300

...

2017/03/10 15:23

1m

30

2017/03/10 15:24

1m

39

...

2017/04/01 14:39:50

1s

3

2017/04/01 14:39:51

1a

5

这个时候,当我们需要查询这个event在最近一个月里发生了多少次,我们就到数据库里按时间range查出最近一个月的row,然后加起来。因为我们有做数据瘦身,所以旧数据已经一定程度上加好了。

另外一个考虑是,怎样减少写操作。因为每次用户操作都会产生一堆metric,如果每次都往数据库写,会造成很多写。所以通常是在server上有一个小程序跑着,收集一段时间然后再打包发给这个server。这样就可以大大减少数据库的写了。

感觉L家的top k可以用Cassandra,row_key是那个要记录的event/exception,然后column key呢就是那些时间,这样就可以按照时间来range query啦。

Last updated