专栏|分布式限流系列

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: nigix限流

## Nginx限流


### 控制速率(limit_req_zone)


`ngx_http_limit_req_module` 模块提供限制请求处理速率能力,使用`漏桶算法(leaky bucket)`。使用`limit_req_zone` 和`limit_req`两个指令,限制单个IP的请求处理速率。格式:`limit_req_zone key zone rate`


```nginx

http {

   limit_req_zone $binary_remote_addr zone=testRateLimit:10m rate=10r/s;

}

server {

   location /test/ {

 limit_req zone=testRateLimit burst=20 nodelay;

       # 设置(http,server,location)超过限流策略后拒绝请求的响应状态码,默认503

       limit_req_status 555;

       # 设置(http,server,location)限流策略后打印的日志级别:info|notice|warn|error

       limit_req_log_level warn;

       # 设置(http,server,location)启动无过滤模式。启用后不会过滤请求,但仍会记录速率超量的日志,默认为off

       limit_req_dry_run off;

       proxy_pass http://my_upstream;

   }

   error_page 555 /555json;

   location = /555json  {

       default_type application/json;

       add_header Content-Type 'text/html; charset=utf-8';

       return 200 '{"code": 666, "update":"访问高峰期,请稍后再试"}';

   }

}

```


- **key**:定义限流对象,`$binary_remote_addr` 表示基于 `remote_addr` 来做限流,`binary_` 的目的是压缩内存占用量

- **zone**:定义共享内存区来存储访问信息,`myRateLimit:10m` 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息

- **rate**:设置最大访问速率,`rate=10r/s` 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。即上一个请求处理完后,后续100毫秒内又有请求到达,将拒绝处理该请求

- **burst**:处理突发流量

 - `burst=20` 表示若同时有21个请求到达,Nginx 会处理第1个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于21,将拒绝处理多余的请求,直接返回503

 - `burst=20 nodelay` 表示20个请求立马处理,不能延迟。不过即使这20个突发请求立马处理结束,后续来请求也不会立马处理。**burst=20** 相当于缓存队列中占了20个坑,即使请求被处理,这20个位置这只能按100ms一个来释放




### 控制并发连接数(limit_conn_zone)


`ngx_http_limit_conn_module`提供了限制连接数的能力,利用`limit_conn_zone`和`limit_conn`两个指令即可。下面是Nginx官方例子:


```nginx

limit_conn_zone $binary_remote_addr zone=test:10m;

limit_conn_zone $server_name zone=demo:10m;


server {

# 表示限制单个IP同时最多持有10个连接

   limit_conn test 10;

   # 表示虚拟主机(server) 同时能处理并发连接的总数为100个

   limit_conn demo 100;

   # 设置(http,server,location)超过限流策略后拒绝请求的响应状态码,默认503

   limit_conn_status 555;

   # 设置(http,server,location)限流策略后打印的日志级别:info|notice|warn|error

   limit_conn_log_level warn;

   # 设置(http,server,location)启动无过滤模式。启用后不会过滤请求,但仍会记录速率超量的日志,默认为off

   limit_conn_dry_run off;

 

   error_page 555 /555json;

   location = /555json  {

       default_type application/json;

       add_header Content-Type 'text/html; charset=utf-8';

       return 200 '{"code": 666, "update":"访问高峰期,请稍后再试"}';

   }

}

```


**注意**:只有当 **request header** 被后端server处理后,这个连接才进行计数。




### lua限流


**第一步**:安装说明


环境准备:


```shell

yum install -y gcc gcc-c++ readline-devel pcre-devel openssl-devel tcl perl

```


安装drizzle http://wiki.nginx.org/HttpDrizzleModule


```shell

cd /usr/local/src/

wget http://openresty.org/download/drizzle7-2011.07.21.tar.gz

tar xzvf drizzle-2011.07.21.tar.gz

cd drizzle-2011.07.21/

./configure --without-server

make libdrizzle-1.0

make install-libdrizzle-1.0

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

```


