top-k-frequent-ip-in-one-hour

实时输出最近一个小时内访问频率最高的10IP,要求:

  • 实时输出
  • 从当前时间向前数的1个小时
  • QPS可能会达到10W/s

这道题乍一看很像Top K频繁项,是不是需要Lossy CountCount-Min Sketch之类的算法呢?

其实杀鸡焉用牛刀,这道题用不着上述算法,请听我仔细分析。

  1. QPS10/秒,即一秒内最高有10万个请求,那么一个小时内就有100000*3600=360000000≈$$2^{28.4}$$,向上取整,大概是$$2^{29}$$个请求,也不是很大。我们在内存中建立3600HashMap<Int,Int>,放在一个数组里,每秒对应一个HashMapIP地址为key,出现次数作为value。这样,一个小时内最多有$$2^{29}$$pair,每个pair8字节,总内存大概是$$2^{29} \times 8=2^{32}$$字节,即4GB,单机完全可以存下。

  2. 同时还要新建一个固定大小为10的小根堆,用于存放当前出现次数最大的10IP。堆顶是10IP里频率最小的IP

  3. 每次来一个请求,就把该秒对应的HashMap里对应的IP计数器增1,并查询该IP是否已经在堆中存在,

    • 如果不存在,则把该IP3600HashMap的计数器加起来,与堆顶IP的出现次数进行比较,如果大于堆顶元素,则替换掉堆顶元素,如果小于,则什么也不做
    • 如果已经存在,则把堆中该IP的计数器也增1,并调整堆
  4. 需要有一个后台常驻线程,每过一秒,把最旧的那个HashMap销毁,并为当前这一秒新建一个HashMap,这样维持一个一小时的窗口。

  5. 每次查询top 10IP地址时,把堆里10IP地址返回来即可。

以上就是该方案的全部内容。

有的人问,可不可以用"IP +时间"作为key,把所有pair放在单个HashMap里?如果把所有数据放在一个HashMap里,有两个巨大的缺点,

  • 4步里,怎么淘汰掉一个小时前的pair呢?这时候后台线程只能每隔一秒,全量扫描这个HashMap里的所有pair,把过期数据删除,这是线性时间复杂度,很慢。
  • 这时候HashMap里的key存放的是"IP +时间"组合成的字符串,占用内存远远大于一个int。而前面的方案,不用存真正的时间,只需要开一个3600长度的数组来表示一个小时时间窗口。
上一页