限流必然是颇有价值的,在系统资源不足时面对外部世界的不肯定性(突发流量,超预期的用户)而造成的一种自我保护机制。 可是价值感是很低的,由于99.99%的时候系统老是工做在安全线之下,甚至一年到头都碰不到一次撞线的机会。这就比如法律,它始终存在,可是大部分时候对于大多数人它几乎不存在,或者说感知不到它的存在。算法
一个软件系统每每会存在不少隐藏的bug,最经常使用的功能bug每每不多。不经常使用的功能由于长时间不被人关注缺乏重现的机会会一直隐藏在那里乘机爆发。并且随着软件系统的迭代更新,不受关注的功能极有可能被测试人员忽视从覆盖测试中被遗忘了。结果一旦遇到了突发的场景(墨菲定律),这一段被忽视的存在bug的代码逻辑被唤醒的,而后就会致使系统出错甚至奔溃。限流功能就是这些不被关注的功能之一。数据库
为了解决限流的价值感问题,工程师们须要对限流功能进行周期性演练,须要使用单元测试和压力测试进行屡次重演。总之就是想尽各类办法来触发限流功能撞线,以重现那一段生产环境几乎不会被执行的 else 逻辑。安全
业界较为常见的两个基础限流算法是漏斗算法和令牌算法,这两个算法大同小异。漏斗算法能够理解为每个请求都会消耗必定的空气,而漏斗里的空气是有限的,经过漏水的方式得到空气的速率也是有限的。令牌算法能够理解为每个请求都会消耗一个令牌,而令牌桶生产令牌的能力是有限的,存放令牌的桶容量也是有限的。网络
生产环境的请求QPS在曲线上看是平滑的,由于大多数统计系统都采用了平滑算法(一段时间内的均值),可是在实际的运行过程当中会有必定的随机性和波动性,会有突发的一些离群点,这个通常被称之为突发流量。分布式
限流算法须要考虑这种突发流量,它应该能够短时间内容忍,容忍也是有上限的,就是容忍时间必须很短。上面两个算法都具备这个容忍能力,这个容忍就体如今漏斗中空气的积蓄,令牌桶中令牌的积蓄,积蓄耗光就达到了容忍的上限。性能
若是你的应用是单个进程,那么限流就很简单,请求的计数算法均可以在内存里完成。限流算法几乎没有损耗,都是纯内存的计算。可是互联网世界的应用都是多节点的分布式的,每一个节点的请求处理能力还不必定同样。咱们须要考虑的是这多个节点的总体请求处理能力。单元测试
单个进程的处理能力是1w QPS 并不意味着总体的请求处理能力是 N * 1w QPS,由于总体的处理能力还会有共享资源的能力限制。这个共享资源通常是指数据库,也能够是同一台机器的多个进程共享的 CPU 和 磁盘 等资源,还有网络带宽因素也会制约总体的请求量。测试
这时候请求的计数算法就须要集中在一个地方(限流中间件)来完成。应用程序在处理请求以前都须要向这种集中管理器申请流量(空气、令牌桶)。 每个请求都须要一次网络 IO,从应用程序到限流中间件之间。cdn
好比咱们可使用 Redis + Lua 来实现这个限流功能,可是 Lua 的性能要比 C 弱不少,一般这个限流算法能达到 1w 左右的 QPS就到顶了。还可使用 Redis-Cell 模块,其内部使用 Rust 实现,它能达到 5w 左右的 QPS 也就到极限了。这时候它们都进入了满负荷状态,可是在生产环境中咱们不会但愿它们一直满负荷工做。中间件
一个简单的想法就是将限流的 key 分桶,而后使用 Redis 集群来扩容,让限流的申请指令通过客户端的 hash 分桶后打散的集群的多个几点,借此分散压力。
若是还使用上面的方法那须要的带宽资源和Redis实例也是惊人的。咱们可能须要几十个 Redis 节点,外加上百M(1M * 20 字节 * 8) 的带宽来完成这个工做。
这时咱们必须转换思路,再也不使用这种集中管控的方式来工做了。就比如一个国家人太多了,就要分省、市、县来分别进行管理 —— 放权。
咱们将总体的 QPS 按照权重分散多每一个子节点,每一个字节点在内存中进行单机限流。若是每一个节点都是对等的,那么每一个子节点就能够分得 1/n 的 QPS。
它的优势在于分散了限流压力,将 IO 操做变成纯内存计算,这样就能够很轻松地应对超高的 QPS 限流。可是这也增长了系统的复杂度,须要有一个集中的配置中心来向每一个子节点来分发 QPS 阈值,须要每一个应用字节点向这个配置中心进行注册,须要有一个配置管理后台来对系统的 QPS 分配进行管理。
若是没有一个完善易用成熟的开源软件的话,这样的一个控制中心服务和SDK每每须要一个小型团队来完成。这对于中小型企业来讲每每是承受不起的,再想一想限流的价值感如此之低,这种可能性就更加微乎其微了。