原文地址:http://blog.51cto.com/zhweizhi/2063157

在开发 api 网关的时,做过一些简单的限流,比如说静态拦截和动态拦截;静态拦截说白了就是限流某一个接口在一定时间窗口的请求数。用户可以在系统上给他们的接口配置一个每秒最大调用量,如果超过这个限制,则拒绝服务此接口,而动态拦截其实也是基于静态拦截进行改进,我们可以依据当前系统的响应时间来动态调整限流的阈值,如果响应较快则可以把阈值调的大一些,放过更多请求,反之则自动降低限流阈值,只使少量请求通过。

其实这就是一个很简单的限流方式。但是因为这些场景在我们开发的时候经常遇到,所以在这里用 OpenResty 大概实现一些常见的限流方式。(此处使用OpenResty1.13.6.1版本自带lua-resty-limit-traffic模块 ,实现起来更为方便)

限制接口总并发数

场景:
按照 ip 限制其并发连接数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
lua_shared_dict my_limit_conn_store 100m;
...
location /hello {access_by_lua_block {local limit_conn = require "resty.limit.conn"-- 限制一个 ip 客户端最大 1 个并发请求-- burst 设置为 0,如果超过最大的并发请求数,则直接返回503,-- 如果此处要允许突增的并发数,可以修改 burst 的值(漏桶的桶容量)-- 最后一个参数其实是你要预估这些并发(或者说单个请求)要处理多久,以便于对桶里面的请求应用漏桶算法local lim, err = limit_conn.new("my_limit_conn_store", 1, 0, 0.5)              if not lim thenngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", err)return ngx.exit(500)endlocal key = ngx.var.binary_remote_addr-- commit 为true 代表要更新shared dict中key的值,-- false 代表只是查看当前请求要处理的延时情况和前面还未被处理的请求数local delay, err = lim:incoming(key, true)if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit req: ", err)return ngx.exit(500)end-- 如果请求连接计数等信息被加到shared dict中,则在ctx中记录下,-- 因为后面要告知连接断开,以处理其他连接if lim:is_committed() thenlocal ctx = ngx.ctxctx.limit_conn = limctx.limit_conn_key = keyctx.limit_conn_delay = delayendlocal conn = err-- 其实这里的 delay 肯定是上面说的并发处理时间的整数倍,-- 举个例子,每秒处理100并发,桶容量200个,当时同时来500个并发,则200个拒掉-- 100个在被处理,然后200个进入桶中暂存,被暂存的这200个连接中,0-100个连接其实应该延后0.5秒处理,-- 101-200个则应该延后0.5*2=1秒处理(0.5是上面预估的并发处理时间)if delay >= 0.001 thenngx.sleep(delay)end}log_by_lua_block {local ctx = ngx.ctxlocal lim = ctx.limit_connif lim thenlocal key = ctx.limit_conn_key-- 这个连接处理完后应该告知一下,更新shared dict中的值,让后续连接可以接入进来处理-- 此处可以动态更新你之前的预估时间,但是别忘了把limit_conn.new这个方法抽出去写,-- 要不每次请求进来又会重置local conn, err = lim:leaving(key, 0.5)if not conn thenngx.log(ngx.ERR,"failed to record the connection leaving ","request: ", err)returnendend}proxy_pass http://10.100.157.198:6112;proxy_set_header Host $host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_connect_timeout 60;proxy_read_timeout 600;proxy_send_timeout 600;
}

说明:
其实此处没有设置 burst 的值,就是单纯的限制最大并发数,如果设置了 burst 的值,并且做了延时处理,其实就是对并发数使用了漏桶算法,但是如果不做延时处理,其实就是使用的令牌桶算法。参考下面对请求数使用漏桶令牌桶的部分,并发数的漏桶令牌桶实现与之相似

限制接口时间窗请求数

