openresty开发系列37--nginx-lua-redis实现访问频率控制

一)需求背景

在高并发场景下为了防止某个访问ip访问的频率过高,有时候会需要控制用户的访问频次
在openresty中,可以找到:
set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua等方法。
那么访问控制应该是,access阶段。
我们用Nginx+Lua+Redis来做访问限制主要是考虑到高并发环境下快速访问控制的需求。

二)设计方案

我们用redis的key表示用户,value表示用户的请求频次,再利用过期时间实现单位时间;

现在我们要求10秒内只能访问10次frequency请求,超过返回403

1)首先为nginx.conf配置文件,nginx.conf部分内容如下:

location /frequency {
    access_by_lua_file /usr/local/lua/access_by_limit_frequency.lua;
    echo "访问成功";
}

2)编辑/usr/local/lua/access_by_limit_frequency.lua

  1. local function close_redis(red)
  2. if not red then
  3. return
  4. end
  5. --释放连接(连接池实现)
  6. local pool_max_idle_time = --毫秒
  7. local pool_size = --连接池大小
  8. local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
  9. if not ok then
  10. ngx.say("set keepalive error : ", err)
  11. end
  12. end
  13.  
  14. local function errlog(...)
  15. ngx.log(ngx.ERR, "redis: ", ...)
  16. end
  17.  
  18. local redis = require "resty.redis" --引入redis模块
  19. local red = redis:new() --创建一个对象,注意是用冒号调用的
  20.  
  21. --设置超时(毫秒)
  22. red:set_timeout()
  23. --建立连接
  24. local ip = "10.11.0.215"
  25. local port =
  26. local ok, err = red:connect(ip, port)
  27. if not ok then
  28. close_redis(red)
  29. errlog("Cannot connect");
  30. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  31. end
  32.  
  33. local key = "limit:frequency:login:"..ngx.var.remote_addr
  34.  
  35. --得到此客户端IP的频次
  36. local resp, err = red:get(key)
  37. if not resp then
  38. close_redis(red)
  39. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) --redis 获取值失败
  40. end
  41.  
  42. if resp == ngx.null then
  43. red:set(key, ) -- 单位时间 第一次访问
  44. red:expire(key, ) --10秒时间 过期
  45. end
  46.  
  47. if type(resp) == "string" then
  48. if tonumber(resp) > then -- 超过10
  49. close_redis(red)
  50. return ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403
  51. end
  52. end
  53.  
  54. --调用API设置key
  55. ok, err = red:incr(key)
  56. if not ok then
  57. close_redis(red)
  58. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) --redis 报错
  59. end
  60.  
  61. close_redis(red)

# 当redis设置了密码时,需要用red:auth() 方法进行验证

  1. # vim /usr/local/lua/access_by_limit_frequency.lua
  2.  
  3. local function close_redis(red)
  4. if not red then
  5. return
  6. end
  7.  
  8. local pool_max_idle_time =
  9. local pool_size =
  10. local ok, err = red:set_keepalive(pool_max_idle_tme, pool_size)
  11. if not ok then
  12. ngx.say("set keepalive err : ", err)
  13. end
  14. end
  15.  
  16. local function errlog(...)
  17. ngx.log(ngx.ERR, "redis: ", ...)
  18. end
  19.  
  20. local redis = require "resty.redis"
  21. local red = redis:new()
  22.  
  23. red:set_timeout()
  24. local ip = "10.11.0.215"
  25. local port =
  26. local ok,err = red:connect(ip, port)
  27. if not ok then
  28. close_redis(red)
  29. errlog("connot connect");
  30. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  31. end
  32.  
  33. local count, err = red:get_reused_times()
  34. if == count then ----新建连接,需要认证密码
  35. ok, err = red:auth("redis123")
  36. if not ok then
  37. ngx.say("failed to auth: ", err)
  38. return
  39. end
  40. elseif err then ----从连接池中获取连接,无需再次认证密码
  41. ngx.say("failed to get reused times: ", err)
  42. return
  43. end
  44.  
  45. local key = "limit:frequency:login: " ..ngx.var.remote_addr
  46.  
  47. --得到此客户端IP的频次
  48. local resp,err = red:get(key)
  49. if not resp then
  50. close_redis(red)
  51. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  52. end
  53.  
  54. if resp == ngx.null then
  55. red:set(key, )
  56. red:expire(key, ) -- 设置过期时间,即返回403的时间为100
  57. end
  58.  
  59. --ngx.say("connect ok")
  60. --ngx.say("resp:",resp)
  61. if type(resp) == "string" then
  62. if tonumber(resp) > then
  63. close_redis(red)
  64. return ngx.exit(ngx.HTTP_FORBIDDEN)
  65. end
  66. end
  67.  
  68. ok, err = red:incr(key)
  69. if not ok then
  70. close_redis(red)
  71. return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  72. end
  73.  
  74. close_redis(red)

请求地址:/frequency

10秒内 超出10次 ,返回403

10秒后,又可以访问了

在Nginx需要限速的location中引用上述脚本配置示例:

location /user/ {
    set $business "USER";
    access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
    proxy_redirect off;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://user_224/user/;
}

