OpenResty x Open Talk 深圳站

OpenResty 动态流控的集中姿势

1. OpenResty x Open Talk SHENZHEN 2019 OpenResty 动态流控的集中姿势 张聪 @ timebug 主办方:
2. A Systems Engineer at ⼜又拍云 ★ Email: ★ Github: timebug.info@gmail.com https://github.com/timebug
3. https://github.com/upyun/upyun-resty UPYUN CDN & API is built on top of NGINX with ngx_lua/OpenResty
4. 什什么是流控 我个⼈人的理理解是(针对应⽤用层): • 流控通常意义下是通过⼀一些合理理的技术⼿手段对⼊入⼝口请求或流量量进⾏行行有效地疏导和 控制,从⽽而使得有限资源的上游服务和整个系统能始终在健康的设计负荷下⼯工作, 同时在不不影响绝⼤大多数⽤用户体验的情况下确保“利利益”最⼤大化。 • 流控有时候也是在考虑安全和成本时的⼀一个⼿手段。
5. 为什什么要流控 • 为了了业务数据安全,针对关键密码认证请求进⾏行行有限次数限制,避免他⼈人通过字典攻击暴暴⼒力力破解。 • 在保障正常⽤用户请求频率的同时,限制⾮非正常速率的恶意 DDoS 攻击请求,拒绝⾮非⼈人类访问。 • 控制上游应⽤用在同⼀一时刻处理理的⽤用户请求数量量,以免出现并发资源竞争导致体验下降。 • • • • 上游业务处理理能⼒力力有限,如果某⼀一时刻累计未完成任务超过设计最⼤大容量量,会导致整体系统出现不不稳定甚⾄至持续恶化,需要时刻保 持在安全负荷下⼯工作。 集群模式下,负载均衡也是流控最基础的⼀一个环节,当然也有⼀一些业务⽆无法精确进⾏行行前置负载均衡,例例如图⽚片处理理等场景就容易易出 现单点资源瓶颈,此时需要根据上游节点实时负载情况进⾏行行主动调度。 在实际的业务运营中,往往处于成本考虑,还需要进⾏行行流量量整形和带宽控制,包括下载限速和上传限速,以及在特定领域例例如终端 设备⾳音视频播放场景下,根据实际码率进⾏行行针对性速率限制等。 … (当然,还有很多上⾯面没提到的原因)
6. 怎么做流控 • 经典的 NGINX ⽅方式。 • 灵活的 OpenResty ⽅方式。 • ⼜又拍云的线上实际案例例。
8. NGINX 请求速率限制(1) limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s; limit_req_log_level error; limit_req_status 429; server { location /upyun/ { limit_req zone=mylimit; proxy_pass http://website; } } ➡ngx_http_limit_req_module ๏ 针对来源 IP,限制其请求速率为 5r/s。 ๏ 意味着,相邻请求间隔⾄至少 200ms,否则拒绝。 ๏ 但实际业务中,偶尔有些徒增也是正常的。
9. NGINX 请求速率限制(2) ๏ 设置 brust = 4,表示在超过限制速率 5r/s 的时候,同时最多允许额外有 4 个请求排队等候, 待平均速率回归正常后,队列列最前⾯面的请求会优先被处理理。 ๏ 在已经存在 4 个请求同时等候的情况下,此时“⽴立刻”过来的请求就会被拒绝。 ๏ 虽然允许了了⼀一定程度的突发,但有些业务场景下,排队导致的请求延迟增加是不不可接受的,例例 如上图中突发队列列队尾的那个请求被滞后了了 800ms 才进⾏行行处理理。
10. NGINX 请求速率限制(3) ๏ nodelay 参数配合 brust = 4 就可以使得突发时需要等待的请求⽴立即得到处理理,与此同时,模拟⼀一个插 槽个数为 4 的“令牌”队列列(桶)。 ๏ 从抽象的⻆角度描述下这个过程,该“令牌”桶会每隔 200ms 释放⼀一个“令牌”,空出的槽位等待新的“令 牌”进来,若桶槽位被填满,随后突发的请求就会被拒绝。 ๏ 这个模式下,在控制请求速率的同时,允许了了⼀一定程度的突发,并且尽可能改善了了延迟体验。
11. NGINX 请求速率限制(4) ๏ NGINX 1.15.7 还⽀支持 delay=number 和 brust=number 参数配合使⽤用。 ๏ 在有些特定场景下,我们既需要保障正常的少量量关联资源能够快速地加载,同时也需要对于突发请求 及时地进⾏行行限制,⽽而 delay 参数能更更精细地来控制这类限制效果。 ๏ 例例如某⻚页⾯面引⽤用的资源⼤大概 4 ~ 6 个,需要确保并发加载这些资源,但⼜又肯定不不会超过 12 个,那么针 对该⻚页⾯面的限制策略略除了了 burst=12 外,还可以设置 delay=8,在突发请求的情况下,其中前 8 个不不 需要进⾏行行延迟处理理,后 4 个则依然排队等候处理理。
12. NGINX 并发连接数限制 limit_conn_zone $binary_remote_addr zone=addr:10m; limit_conn_log_level error; limit_conn_status 503; server { location /download/ { limit_conn addr 1; } } ➡ngx_http_limit_conn_module ๏ 对于处理理中的请求,该模块在读完请求头全部内容后才开始计数。 ๏ 在 HTTP/2 和 SPDY 协议下,每⼀一个并发请求都会当做⼀一个独⽴立的连接。
13. NGINX 下载带宽限制 location /download/ { limit_rate_after 500k; limit_rate 20k; } ➡ngx_http_core_module ๏ 在下载完前⾯面 500 KB 数据后,对接下来的数据以每秒 20 KB 速度进⾏行行限制。 ๏ 常常⽤用于⼀一些⽂文件下载、视频播放等业务场景,避免不不必要的流量量浪费。
14. OpenResty 动态流控 • 我们需要更更加丰富的流控策略略! • 我们需要更更加灵活的配置管理理! • 我们需要在 NGINX 请求⽣生命周期的更更多阶段进⾏行行控制! • 我们需要跨机器器进⾏行行状态同步! • NGINX PPPlus ?
15. OpenResty 动态流控请求速率限制/并发连接数限制 local limit_req = require "resty.limit.req" local lim, err = limit_req.new("mylimit", 5, 9) local delay, err = lim:incoming(ngx.var.binary_remote_addr, true) if not delay then if err == "rejected" then return ngx.exit(429) end return ngx.exit(500) end if delay >= 0.001 then ngx.sleep(delay) end ➡lua-resty-limit-traffic (resty.limit.req) ๏ 上述 Lua 代码基本效果等同于 NGINX limit_req 模块 rate=5r/s brust=4 的限速配置。 ๏ 但 Lua 实现更更加灵活以及⼏几乎可以在任意上下⽂文使⽤用。 ➡lua-resty-limit-traffic (resty.limit.conn) ๏ 功能和 NGINX limit_conn ⼀一致,但 Lua 版本允许突发连接进⾏行行短暂延迟等候。
16. OpenResty 动态流控 - 请求数量量限制 local limit_count = require "resty.limit.count" local lim, err = limit_count.new("mylimit", 5000, 3600) local delay, err = lim:incoming(ngx.req.get_headers()["Authorization"], true) if not delay then if err == "rejected" then ngx.header["X-RateLimit-Limit"] = "5000" ngx.header["X-RateLimit-Remaining"] = 0 return ngx.exit(503) end return ngx.exit(500) end ngx.header["X-RateLimit-Limit"] = "5000" ngx.header["X-RateLimit-Remaining"] = err -- current remaining number ➡lua-resty-limit-traffic (resty.limit.count) ๏ 和 Github API Rate Limiting 接⼝口设计类似。 ๏ 该 Lua 模块⽤用于对单位窗⼝口时间内累计请求数量量进⾏行行限制。
17. OpenResty 动态流控 - 跨机器器速率限制 local ratelimit = require "resty.redis.ratelimit" local lim, err = ratelimit.new("mylimit", "5r/s", 4, 0) local red = { host = "127.0.0.1", port = 6379, timeout = 1 } local delay, err = lim:incoming(ngx.var.binary_remote_addr, red) if not delay then if err == "rejected" then return ngx.exit(429) end return ngx.exit(500) end if delay >= 0.001 then ngx.sleep(delay) end ➡ lua-resty-redis-ratelimit (resty.redis.ratelimit) ๏ 和 NGINX limit_req 以及 resty.limit.req ⼀一样,都是基于漏漏桶算法对平均请求速率进⾏行行限制。不不同的是,该模块将信息保存在 Redis 从⽽而实 现多 NGINX 实例例状态共享。 ๏ 借助于 Redis Lua Script 机制,使得跨机器器状态变更更操作能保证原⼦子性。同时,该模块⽀支持在整个集群层⾯面禁⽌止某个⾮非法⽤用户⼀一段时间, 可实现全局⾃自动拉⿊黑功能。 ๏ NGINX 和 Redis 交互需要⽹网络 IO,会带来⼀一定延迟开销,仅适合请求量量不不⼤大,但需要⾮非常精确限制全局请求速率或统计时间跨度⾮非常⼤大的 场景。当然这块也可以在牺牲⼀一些精确度和及时性的情况下,在 NGINX 侧进⾏行行局部计算。
18. 知识点 - 漏漏桶算法 local ms = math.abs(now - last) excess = excess - rate * ms / 1000 + 1000 if excess < 0 then excess = 0 end if excess > 0 then return BUSY end ๏ 在具体实现中,⼀一般最⼩小的速率表示是 0.001r/s,为了了直观计算,我们⽤用 1 个⽔水滴 (假设单位 t)来表达 0.001 个请求,那么 rate = 5r/s 相当于 5000t/s。 ๏ ms 为当前请求和上⼀一个请求的时间间隔,假设 100ms,单位毫秒,下⾯面公式中除 以 1000 等于 0.1s。 ๏ excess 表示上⼀一次超出的⽔水滴数(延迟通过);特别地,excess < 0 的时候会被 重置为 0,此时相当于漏漏桶处于空桶状态,进来的⽔水滴直接流出了了。 ๏ 如果 excess > 0 说明漏漏桶有⽔水滴堆积了了,按我们设计,返回 BUSY,表示繁忙。
19. OpenResty 动态流控 - 令牌桶限速 local lim_global = limit_rate.new("my_limit_rate_store", 100, 6000, 2) -- global 20r/s 6000r/5m local lim_single = limit_rate.new("my_limit_rate_store", 500, 600, 1) — single 2r/s 600r/5m local t0, err = lim_global:take_available("__global__", 1) local t1, err = lim_single:take_available(ngx.var.arg_userid, 1) if t0 == 1 then return -- global bucket is not hungry else if t1 == 1 then return -- single bucket is not hungry else return ngx.exit(503) end end ➡ lua-resty-limit-rate (resty.limit.rate) ๏ 该 Lua 模块基于令牌桶算法实现,也是流控中的⼀一种经典思想。 ๏ 相⽐比 limit.req 基于漏漏桶的设计,该模块更更加关注容量量变化⽽而⾮非相邻请求间的速率,适合有⼀一定弹性设计容量量的 系统,在资源⾜足够的情况下,速率允许有较⼤大的波动。 ๏ 相⽐比 limit.count 对单位窗⼝口时间内累计请求数量量进⾏行行限制,该模块在特定配置下,也能达到类似效果,并且能 避免在单位时间窗⼝口切换瞬间导致可能双倍的限制请求情况出现。 ๏ 除了了请求速率限制(⼀一个令牌⼀一个请求),还能够对字节传输进⾏行行流量量整形,此时,⼀一个令牌相当于⼀一个字节。
20. ⼜又拍云的线上实际案例例 ๏ 海海外代理理进⾏行行上传流量量整形,避免跑满传输线路路带宽。 ๏ 某 API 请求基于令牌桶针对不不同账户进⾏行行请求速率控制。 ๏ CDN 特性:IP 访问限制,⽀支持阶梯策略略升级。 ๏ CDN 特性:码率适配限速。
21. All Web/API services use Kong as the gateway (Kong is a very famous OpenResty application) KONG-S 内部站点⼊入⼝口 KONG-X 外部站点/API ⼊入⼝口 KONG-HK 海海外加速透明转发
22. ⼜又拍云的线上实际案例例 - 流量量整形 body = function() local size = 8192 if size > request_size then size = request_size end local count, err = token_bucket:take_available("service_id", size) if count == 0 then return nil, err -- drop body end size = count request_size = request_size - size local bytes, err = req_socket:receive(size) if err then return nil, err end return bytes end
23. ⼜又拍云的线上实际案例例 - 令牌桶应⽤用 check_limit = { ["__global__"] = { interval = 100, capacity = 12000, quantum = 4, }, -- 40r/s 12000r/5m ["__bucket__"] = { interval = 200, capacity = 6000, quantum = 2, }, -- 10r/s 6000r/10m ["__delete__"] = { interval = 500, capacity = 1200, quantum = 1, }, -- 2r/s 1200r/10m },
24. ⼜又拍云的线上实际案例例 - IP 访问限制
25. ⼜又拍云的线上实际案例例 - 码率适配限速 ๏ 该功能通过⾃自动分析当前视频⽂文件传输的码率,将视频⽂文件的传输速度控制在视频码率 范围之内,可以在⼀一定程度上防⽌止⾼高峰期时带宽占⽤用,节省成本。
26. Q&A
27. TKANKS! 关注又拍云微信公众号, 获取更多干货! 主办方:

相关幻灯片