新来个技术总监,把限流实现的那叫一个优雅,佩服( 二 )


漏桶算法有以下特点:

  • 漏桶具有固定容量,出水速率是固定常量(流出请求)
  • 如果桶是空的,则不需流出水滴
  • 可以以任意速率流入水滴到漏桶(流入请求)
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)
漏桶限制的是常量流出速率(即流出速率是一个固定常量值),所以最大的速率就是出水的速率,不能出现突发流量 。
令牌桶令牌桶算法(Token Bucket)是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法 。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送 。
新来个技术总监,把限流实现的那叫一个优雅,佩服

文章插图
 
我们有一个固定的桶,桶里存放着令牌(token) 。一开始桶是空的,系统按固定的时间(rate)往桶里添加令牌,直到桶里的令牌数满,多余的请求会被丢弃 。当请求来的时候,从桶里移除一个令牌,如果桶是空的则拒绝请求或者阻塞 。
令牌桶有以下特点:
  • 令牌按固定的速率被放入令牌桶中
  • 桶中最多存放 B 个令牌,当桶满时,新添加的令牌被丢弃或拒绝
  • 如果桶中的令牌不足 N 个,则不会删除令牌,且请求将被限流(丢弃或阻塞等待)
令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌...),并允许一定程度突发流量,所以也是非常常用的限流算法 。
redis + Lua 分布式限流单机版限流仅能保护自身节点,但无法保护应用依赖的各种服务,并且在进行节点扩容、缩容时也无法准确控制整个服务的请求限制 。
而分布式限流,以集群为维度,可以方便的控制这个集群的请求限制,从而保护下游依赖的各种服务资源 。
分布式限流最关键的是要将限流服务做成原子化,我们可以借助 Redis 的计数器,Lua 执行的原子性,进行分布式限流,大致的 Lua 脚本代码如下:
local key = "rate.limit:" .. KEYS[1] --限流KEYlocal limit = tonumber(ARGV[1])--限流大小local current = tonumber(redis.call('get', key) or "0")if current + 1 > limit then --如果超出限流大小return 0else--请求数+1,并设置1秒过期redis.call("INCRBY", key,"1")redis.call("expire", key,"1")return current + 1end限流逻辑(JAVA 语言):
public static boolean accquire() throws IOException, URISyntaxException {Jedis jedis = new Jedis("127.0.0.1");File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua");String luaScript = FileUtils.readFileToString(luaFile);String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒String limit = "5"; // 最大限制List<String> keys = new ArrayList<String>();keys.add(key);List<String> args = new ArrayList<String>();args.add(limit);Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数return result == 1;}聊聊其它上面的限流方式,主要是针对服务器进行限流,我们也可以对容器进行限流,比如 Tomcat、Nginx 等限流手段 。
Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待执行;而 Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数 。
对于 Java 语言,我们其实有相关的限流组件,比如大家常用的 RateLimiter,其实就是基于令牌桶算法,大家知道为什么唯独选用令牌桶么?
【新来个技术总监,把限流实现的那叫一个优雅,佩服】在实际的限流场景中,我们也可以控制单个 IP、城市、渠道、设备 id、用户 id 等在一定时间内发送的请求数;如果是开放平台,需要为每个 Appkey 设置独立的访问速率规则 。
限流对比下面我们就对常用的线程策略,总结它们的优缺点,便于以后选型 。
计数器:
  • 优点:固定时间段计数,实现简单,适用不太精准的场景;
  • 缺点:对边界没有很好处理,导致限流不能精准控制 。
滑动窗口:
  • 优点:将固定时间段分块,时间比“计数器”复杂,适用于稍微精准的场景;
  • 缺点:实现稍微复杂,还是不能彻底解决“计数器”存在的边界问题 。
漏桶:


推荐阅读