面试官:谈谈为什么要限流,有哪些限流方案?

摘要:当大量用户同时访问一个服务时,系统的承载能力有限,瞬间的高并发请求会导致系统过载,甚至崩溃。比如,某些电商平台在大促期间,突然涌入大量用户请求,直接把服务器压垮。想象一下,访问量大到连服务器的CPU都烧起来了,数据库压力山大,直接崩溃,大家连网站都进不去,那损

今天我们来聊聊一个程序员在工作中几乎每天都会接触到的:限流

虽然这个名字听起来有点抽象,但实际上它无处不在。如果你做过后台开发,特别是处理高并发场景的代码,限流就是你的“好朋友”了。

你可能已经有些了解限流是什么,或者听说过一些限流方案,但你有没有想过,为什么我们要限流呢?而且,限流的方案又有多少种呢?今天就跟我一起掰扯掰扯。

当大量用户同时访问一个服务时,系统的承载能力有限,瞬间的高并发请求会导致系统过载,甚至崩溃。比如,某些电商平台在大促期间,突然涌入大量用户请求,直接把服务器压垮。想象一下,访问量大到连服务器的CPU都烧起来了,数据库压力山大,直接崩溃,大家连网站都进不去,那损失可就大了。

有些接口的资源消耗较大,或者一些特定的业务需要通过计算、存储等方式消耗大量资源。如果没有限流,可能会因为大量请求涌入导致资源浪费,甚至造成服务的其他部分失效。比如,我们的计算服务器已经不堪重负,再加上一个请求,可能就导致整个业务系统崩溃,那就麻烦了。

想象一个高并发场景,所有请求都能平均获得资源处理吗?当然不可能。限流可以保证每个请求都有公平的机会被处理,而不是一堆请求排队,其他人的请求被“饿死”。简而言之,限流可以避免某些请求过于“霸道”,保证整体的健康运行。

如果没有限流,系统可能因为负载过重而变得响应缓慢,用户体验差。相比之下,限流后,系统可以平稳处理请求,虽然可能会延迟一些,但至少不会崩溃,保证了用户的持续访问。就像排队进餐厅,餐厅虽然限了每次进店的人数,但每个进店的用户都能顺利坐下,吃到热乎的饭,服务质量比乱作一团强太多。

限流的方案有很多种,每种方案适用于不同的场景。这里我给大家总结了几种常见的限流方式,让大家了解一下怎么根据实际情况选择合适的方案。

漏桶算法是一种经典的限流算法。它的核心思想是“按照固定速度漏水”,无论多少请求涌入系统,处理的速度是恒定的,超出处理能力的请求会被丢弃。

简单实现:

import java.util.concurrent.atomic.AtomicInteger;public class LeakyBucket { private static final int CAPACITY = 10; // 漏桶容量 private AtomicInteger currentWaterLevel = new AtomicInteger(0); // 当前水位 // 请求处理 public boolean request { if (currentWaterLevel.get 0) { currentWaterLevel.decrementAndGet; } }}

漏桶算法的优点是处理速度恒定,可以有效防止突发流量导致系统崩溃。但它的缺点是请求会丢失,适合对丢失请求不敏感的场景。

令牌桶算法的核心思想是“按照速率生成令牌,只有持有令牌的请求才能执行”。不同于漏桶,令牌桶允许请求的突发流量,但令牌桶中的令牌数量有限,超过令牌数量的请求就会被拒绝。

简单实现:

import java.util.concurrent.atomic.AtomicInteger;public class TokenBucket { private static final int CAPACITY = 10; // 令牌桶容量 private static final int RATE = 2; // 令牌生成速度 private AtomicInteger tokens = new AtomicInteger(0); // 当前令牌数量 public TokenBucket { // 启动令牌生成线程 new Thread( -> { while (true) { try { Thread.sleep(1000); // 每秒生成一次令牌 if (tokens.get 0) { tokens.decrementAndGet; return true; // 请求通过 } return false; // 请求被拒绝 }}

令牌桶算法的优点是能够处理突发流量,并且能够按流量控制请求的速率。适用于需要平滑流量的场景,比如接口限流。

计数器限流是一种简单的方案,核心思想就是在固定时间窗口内统计请求的数量,如果超出了预设的限制,就拒绝后续请求。

简单实现:

import java.util.concurrent.atomic.AtomicInteger;public class FixedWindowRateLimiter { private static final int MAX_REQUESTS = 5; // 最大请求数 private AtomicInteger requestCount = new AtomicInteger(0); // 请求计数 private long windowStartTime = System.currentTimeMillis; // 当前窗口开始时间 public boolean request { long currentTime = System.currentTimeMillis; if (currentTime - windowStartTime > 1000) { // 如果超过1秒钟,重新计数 windowStartTime = currentTime; requestCount.set(0); } if (requestCount.get

计数器限流非常简单直观,适合于场景中请求数较为均匀的情况。但它的缺点是如果请求过于集中,容易造成流量冲击。

滑动窗口限流是在计数器限流的基础上进行改进,使用多个子窗口动态统计请求数。它能够较为精细地处理突发流量,同时避免了计数器限流的“时间窗口爆发”问题。

简单实现:

import java.util.LinkedList;public class SlidingWindowRateLimiter { private static final int MAX_REQUESTS = 5; // 最大请求数 private LinkedList requestTimes = new LinkedList; // 请求时间记录 public boolean request { long currentTime = System.currentTimeMillis; long windowStartTime = currentTime - 1000; // 1秒钟内的时间窗口 // 删除过期的请求记录 while (!requestTimes.isEmpty && requestTimes.getFirst

滑动窗口比计数器更加精细,适合有突发流量的场景,能够灵活地平滑请求。它既能防止请求过多集中在一个时刻,又能保证平滑的流量处理。

限流是一个保证系统稳定、提高用户体验的关键技术。不同的限流算法适用于不同的场景,比如漏桶适合恒定流量、令牌桶适合突发流量、计数器适合简单的流量控制,而滑动窗口则更适合高并发的平滑限流。选择合适的限流方案,可以确保我们的系统在高并发下依然保持稳定,避免崩溃,提升整体服务的质量。

最后,限流虽然是一个非常重要的工具,但它本身并不是万能的。有时候我们需要结合其他技术手段,比如降级、熔断等,一起来保障系统的健壮性。

来源:麻辣小王子

相关推荐