场景:
限制 ip 每分钟只能调用 120 次 /hello 接口(允许在时间段开始的时候一次性放过120个请求)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
lua_shared_dict my_limit_count_store 100m;
...init_by_lua_block {require "resty.core"
}
....location /hello {access_by_lua_block {local limit_count = require "resty.limit.count"-- rate: 10/min local lim, err = limit_count.new("my_limit_count_store", 120, 60)if not lim thenngx.log(ngx.ERR, "failed to instantiate a resty.limit.count object: ", err)return ngx.exit(500)endlocal key = ngx.var.binary_remote_addrlocal delay, err = lim:incoming(key, true)-- 如果请求数在限制范围内,则当前请求被处理的延迟(这种场景下始终为0,因为要么被处理要么被拒绝)和将被处理的请求的剩余数if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit count: ", err)return ngx.exit(500)end}proxy_pass http://10.100.157.198:6112;proxy_set_header Host $host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_connect_timeout 60;proxy_read_timeout 600;proxy_send_timeout 600;
}

平滑限制接口请求数

场景:
限制 ip 每分钟只能调用 120 次 /hello 接口(平滑处理请求,即每秒放过2个请求)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
lua_shared_dict my_limit_req_store 100m;
....location /hello {access_by_lua_block {local limit_req = require "resty.limit.req"-- 这里设置rate=2/s,漏桶桶容量设置为0,(也就是来多少水就留多少水) -- 因为resty.limit.req代码中控制粒度为毫秒级别,所以可以做到毫秒级别的平滑处理local lim, err = limit_req.new("my_limit_req_store", 2, 0)if not lim thenngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)return ngx.exit(500)endlocal key = ngx.var.binary_remote_addrlocal delay, err = lim:incoming(key, true)if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit req: ", err)return ngx.exit(500)end}proxy_pass http://10.100.157.198:6112;proxy_set_header Host $host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_connect_timeout 60;proxy_read_timeout 600;proxy_send_timeout 600;
}

漏桶算法限流

场景:
限制 ip 每分钟只能调用 120 次 /hello 接口(平滑处理请求,即每秒放过2个请求),超过部分进入桶中等待,(桶容量为60),如果桶也满了,则进行限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
lua_shared_dict my_limit_req_store 100m;
....location /hello {access_by_lua_block {local limit_req = require "resty.limit.req"-- 这里设置rate=2/s,漏桶桶容量设置为0,(也就是来多少水就留多少水) -- 因为resty.limit.req代码中控制粒度为毫秒级别,所以可以做到毫秒级别的平滑处理local lim, err = limit_req.new("my_limit_req_store", 2, 60)if not lim thenngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)return ngx.exit(500)endlocal key = ngx.var.binary_remote_addrlocal delay, err = lim:incoming(key, true)if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit req: ", err)return ngx.exit(500)end-- 此方法返回,当前请求需要delay秒后才会被处理,和他前面对请求数-- 所以此处对桶中请求进行延时处理,让其排队等待,就是应用了漏桶算法-- 此处也是与令牌桶的主要区别既if delay >= 0.001 thenngx.sleep(delay)end}proxy_pass http://10.100.157.198:6112;proxy_set_header Host $host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_connect_timeout 60;proxy_read_timeout 600;proxy_send_timeout 600;
}

令牌桶算法限流

令牌桶其实可以看着是漏桶的逆操作,看我们对把超过请求速率而进入桶中的请求如何处理,如果是我们把这部分请求放入到等待队列中去,那么其实就是用了漏桶算法,但是如果我们允许直接处理这部分的突发请求,其实就是使用了令牌桶算法。

场景:
限制 ip 每分钟只能调用 120 次 /hello 接口(平滑处理请求,即每秒放过2个请求),但是允许一定的突发流量(突发的流量,就是桶的容量(桶容量为60),超过桶容量直接拒绝

这边只要将上面漏桶算法关于桶中请求的延时处理的代码修改成直接送到后端服务就可以了,这样便是使用了令牌桶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
lua_shared_dict my_limit_req_store 100m;
....location /hello {access_by_lua_block {local limit_req = require "resty.limit.req"local lim, err = limit_req.new("my_limit_req_store", 2, 0)if not lim thenngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)return ngx.exit(500)endlocal key = ngx.var.binary_remote_addrlocal delay, err = lim:incoming(key, true)if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit req: ", err)return ngx.exit(500)end-- 此方法返回,当前请求需要delay秒后才会被处理,和他前面对请求数-- 此处忽略桶中请求所需要的延时处理,让其直接返送到后端服务器,-- 其实这就是允许桶中请求作为突发流量 也就是令牌桶桶的原理所在if delay >= 0.001 then--    ngx.sleep(delay)end}proxy_pass http://10.100.157.198:6112;proxy_set_header Host $host;proxy_redirect off;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_connect_timeout 60;proxy_read_timeout 600;proxy_send_timeout 600;
}

说明:
其实nginxngx_http_limit_req_module 这个模块中的delaynodelay也就是类似此处对桶中请求是否做延迟处理的两种方案,也就是分别对应的漏桶和令牌桶两种算法

OpenResty实现限流的几种方式相关推荐

  1. 信号量与令牌桶_限流的4种方式令牌桶实战

    限流的4种方式 正文 限流 限流是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机.常用的限流算法有令牌桶和和漏桶,而Google开源项目Guava ...

  2. Redis 实现限流的三种方式

    欢迎关注方志朋的博客,回复"666"获面试宝典 面对越来越多的高并发场景,限流显示的尤为重要. 当然,限流有许多种实现的方式,Redis具有很强大的功能,我用Redis实践了三种的 ...

  3. Redis 限流的 3 种方式,还有谁不会!

    面对越来越多的高并发场景,限流显示的尤为重要. 当然,限流有许多种实现的方式,Redis具有很强大的功能,我用Redis实践了三种的实现方式,可以较为简单的实现其方式.Redis不仅仅是可以做限流,还 ...

  4. Redis 限流的 3 种方式

    大家好,我是DD. 现在出去面试各种后端开发岗位,高并发场景相关的题基本是不会少的.而在高并发场景下,限流又是非常重要的一块.今天就来聊聊限流的解决方案. 当然,限流有许多种实现的方式,Redis具有 ...

  5. 【Redis】分布式限流与Redis实现限流的四种方式(Redis专栏启动)

  6. 限流算法, 以 Golang 方式

    限流算法, 以 Golang 方式 速率限制 在 Web Server.TCP 通讯.API 交互等领域中,速率限制,Rate Limit,一般是面向请求次数.流量等参数进行速率控制.有的时候它又被称 ...

  7. 网站seo优化引流的三种方式

    中安云城现在为大家共享一下引流的几种方式,现在需要细分一下. 1.社群引流 这个方式其实也挺常见的,选择与你推广的产品相关的论坛发布文章会对你的推广引流效果更好一点,具有特征性,同时也不要去不太活跃以 ...

  8. 限流的4种策略--固定窗口、滑动窗口、漏桶、令牌桶

    01 Why 分布式系统中,由于接口API无法控制上游调用方的行为,因此当瞬时请求量突增时,会导致服务器占用过多资源,发生响应速度降低.超时.乃至宕机,甚至引发雪崩造成整个系统不可用. 限流,Rate ...

  9. html脱离标准文档流,关于css脱离标准文档流的两种方式

    所谓脱离标准文档流就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会当做脱离文档流的元素不存在而进行定位. 不浮动的盒子会无视浮动的盒子,假使现有两个盒子,一个浮动一个不浮动,则浮动的盒子会覆 ...

最新文章

  1. Linux查看所有用户和组信息
  2. 机器人学习--粒子滤波定位-MATLAB仿真1
  3. 2053. 数组中第 K 个独一无二的字符串
  4. 最后一周!4000+HC免笔试!字节跳动2022校招研发提前批倒计时
  5. JdbcTemplate查询返回JavaBean的几种方法
  6. 解决EasyDSS、EasyNVR流媒体RTMP、HLS(m3u8)、HTTP-FLV播放提示H5播放错误的问题
  7. 在Style Report中制作主从分级报表
  8. ADO.NET 数据库操作类
  9. 织梦响应式酒店民宿住宿类网站织梦模板(自适应手机端)
  10. Android使用TextToSpeech(TTS)实现文字转语音合成播放
  11. laragon mysql版本_laragon mysql8 安装
  12. 不想学习的小颓靡怎么解决
  13. ionic 中tab切换时出现一闪而过白屏
  14. 微信小程序导入其他字体会不会影响运行_微信小程序是否可以引用特殊字体
  15. AVD Manager 开始之后点击launch会报错
  16. scratch案例开发2
  17. Java(页面的添加和删除)
  18. 新手必学:利用电影贴吧引流轻松变现
  19. 场景UI交互动效设计法则与12种常见设计
  20. python绘制草莓熊

热门文章

  1. 八大排序 - (详解)
  2. 三阶交调(三阶互调) IP3
  3. SSL协议原理(Secure Socket Layer)【转载】
  4. border-radius 兼容 IE8浏览器
  5. 【oracle】oracle创建表、创建索引、创建自增id
  6. 图像的低频,中频,高频信息含义?
  7. NVMe SSD测试为何要先做预处理?
  8. js:身份证号码脱敏(对中间11位号码用*号替换)
  9. react实现手机号码验证
  10. oracle通信通道的文件结尾_“ORA-03113: 通信通道的文件结尾”报错处理