最近学到的限流知识

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 笔记

、限流基础知识介绍


为啥要限流,相信就不用我多说了。

  • 比如,我周末去饭店吃饭,但是人太多了,我只能去前台拿个号,等号码到我的时候才能进饭店吃饭。如果饭店没有限流怎么办?一到饭点,人都往里冲,而饭店又处理不了这么多人流,很容易就出事故(饭店塞满了人,无路可走。饭店的工作人员崩溃了,处理不过来)
  • 回到代码世界上也是一样的,服务器能处理的请求数有限,如果请求量特别大,我们需要做限流(要么就让请求等待,要么就把请求给扔了)

5.jpg

在代码世界上,限流有两种比较常见的算法:

  • 令牌桶算法
  • 漏桶算法


1.1 什么是漏桶算法


比如,现在我有一个桶子,绿色那块是我能装水的容量,如果超过我能装下的容量,再往桶子里边倒水,就会溢出来(限流):

6.png

我们目前可以知道的是:

  • 桶子的容量是固定的(是图上绿色那块)
  • 超出了桶子的容量就会溢出(要么等待,要么直接丢弃)

OK,现在我们在桶子里挖个洞,让水可以从洞子里边流出来:

7.png

桶子的洞口的大小是固定的,所以水从洞口流出来的速率也是固定的

所以总结下来算法所需的参数就两个:

  • 桶子的容量
  • 漏水的速率

漏桶算法有两种实现:

  1. 不允许突发流量的情况:如果进水的速率大于出水的速率,直接舍弃掉多余的水。比如,我的桶子容量能装100L,但我的桶子出水速率是10L/s。此时,如果现在有100L/s的水进来,我只让10L的水进到桶子,其余的都限流。(限定了请求的速度
  2. 允许一定的突发流量情况:我的桶子能装100L,如果现在我的桶子是空的,那么这100L的水都能瞬间进我的桶子。我以10L/s的速率将这些水流出,如果还有100L的水进来,只能限流了。

经过上面的分析我们就知道:

漏桶算法可以平滑网络上的突发流量(因为漏水的速率是固定的)


1.2 什么是令牌桶算法


现在我有另外一个桶子,这个桶子不用来装水,用来装令牌:

8.png

令牌会一定的速率扔进桶子里边,比如我1秒扔10个令牌进桶子:

9.png

桶子能装令牌的个数有上限的,比如我的桶子最多只能装1000个令牌。

每个请求进来,就会去桶子拿一个令牌

  • 比如这秒我有1001个请求,我就去桶子里边拿1001个令牌,此时可能会出现两种情况:
  • 桶子里边没有1001个令牌,只有1000个,那没拿到令牌的请求只能被阻塞了(等待)
  • 桶子里边有1001个令牌,所有请求都可以执行。

10.png

令牌桶算法支持网络上的突发流量

漏桶和令牌桶的区别:从上面的例子估计大家也能看出来了,漏桶只能以固定的速率去处理请求,而令牌桶可以以桶子最大的令牌数去处理请求


二、RateLimiter使用


RateLimiter是Guava的一个限流组件,我这边的系统就有用到这个限流组件,使用起来十分方便。

引入pom依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>

RateLimiter它是基于令牌桶算法的,API非常简单,看以下的Demo:

public static void main(String[] args) {
        //线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        //速率是每秒只有3个许可
        final RateLimiter rateLimiter = RateLimiter.create(3.0);
        for (int i = 0; i < 100; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可
                        rateLimiter.acquire();
                        System.out.println("Accessing: " + no + ",time:"
                                + new SimpleDateFormat("yy-MM-dd HH:mm:ss").format(new Date()));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            //执行线程
            exec.execute(runnable);
        }
        //退出线程池
        exec.shutdown();
    }

我们可以从结果看出,每秒只能执行三个:

11.png

三、分布式限流

RateLimiter是一个单机的限流组件,如果是分布式应用的话,该怎么做?

可以使用Redis+Lua的方式来实现,大致的lua脚本代码如下:

local key = "rate.limit:" .. KEYS[1] --限流KEY
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
  return 0
else  --请求数+1,并设置1秒过期
  redis.call("INCRBY", key,"1")
   redis.call("expire", key,"1")
   return current + 1
end

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;
}

解释:

  • Java代码传入key和最大的限制limit参数进lua脚本
  • 执行lua脚本(lua脚本判断当前key是否超过了最大限制limit)
  • 如果超过,则返回0(限流)
  • 如果没超过,返回1(程序继续执行)
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
缓存 算法 Java
限流算法 - 基本实现
限流算法 - 基本实现
59 0
|
28天前
|
算法 NoSQL Java
服务、服务间接口限流实现
`shigen`是一位坚持更新博客的写手,专注于记录个人成长、分享认知与感动。本文探讨了接口限流的重要性,通过实例分析了在调用第三方API时遇到的“请求过多”问题及其解决方法,包括使用`Thread.sleep()`和`Guava RateLimiter`进行限流控制,以及在分布式环境中利用Redis实现更高效的限流策略。
33 0
服务、服务间接口限流实现
|
消息中间件 算法 Sentinel
只需5分钟,了解常见的四种限流算法
只需5分钟,了解常见的四种限流算法
261 4
|
存储 算法 Java
限流常见的算法有哪些呢?
限流常见的算法有哪些呢?
66 0
|
6月前
|
存储 算法 NoSQL
常见限流算法及其实现
在分布式系统中,随着业务量的增长,如何保护核心资源、防止系统过载、保证系统的稳定性成为了一个重要的问题。限流算法作为一种有效的流量控制手段,被广泛应用于各类系统中。本文将详细介绍四种常见的限流算法、两种常用的限流器工具,从原理、源码的角度进行分析。
431 0
|
6月前
|
缓存 Java 应用服务中间件
常见的限流降级方案
【1月更文挑战第21天】
|
6月前
|
算法 Go API
限流算法~
限流算法~
64 1
|
6月前
|
缓存 算法 NoSQL
常见限流算法解读
常见限流算法解读
|
算法
限流常见的算法有哪些?
常见的限流算法有以下几种:
80 0
|
缓存 算法 网络协议
限流实现2
剩下的几种本来打算能立即写完,没想到一下三个月过去了,很是尴尬。本次主要实现如下两种算法 - 令牌桶算法 - 漏斗算法