安装openresty:


```shell

wget http://openresty.org/download/ngx_openresty-1.7.2.1.tar.gz

tar xzvf ngx_openresty-1.7.2.1.tar.gz

cd ngx_openresty-1.7.2.1/

./configure --with-http_drizzle_module

gmake

gmake install

```


**第二步**:Nginx配置nginx.conf


`/usr/local/openresty/nginx/conf/nginx.conf`:


```nginx

# 添加MySQL配置(drizzle)

upstream backend {

   drizzle_server 127.0.0.1:3306 dbname=test user=root password=123456 protocol=mysql;

   drizzle_keepalive max=200 overflow=ignore mode=single;

}


server {

   listen       80;

   server_name  localhost;


   location / {

       root   html;

       index  index.html index.htm;

   }


   location /lua _test{

       default_type text/plain;

       content_by_lua 'ngx.say("hello, lua")';

   }



   location /lua_redis {

       default_type text/plain;

       content_by_lua_file /usr/local/lua_test/redis_test.lua;

   }


   location /lua_mysql {

           default_type text/plain;

           content_by_lua_file /usr/local/lua_test/mysql_test.lua;

   }



   location @cats-by-name {

       set_unescape_uri $name $arg_name;

       set_quote_sql_str $name;

       drizzle_query 'select * from cats where name=$name';

       drizzle_pass backend;

       rds_json on;

   }

   location @cats-by-id {

       set_quote_sql_str $id $arg_id;

       drizzle_query 'select * from cats where id=$id';

       drizzle_pass backend;

       rds_json on;

   }

   location = /cats {

       access_by_lua '

           if ngx.var.arg_name then

               return ngx.exec("@cats-by-name")

           end


           if ngx.var.arg_id then

               return ngx.exec("@cats-by-id")

           end

       ';


       rds_json_ret 400 "expecting \"name\" or \"id\" query arguments";

   }

   # 通过url匹配出name,并编码防止注入,最后以json格式输出结果

   location ~ '^/mysql/(.*)' {

       set $name $1;

       set_quote_sql_str $quote_name $name;

       set $sql "SELECT * FROM cats WHERE name=$quote_name";

       drizzle_query $sql;

       drizzle_pass backend;

       rds_json on;

   }

   # 查看MySQL服务状态

   location /mysql-status {

       drizzle_status;

   }

}

```


**第三步**:lua测试脚本


`/usr/local/lua_test/redis_test.lua`:


```lua

local redis = require "resty.redis"

local cache = redis.new()

cache.connect(cache, '127.0.0.1', '6379')

local res = cache:get("foo")

if res==ngx.null then

   ngx.say("This is Null")

   return

end

ngx.say(res)

```


`/usr/local/lua_test/mysql_test.lua`:


```lua

local mysql = require "resty.mysql"

local db, err = mysql:new()

if not db then

   ngx.say("failed to instantiate mysql: ", err)

   return

end


db:set_timeout(1000) -- 1 sec


-- or connect to a unix domain socket file listened

-- by a mysql server:

--     local ok, err, errno, sqlstate =

--           db:connect{

--              path = "/path/to/mysql.sock",

--              database = "ngx_test",

--              user = "ngx_test",

--              password = "ngx_test" }


local ok, err, errno, sqlstate = db:connect{

   host = "127.0.0.1",

   port = 3306,

   database = "test",

   user = "root",

   password = "123456",

   max_packet_size = 1024 * 1024 }


if not ok then

   ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)

   return

end


ngx.say("connected to mysql.")


local res, err, errno, sqlstate =

   db:query("drop table if exists cats")

if not res then

   ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

   return

end


res, err, errno, sqlstate =

   db:query("create table cats "

            .. "(id serial primary key, "

            .. "name varchar(5))")

if not res then

   ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

   return

end


ngx.say("table cats created.")


res, err, errno, sqlstate =

   db:query("insert into cats (name) "

            .. "values (\'Bob\'),(\'\'),(null)")

if not res then

   ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

   return

end


ngx.say(res.affected_rows, " rows inserted into table cats ",

       "(last insert id: ", res.insert_id, ")")


-- run a select query, expected about 10 rows in

-- the result set:

res, err, errno, sqlstate =

   db:query("select * from cats order by id asc", 10)

if not res then

   ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

   return

end


local cjson = require "cjson"

ngx.say("result: ", cjson.encode(res))


-- put it into the connection pool of size 100,

-- with 10 seconds max idle timeout

local ok, err = db:set_keepalive(10000, 100)

if not ok then

   ngx.say("failed to set keepalive: ", err)

   return

end


-- or just close the connection right away:

-- local ok, err = db:close()

-- if not ok then

--     ngx.say("failed to close: ", err)

--     return

-- end

';

```


