需求

  • 客户需要查询 5 分钟为单位的流维度的实时带宽

问题

  • 直播系统会每隔 5 分钟将每条流的带宽等信息汇报到 CDN 的边缘节点上,如果我们将每条流的上报都直接转发给后端系统,那么请求数就会成倍的增加,后端系统会承受不住。所以我们需要做的就是将数据聚合后再进行汇报,或者达到一定的量,比如 1M,或者是 5 分钟时间达到。
  • 目前边缘节点的业务是用 ngx_lua 实现的,最先想到的方案就是通过 ngx.shared.dict 来实现。

方案

  • 我最早想到的方案是设置 1m 的共享内存,每次直播系统 post 请求过来的时候尝试往同一个 key 的共享内存里追加数据,采用 safe_set 的方式,如果返回 “no memory” 的话就说明共享内存已经满了,就可以取出数据给后端系统发数据了。但是这里有个问题是每次我更新这个 key 的数据时都需要先进行 decode 再 encode,而 decode 和 encode 的操作是比较耗 cpu 的,直播系统在 5 分钟到点的时候就会让 nginx 的 cpu 走高,影响正常的业务请求。所以第一个方案被我否定了。
  • 第二个方案自然想着要避开瞬间的 decode 和 encode,于是我想到针对每次的请求设置不同的 key,这样每次直播系统 post 过来的时候只需要 safe_set 就行了,但是当请求的流特别多的时候,那 key 就相应的增加了,当需要向后端系统发送请求的时候,此时需要遍历所有的 key,然后取出值组装后再发给后端系统。如果 key 特别多的话,遍历的时候就会阻塞该共享内存一定时间导致请求失败。
  • 第三个方案就是采用 lpushlpop 来进行存取,每次的 lpushlpop 都是针对同一个 key 进行的,而且也不需要频繁的进行 decode 和 encode,正好可以用在这个方案中。不过 lpushlpop 是 ngx_lua 0.10.6 后才支持的,唯一要做的就是适配好 nginx 和 ngx_lua 的版本兼容问题。

实现

  • 上述方案对比后发现第三个方案比较合适,至于实现就比较简单了。5 分钟的定时任务就通过 ngx.timer.at 来做;buffer 的方案就通过 lpushlpop 来实现了。