注:对于有大量静态资源文件(如:js、css、图片等)的前端页面可以设置只有指定格式的请求才进行访问限速,示例代码如下:

location /h5 {
if ($request_uri ~ .*\.(html|htm|jsp|json)) {
    set $business "H5";
    access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
}
    proxy_redirect off;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://h5_224/h5;
}

如果我们想整个网站 都加上这个限制条件,那只要把
access_by_lua_file /usr/local/lua/access_by_limit_frequency.lua;
这个配置,放在server部分,让所有的location 适用就行了

openresty开发系列37--nginx-lua-redis实现访问频率控制的更多相关文章

  1. openresty开发系列38--通过Lua+Redis 实现动态封禁IP

    openresty开发系列38--通过Lua+Redis 实现动态封禁IP 一)需求背景为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单.对于黑名单之内的 IP ,拒绝 ...

  2. openresty开发系列27--openresty中封装redis操作

    openresty开发系列27--openresty中封装redis操作 在关于web+lua+openresty开发中,项目中会大量操作redis, 重复创建连接-->数据操作-->关闭 ...

  3. openresty开发系列24--openresty中lua的引入及使用

    openresty开发系列24--openresty中lua的引入及使用 openresty 引入 lua 一)openresty中nginx引入lua方式 1)xxx_by_lua   ---> ...

  4. openresty开发系列26--openresty中使用redis模块

    openresty开发系列26--openresty中使用redis模块 在一些高并发的场景中,我们常常会用到缓存技术,现在我们常用的分布式缓存redis是最知名的, 操作redis,我们需要引入re ...

  5. openresty开发系列40--nginx+lua实现获取客户端ip所在的国家信息

    openresty开发系列40--nginx+lua实现获取客户端ip所在的国家信息 为了实现业务系统针对不同地区IP访问,展示包含不同地区信息的业务交互界面.很多情况下系统需要根据用户访问的IP信息 ...

  6. openresty开发系列28--openresty中操作mysql

    openresty开发系列28--openresty中操作mysql Mysql客户端   应用中最常使用的就是数据库了,尤其mysql数据库,那openresty lua如何操作mysql呢?   ...

  7. openresty开发系列10--openresty的简单介绍及安装

    openresty开发系列10--openresty的简单介绍及安装 一.Nginx优点 十几年前,互联网没有这么火,软件外包开发,信息化建设,帮助企业做无纸化办公,收银系统,工厂erp,c/s架构偏 ...

  8. openresty开发系列36--openresty执行流程之6日志模块处理阶段

    openresty开发系列36--openresty执行流程之6日志模块处理阶段 一)header_filter_by_lua 语法:header_filter_by_lua <lua-scri ...

  9. openresty开发系列35--openresty执行流程之5内容content阶段

    openresty开发系列35--openresty执行流程之5内容content阶段 content 阶段 ---init阶段---重写赋值---重写rewrite---access content ...

随机推荐

  1. 什么影响了mysql的性能-存储引擎层

    5.6版本以前默认是MyISam存储引擎,5.6版本之后默认支持的Innodb存储引擎,这两种也是最常用的. 存储引擎层 MyISAM 5.5之前版本默认存储引擎 存储引擎表由MYD和MYI组成 特性 ...

  2. MySQL/MariaDB数据库的PROXY实现读写分离

    MySQL/MariaDB数据库的PROXY实现读写分离 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.ProxySQL概述 1>.各家互联网公司读写分离的解决方案 m ...

  3. Python的路径操作(os模块与pathlib模块)

    Python的路径操作(os模块与pathlib模块) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.os.path模块(Python 3.4版本之前推荐使用该模块) #!/u ...

  4. MySQL:查询、修改(二)

    干货: 使用SELECT查询的基本语句SELECT * FROM <表名>可以查询一个表的所有行和所有列的数据.SELECT查询的结果是一个二维表. 使用SELECT *表示查询表的所有列 ...

  5. httprunner学习1-环境与登录接口案例

    前言 HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试. 具有以下优点: 继承 Requests 的全部特性,轻松实 ...

  6. [转载]Linux进程调度原理

    [转载]Linux进程调度原理 Linux进程调度原理 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交 ...

  7. test201909027 老Z

    30+100+40=170.数据出锅*2,也是没谁了. 装饰 快要到 Mope 的生日了,Mope 希望举行一场盛大的生日派对. 派对的准备工作中当然有装饰房间啦.Mope 的房间里有按从左到右的顺序 ...

  8. NPM——npm|cnpm如何升级

    前言 手动更新了node.js版本后,想要升级下npm的版本 步骤 其实无论npm还是cnpm升级的命令都是一样的,除了需要指定包名. 升级npm $ npm install -g npm 查看npm ...

  9. 2019.12.06 java基础

    JRE:运行环境(包含JVM(Java Virtual Machine)- Java虚拟机和核心类库) JDK: JAVA语言的软件开发工具包(Java Development Kit) Dos命令行 ...

  10. CTF入门(一)

    ctf入门指南 如何入门?如何组队? capture the flag 夺旗比赛 类型: Web密码学pwn 程序的逻辑分析,漏洞利用windows.linux.小型机等misc 杂项,隐写,数据还原 ...