限流器设计
为什么需要限流器
在我们的Web系统中,由于服务器处理效率有限,且部分服务需要消耗资金等资源,因此,为了保障系统正常运行,我们需要对客户端或服务端发送流量的速率进行控制。比如,在我的智能BI平台中,用户上传Excel文件并对文件进行解析需要花费时间和占用内存空间,且因为引入了大模型资源,每次进行分析请求都需要向服务提供者支付费用。如果不对用户的请求加以限制,极有可能因为短时间内大量上传解析导致服务宕机或消耗大量资费。
总的来说,限流操作能够带来以下三点好处:
预防服务器过载。通过限流可以过滤由机器人或用户不当行为造成的过量请求。
降低成本。限制过量请求意味着所需的服务器资源更少,且可以把更多资源分配给更高优先级的API。对于第三方按量付费的服务,限制这些请求的数量对降低成本至关重要。
预防拒绝服务攻击(Denial of Service, DoS)导致的资源耗尽问题。服务器的QPS是有限的,如果被恶意用户的请求消耗掉,就会导致其他用户无法正常使用服务。
在此,我们可以引入限流器的概念:如果请求数量超过限流器设定的阈值,请求就会被限流器拦截。
架构设计
我们从Web开发中常用的前后端分离模式出发,来探讨限流器的应用。
在哪里进行限流
在前后端分离的Web项目中,前后端通常是分开部署的。后端服务器负责对外暴露API接口,而前端服务器则提供用户界面,并帮助用户向后端服务器发起请求,渲染最终结果。因此,从直观上看,我们有两个选择:在前端或后端实现限流器。
然而,在前端实现限流通常并不可靠。因为后端服务接口是通过网络对外暴露的,用户并不一定必须通过前端来向后端发起请求。开发人员或攻击者可以伪造前端请求,从而绕过前端的限流机制。
因此,在后端实现限流操作是更为常见的方案。如下图所示,我们可以在后端添加一个限流器组件或服务,来对其他API进行限流。
虽然在后端实现限流操作是常见的方案,但它也存在一个问题:限流器组件可能会与其他业务组件产生较高的耦合度。这种耦合度不仅会降低系统的灵活性和可维护性,还可能增加开发的成本。因为一旦限流器组件需要修改或升级,就可能影响到与之关联的其他业务组件,从而增加开发和维护的复杂性。
为了解决这个问题,还有一种更为优雅的方案:使用中间件进行限流。中间件位于客户端和后端服务器之间,可以实现对API请求的限制和过滤。也就是说,在流量到达后端服务器之前,中间件会先对请求进行判断和限流操作,从而避免直接让流量作用在后端服务器上。
使用中间件进行限流的好处在于,它可以将限流逻辑与业务逻辑分离,降低组件间的耦合度,提高系统的灵活性和可维护性。同时,中间件还可以对流量进行预处理和过滤,减轻后端服务器的压力,提高系统的整体性能和稳定性。
如下图所示,通过使用中间件进行限流,我们可以有效地控制API请求的流量,保护后端服务器免受过量请求的冲击。这种方案不仅提高了系统的可扩展性和可维护性,还为开发人员提供了更为灵活和高效的限流手段。
随着微服务架构的不断成熟,API网关已成为微服务生态中不可或缺的一部分。它不仅负责处理外部请求与内部服务之间的通信,还能有效地集成限流功能,通过这一中间件对流量进行精准控制。在API网关中实现限流,能够确保后端服务免受过量请求的冲击,从而提升系统的整体性能和稳定性。
在选择限流器的部署位置时,我们需要全面考虑项目的技术栈、资源分配、业务优先级以及长期发展目标。正确的部署策略能够确保限流器既能有效发挥作用,又不会对系统性能造成不必要的负担。
然而,从零开始构建限流器往往是一项复杂且耗时的任务。幸运的是,市面上已经涌现出众多成熟的限流应用和解决方案,它们提供了丰富的功能和灵活的配置选项,大大简化了限流器的开发工作。因此,在设计和实现限流器时,我们可以积极参考这些现成的应用和方案,以节省时间和精力,并降低潜在的技术风险。
常见限流算法
除了限流器的部署位置外,限流算法的选择同样至关重要。不同的限流算法适用于不同的业务场景,它们能够在保证系统稳定性的同时,最大限度地满足业务需求。接下来,我将简要介绍目前流行的 4 种限流算法:
固定窗口计数器算法(Fixed Window Counter)
滑动窗口计数器算法(Sliding Window Counter)
漏桶算法(Leaking Bucket)
令牌桶算法(Token Bucket)
固定窗口计数器算法(Fixed Window Counter)
固定窗口计数器算法的原理大致如下:
按照时间轴划分出固定大小的时间窗口,并为每一个时间窗口分配一个计数器
在当前时间段内,每有一个请求到达,就让计数器的值加一
如果在当前时间窗口内,计数器的值到达了设定的阈值,新来的请求就会被丢弃,直到开始一个新的时间窗口
如下图,若设定时间窗口长度为 1 秒,且每秒允许的最大请求数为 3,那么可能会出现如下情况:在第 2 秒至第 3 秒的时间段内,共有 5 个请求到达,其中3个被成功处理,而剩下的2个则被丢弃。然而,这种看似合理的处理方式实则隐藏着固定窗口计数器算法的一个显著缺陷——它无法有效应对时间窗口边界上的流量突发情况。想象一下,在第 1 秒即将结束至第2秒开始的极短时间内,若突然涌入 3 个请求,而紧接着在第2秒开始后的瞬间又迎来另外 3 个请求,尽管按照每秒的窗口划分来看,每秒的请求数均未超过 3 个,但在第 2 秒左右的极短时间段内,系统实际上已经接收到了 6 个请求,这远远超出了预期的每秒 3 个请求的限制。
相对而言,固定窗口计数器算法的优势在于:
内存占用效率高,资源消耗较少;
算法逻辑简单明了,易于理解和实现;
在某些特定场景下,如每个时间窗口内请求次数固定的需求中,该算法能够很好地满足要求。
然而,其不足之处也同样明显:在时间窗口边界处,若流量出现激增,则可能导致实际请求数远超预设阈值,此外,如何合理设定时间窗口的长度和计数器的阈值也是使用该算法时面临的一大挑战。
滑动窗口计数器算法(Sliding Window Counter)
滑动窗口计数器算法依据时间轴将时间切割成一系列固定长度的窗口,但不同于固定窗口计数器算法的是,它根据前面窗口内请求的平均速率来推算每个窗口应允许的最大请求数。这种机制使得算法能够更灵活地适应流量的动态变化。
在每个时间窗口内,算法会维护一个计数器来记录接收到的请求数量。然而,与固定阈值不同,滑动窗口计数器算法会根据前面一个或多个窗口内的请求平均速率来动态调整当前窗口的阈值。这意味着,如果前面的窗口内请求数量较多,那么当前窗口的阈值可能会相应提高,以容纳更多的请求;反之,如果前面的窗口内请求数量较少,那么当前窗口的阈值可能会降低,以防止系统过载。
当时间推进到下一个时间窗口时,旧的窗口会被淘汰,而新的窗口则会被创建,并继续根据前面的请求平均速率来推算允许的最大请求数。这样,滑动窗口计数器算法就能够根据流量的实际情况进行动态调整,从而更有效地控制流量。
相对而言,滑动窗口计数器算法的优势在于:
能够根据流量的动态变化进行自适应调整,提高了流量控制的灵活性和准确性;
适用于需要动态调整流量限制的场景,如在线服务、实时数据处理等;
通过动态调整阈值,能够更有效地防止系统过载和请求丢失。
然而,其不足之处也在于算法实现的复杂性。由于需要根据前面的请求平均速率来推算当前窗口的阈值,因此滑动窗口计数器算法可能需要额外的计算资源和数据结构来支持这一功能。此外,合理设定时间窗口的长度和用于计算平均速率的窗口数量也是使用该算法时需要考虑的重要因素。
漏桶算法(Leaking Bucket)
漏桶算法可以很好地解决前面提到的流量突刺问题,它的核心思路是以恒定的速率来处理请求。通常可以把它视为一个先进先出的队列,大致实现思路如下:
当一个请求到达的时候,检查队列是否已满,如果没有,就把请求添加到队列中
如果队列已满,就丢弃请求
定时从队列中取出请求并进行处理
如下图:
漏桶算法需要考虑的主要有两个参数:
桶大小:即队列的大小,队列中存储到来的请求。
出队速度:定义了每秒从桶中取出的请求个数。
漏桶算法的优点有两点:
因为队列大小有限,它的内存使用会更加高效
因为按固定速率处理请求,,它能平滑网络上的突发流量
但对应的,它也有缺点:在某些情况下,漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。
令牌桶算法(Token Bucket)
令牌桶算法则很好地综合了前面几种算法的特点,它是现在网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。
令牌桶算法的大致工作原理如下:
令牌桶是一个固定大小的容器,令牌会以恒定速率被注入到令牌桶中,当桶中令牌数量超出上限,令牌就会溢出被抛弃
每个请求到达都会尝试从桶中获取令牌,如果此时桶中有令牌,请求成功获取到令牌,就可以通过
如果请求没有获取到令牌,这个请求就会被抛弃
如下图所示:
令牌桶算法需要考虑的主要也是两个参数:
桶大小:即桶中最多能有多少个令牌
注入令牌的速率:定义了每秒放入令牌桶中令牌的个数
因为这些设计,令牌桶拥有以下优势:
令牌桶算法能够在限制数据的平均传输速率的同时,还允许某种程度的突发传输。
令牌桶算法更适合处理具有突发特性的流量。
因此令牌桶算法通常用于限制被访问的流量,以保护自身系统。它允许系统在面对瞬时大流量时,能够在短时间内请求并拿到大量令牌,从而处理瞬时流量。像在我的 BI 平台项目中,针对用户调用相关分析接口的限流操作,底层使用的就是令牌桶算法。
总结
在Web系统中,限流器的存在至关重要。它不仅能够有效预防服务器过载,保障系统正常运行,还能显著降低运营成本,防止因恶意请求或流量突刺导致的资源耗尽问题。通过对前后端分离架构中限流器部署位置的分析,我们明确了在后端实现限流操作的必要性,并进一步探讨了使用中间件或API网关进行限流的优雅方案。这些措施不仅提高了系统的灵活性和可维护性,还为开发人员提供了更为高效和可靠的限流手段。
在选择限流算法时,我们介绍了固定窗口计数器算法、滑动窗口计数器算法、漏桶算法以及令牌桶算法等四种常见算法,并详细分析了它们的优缺点及适用场景。这些算法各具特色,能够在不同业务场景下发挥重要作用,帮助系统实现精准的流量控制。
综上所述,限流器作为Web系统中的重要组成部分,对于保障系统稳定性、降低运营成本以及提高用户体验具有重要意义。通过合理选择限流器的部署位置和限流算法,我们能够构建出更加健壮和高效的Web系统,以应对日益复杂的网络环境和用户需求。
希望这篇分享能为你带来启发!如果你有任何问题或建议,欢迎在评论区留言,与我共同交流探讨。