A lua library to provide distributed rate measurement using nginx + redis, you can use it to do a throttling system within many nodes.
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
img
spec add current rate test Feb 1, 2019
src add better ttl logic Feb 1, 2019
.gitignore add code coverage Feb 1, 2019
.luacheckrc fix lua lint errors Feb 1, 2019
.luacov add code coverage Feb 1, 2019
.travis.yml Merge branch 'master' into add-coverage Feb 1, 2019
Dockerfile Add tests structure Jan 25, 2019
Dockerfile.test add code coverage Feb 1, 2019
LICENSE Initial commit Jan 24, 2019
Makefile add lua lint Feb 1, 2019
README.md Use proper branch for badge Feb 4, 2019
docker-compose.yml
nginx.conf Use Nginx resolver + Docker embedded DNS server Jan 28, 2019
resty-redis-rate-1.0.0-0.rockspec

README.md

Build Status license Coverage Status

Resty Redis Rate

A Lua library to provide rate measurement using nginx + Redis. This lib was inspired on Cloudflare's post How we built rate limiting capable of scaling to millions of domains.

You can found more about why and when this library was created here..

Use case: distributed throttling

Nginx has already a rate limiting feature but it is restricted by the local node. Once you have more than one server behind a load balancer this won't work as expected, so you can use redis as a distributed storage to keep the rating data.

local redis_client = redis_cluster:new(config)
-- let's say we'll use the ?token=<value> as the key to rate limit
local rate, err = redis_rate.measure(redis_client, ngx.var.arg_token)
if err then
    ngx.log(ngx.ERR, "err: ", err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

-- once we hit more than 10 reqs/m we'll reply 403
if rate > 10 then
    ngx.exit(ngx.HTTP_FORBIDDEN)
end

ngx.say(rate)

Tests result

We ran three different experiments constrained by a rate limit of 10 req/minute:

  1. Experiment1: 1 reqs/second
  2. Experiment2: 1/6 reqs/second
  3. Experiment3: 1/5 reqs/second

nginx redis throttling exprimentes graph result

All the data points above the rate limit (the red line) resulted in forbidden responses.

You can run the throttling example locally, open up a terminal tab to run the servers.

Make sure you have docker and docker-compose installed.

make up

Open another terminal tab and perform the experiments:

# Experiment 1
for i in {1..120}; do curl "http://localhost:8080/lua_content?token=Experiment1" && sleep 1; done

# Experiment 2
for i in {1..20}; do curl "http://localhost:8080/lua_content?token=Experiment2" && sleep 6; done

# Experiment 3
for i in {1..24}; do curl "http://localhost:8080/lua_content?token=Experiment3" && sleep 5; done

Pipeline and hash tag

We're using the combination of pipeline and hash tag to perform all the commands in a single connection to redis cluster. You can see the tcpdump output showing the three-way handshake followed by the three commands requests $get, $inc and $expire and the redis response.

22:20:10.515457 IP (tos 0x0, ttl 64, id 38199, offset 0, flags [DF], proto TCP (6), length 60)
    172.31.0.3.49824 > 172.31.0.2.7000: Flags [S], cksum 0x5872 (incorrect -> 0xb9b9), seq 1010830934, win 29200, options [mss 1460,sackOK,TS val 170849 ecr 0,nop,wscale 7], length 0

22:20:10.515505 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    172.31.0.2.7000 > 172.31.0.3.49824: Flags [S.], cksum 0x5872 (incorrect -> 0xfcda), seq 1496303914, ack 1010830935, win 28960, options [mss 1460,sackOK,TS val 170849 ecr 170849,nop,wscale 7], length 0

22:20:10.515518 IP (tos 0x0, ttl 64, id 38200, offset 0, flags [DF], proto TCP (6), length 52)
    172.31.0.3.49824 > 172.31.0.2.7000: Flags [.], cksum 0x586a (incorrect -> 0x9be2), seq 1, ack 1, win 229, options [nop,nop,TS val 170849 ecr 170849], length 0

22:20:10.515648 IP (tos 0x0, ttl 64, id 38201, offset 0, flags [DF], proto TCP (6), length 212)
    172.31.0.3.49824 > 172.31.0.2.7000: Flags [P.], cksum 0x590a (incorrect -> 0x7954), seq 1:161, ack 1, win 229, options [nop,nop,TS val 170849 ecr 170849], length 160
	0x0000:  4500 00d4 9539 4000 4006 4ca7 ac1f 0003  E....9@.@.L.....
	0x0010:  ac1f 0002 c2a0 1b58 3c40 0e57 592f c92b  .......X<@.WY/.+
	0x0020:  8018 00e5 590a 0000 0101 080a 0002 9b61  ....Y..........a
	0x0030:  0002 9b61 2a32 0d0a 2433 0d0a 6765 740d  ...a*2..$3..get.
	0x0040:  0a24 3239 0d0a 6e67 785f 7261 7465 5f6d  .$29..ngx_rate_m
	0x0050:  6561 7375 7269 6e67 5f7b 6c75 6967 697d  easuring_{luigi}
	0x0060:  5f31 390d 0a2a 320d 0a24 340d 0a69 6e63  _19..*2..$4..inc
	0x0070:  720d 0a24 3239 0d0a 6e67 785f 7261 7465  r..$29..ngx_rate
	0x0080:  5f6d 6561 7375 7269 6e67 5f7b 6c75 6967  _measuring_{luig
	0x0090:  697d 5f32 300d 0a2a 330d 0a24 360d 0a65  i}_20..*3..$6..e
	0x00a0:  7870 6972 650d 0a24 3239 0d0a 6e67 785f  xpire..$29..ngx_
	0x00b0:  7261 7465 5f6d 6561 7375 7269 6e67 5f7b  rate_measuring_{
	0x00c0:  6c75 6967 697d 5f32 300d 0a24 330d 0a31  luigi}_20..$3..1
	0x00d0:  3230 0d0a                                20..
22:20:10.517337 IP (tos 0x0, ttl 64, id 21067, offset 0, flags [DF], proto TCP (6), length 65)
    172.31.0.2.7000 > 172.31.0.3.49824: Flags [P.], cksum 0x5877 (incorrect -> 0xc55e), seq 1:14, ack 161, win 235, options [nop,nop,TS val 170849 ecr 170849], length 13
	0x0000:  4500 0041 524b 4000 4006 9028 ac1f 0002  E..ARK@.@..(....
	0x0010:  ac1f 0003 1b58 c2a0 592f c92b 3c40 0ef7  .....X..Y/.+<@..
	0x0020:  8018 00eb 5877 0000 0101 080a 0002 9b61  ....Xw.........a
	0x0030:  0002 9b61 242d 310d 0a3a 310d 0a3a 310d  ...a$-1..:1..:1.
	0x0040:  0a                                       .