**第四步**:验证结果


```shell

$ curl 'http://127.0.0.1/lua_test'

hello, lua


$ redis-cli set foo 'hello,lua-redis'

OK

$ curl 'http://127.0.0.1/lua_redis'

hello,lua-redis


$ curl 'http://127.0.0.1/lua_mysql'

connected to mysql.

table cats created.

3 rows inserted into table cats (last insert id: 1)

result: [{"name":"Bob","id":"1"},{"name":"","id":"2"},{"name":null,"id":"3"}]


$ curl 'http://127.0.0.1/cats'

{"errcode":400,"errstr":"expecting \"name\" or \"id\" query arguments"}


$ curl 'http://127.0.0.1/cats?name=bob'

[{"id":1,"name":"Bob"}]


$ curl 'http://127.0.0.1/cats?id=2'

[{"id":2,"name":""}]


$ curl 'http://127.0.0.1/mysql/bob'

[{"id":1,"name":"Bob"}]


$ curl 'http://127.0.0.1/mysql-status'

worker process: 32261


upstream backend

 active connections: 0

 connection pool capacity: 0

 servers: 1

 peers: 1

```

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
|
应用服务中间件 nginx
分布式限流
分布式限流
55 1
|
7月前
|
NoSQL Cloud Native 算法
🤔为什么分布式限流会出现不均衡的情况?
🤔为什么分布式限流会出现不均衡的情况?
|
4月前
|
存储 NoSQL 算法
Go 分布式令牌桶限流 + 兜底保障
Go 分布式令牌桶限流 + 兜底保障
|
5月前
|
存储 缓存 NoSQL
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
102 1
|
7月前
|
存储 缓存 算法
【专栏】探讨分布式限流所面临的挑战以及目前业界常用的解决方案
【4月更文挑战第27天】在互联网时代,分布式限流是应对高并发、保护系统稳定的关键。它面临数据一致性、算法准确性和系统可扩展性的挑战。常见限流算法有令牌桶、漏桶和滑动窗口。解决方案包括使用分布式存储同步状态、结合多种算法及动态调整阈值。定期压力测试确保策略有效性。随着系统规模增长,限流技术将持续发展,理解并应用限流原理对保障服务质量至关重要。
158 3
|
7月前
|
负载均衡 算法
分布式限流:避免流控失控的关键问题
在当今高并发互联网环境下,分布式系统中的限流机制显得尤为重要。然而,分布式限流也面临着一系列挑战和问题。本文将探讨分布式限流中需要注意的关键问题,并提供相应解决方案,以确保流控策略的有效实施。
|
7月前
|
消息中间件 数据采集 缓存
探索分布式限流:挑战与解决方案
分布式限流是现代系统设计中的重要挑战之一。本文将探讨分布式限流的背景和意义,以及在实施分布式限流时需要注意的关键问题,并提供一些解决方案。
分布式接口幂等性、分布式限流(Guava 、nginx和lua限流)
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29717 51
|
存储 NoSQL Redis
分布式限流:Redis
分布式限流:Redis
280 0