使用lua语言做高并发限流

https://blog.csdn.net/shecanwin/article/details/51719746 

lua语言介绍

       Lua[1]  是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。(引自百度百科)

      其实,关于lua只需知道两点,类c的语言语法和较高的性能。这让lua语言在游戏开发或者项目某些组件的开发时常常用到lua,游戏开发啥的咱也不懂,这次就主要说一下我在某个项目中使用lua开发的限流模块。

lua限流模块

      该项目是国内某知名公司的项目,对项目的可用性要求很高,在访问量非常大的情况下保证项目的服务能力,所以一定要对项目访问做限流。限流的整体思路是根据用户的访问ip、cookie、访问的uri来进行计数,达到一定数量之后就 限制访问。这应该是限流算法中的计数法,另外还有令牌算法和漏桶算法,不再详细介绍。

     模块的流程图如下:

   

     下面根据流程图来分解代码。

  首先,是系统初始化,初始化主要做一些内存分配,名单载入的工作,

 
  1. <div style="text-align: left;"><pre name="code" class="html">--require and loadfile to shm

  2. require("config.config")

  3. cjson = require("cjson")

  4. local readfile = require("common.readfile")

  5. cookie = require('common.cookie')

  6. --preload in init

  7. require('common.util')

  8. require('common.count')

  9. require('common.strategy')

  10. redis = require("cache.redis")

  11.  
  12.  
  13.  
  14. ngx.shared.dt_ip_whitelist:flush_all()

  15. ngx.shared.dt_ip_blacklist:flush_all()

  16. ngx.shared.dt_uri_whitelist:flush_all()

  17. ngx.shared.dt_uri_blacklist:flush_all()

  18.  
  19. ngx.shared.dt_st_ip:flush_all()

  20. ngx.shared.dt_st_ip_uri:flush_all()

  21. ngx.shared.dt_st_ip_log:flush_all()

  22.  
  23.  
  24.  
  25. ngx.shared.dt_st_web:flush_all()

  26. ngx.shared.dt_uri:flush_all()

  27.  
  28. ngx.shared.dt_config:flush_all()

  29.  
  30. ngx.shared.dt_intelligent_ban_ip_list:flush_all()

  31. ngx.shared.dt_intelligent_ban_uri_list:flush_all()

  32. ngx.shared.dt_intelligent_ban_ip_uri_list:flush_all()

  33. ---add switch

  34. if not LUA_MAIN_SWITCH or LUA_MAIN_SWITCH ~= 'ON' then return end

  35.  
  36. if IP_WHITE_LIST_SWITCH == "ON" then

  37. local filename = ROOT_PATH..'lua/config/ip_whitelist'

  38. readfile:readfileforshm(filename,ngx.shared.dt_ip_whitelist)

  39. end

  40.  
  41.  
  42. if IP_BLACK_LIST_SWITCH == "ON" then

  43. local filename = ROOT_PATH..'lua/config/ip_blacklist'

  44. readfile:readfileforshm(filename,ngx.shared.dt_ip_blacklist)

  45. end

  46.  
  47. --uri b and w

  48. if URI_BLACK_LIST_SWITCH == "ON" then

  49. local filename = ROOT_PATH..'lua/config/uri_blacklist'

  50. readfile:readfileforuri(filename,ngx.shared.dt_uri_blacklist)

  51. end

  52.  
  53.  
  54. if URI_WHITE_LIST_SWITCH == "ON" then

  55. local filename = ROOT_PATH..'lua/config/ip_whitelist'

  56. readfile:readfileforshm(filename,ngx.shared.dt_uri_whitelist)

  57. end

  58.  
  59.  
  60. --ansys uri

  61. local filename = ROOT_PATH..'lua/config/statistics_uri'

  62. readfile:readfileforuri(filename,ngx.shared.dt_uri)


 

 

可以看得出来,刚开始做的就是把几个缓存区刷新,然后根据要求载入ip黑白名单、uri黑白名单等,最后加入了一个叫做statics_uri的文件,这个文件的作用是把需要统计监测的uri记录下来,并不是把所有的uri都监测起来,那样肯定不现实,一个大的web项目会有很多uri,只能说找出几个比较重要的来监测和控制。对了,这些内存实在配置文件中设置好的,使用了nginx-lua模块,具体的环境配置可以看我另外一篇教程。

 
  1. lua_package_path "/usr/servers/nginx/conf/lua/?.lua;;";

  2. lua_package_cpath "/usr/servers/lualib/?.so";

  3.  
  4.  
  5.  
  6. # add dict to share memcache

  7. lua_shared_dict dt_ip_whitelist 10m;

  8. lua_shared_dict dt_uri_whitelist 10m;

  9. lua_shared_dict dt_uri_blacklist 10m;

  10. lua_shared_dict dt_ip_blacklist 10m;

  11.  
  12. lua_shared_dict dt_intelligent_ban_ip_list 10m;

  13. lua_shared_dict dt_intelligent_ban_uri_list 10m;

  14. lua_shared_dict dt_intelligent_ban_ip_uri_list 10m;

  15.  
  16.  
  17. lua_shared_dict dt_uri 10m;

  18. lua_shared_dict dt_config 2m;

  19.  
  20. lua_shared_dict dt_st_ip 10m;

  21. lua_shared_dict dt_st_uri 10m;

  22. lua_shared_dict dt_st_web 10m;

  23. lua_shared_dict dt_st_ip_uri 10m;

  24. lua_shared_dict dt_st_ip_log 10m;

  25.  
  26. #lua_shared_dict shared_data 10m;

  27. #init model and load config

  28.  
  29. init_by_lua_file /usr/servers/nginx/conf/lua/init/init.lua;

  30.  
  31.  
  32. log_by_lua_file /usr/servers/nginx/conf/lua/statistics/statistics.lua;

  33.  
  34.  
  35. init_worker_by_lua_file conf/lua/statistics/check.lua;

  36.  
  37. #init_worker_by_lua_file

配置文件上来先配置了lua文件的根目录,还有lua库的目录,然后根据需要分配了ip黑名单、ip白名单等几个缓存区,以及初始化lua文件的指定,log阶段的处理文件,还有一个定时任务的lua文件。其中初始化文件就是刚才说的那个文件,叙述顺序有点乱哈,见谅!

       定义好这些准备工作之后,就可以来看看系统是怎么做到限流的,要使用这个模块,在需要检测的location中添加配置:

     access_by_lua_file ,这个配置项的值是access.lua的路径,access是整个项目进行限流的核心文件,当一个请求到达nginx时候,会首先由这个文件来判断是否能进行下一步的访问,如果能,就提供服务,不能的话返回403。下边贴出access的代码:

 
  1. if not LUA_MAIN_SWITCH or LUA_MAIN_SWITCH ~= 'ON' then return end

  2.  
  3. local statistics = require('common.count')

  4. local util = require('common.util')

  5. local strategy = require('common.strategy')

  6.  
  7. local ip = ngx.var.remote_addr

  8. local uri = ngx.var.uri

  9.  
  10. local timeout = 0

  11. local maxcount = 0

  12. local sum = 0

  13. local ban_type = "all"

  14.  
  15. local _, in_whitelist = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri_whitelist)

  16.  
  17. local _, in_blacklist = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri_blacklist)

  18.  
  19. --检测ip是否在黑白名单或者是否被限制

  20. local result = util:check_config_access(ip,in_blacklist,in_whitelist)

  21.  
  22. if result == 2 then

  23. ngx.exit(ngx.HTTP_FORBIDDEN)

  24. return

  25. elseif result == 1 then

  26. return

  27. else

  28. local banlist_ip = util:check_banlist_access_ip(ip)

  29. if banlist_ip == 2 then

  30. ngx.exit(ngx.HTTP_FORBIDDEN)

  31. return

  32. end

  33. end

  34. ----检测uri是否在黑白名单或者是否被限制

  35. local result_uri = util:check_config_access(uri,in_blacklist,in_whitelist)

  36. if result_uri == 2 then

  37. ngx.exit(ngx.HTTP_FORBIDDEN)

  38. return

  39. elseif result_uri == 1 then

  40. return

  41. else

  42. local banlist_uri = util:check_banlist_access_uri(uri)

  43. if banlist_uri == 2 then

  44. ngx.exit(ngx.HTTP_FORBIDDEN)

  45. return

  46. end

  47. end

  48. --检测发帖动作是否被限制

  49. local banlist_ip_uri = util:check_banlist_access_ip_uri(ip..uri)

  50. if banlist_ip_uri == 2 then

  51. ngx.exit(ngx.HTTP_FORBIDDEN)

  52. return

  53. end

  54. ---check type

  55. local re = util:check_st_type()

  56. if re == 1 then return end

  57. --获取 配置文件 中的策略. 根据 策略 来统计 数据,智能封ip uri

  58. --根据ip地址来计数

  59. if IP_COUNT and IP_COUNT == 'ON' then

  60. --如果是发帖的请求,则开始计数

  61. if uri == '/forum.php?mod=ajax&action=checkpostrule&ac=newthread&inajax=yes' and IP_URI_COUNT and IP_URI_COUNT == 'ON' then

  62. local result = strategy:check_strategy('ip_uri')

  63. --ngx.say(result.ban_type..result.sum )

  64. if result ~= nil and result.activate then

  65. timeout = result.duration

  66. sum = result.sum

  67. maxcount = result.times

  68. ban_type = result.ban_type

  69.  
  70.  
  71. else

  72. util:clear_st_dict()

  73. end

  74.  
  75.  
  76. statistics:new(timeout,maxcount,sum,ban_type)

  77. --ip

  78. statistics:count_ban(ip..uri, ngx.shared.dt_st_ip,ngx.shared.dt_intelligent_ban_ip_list,'ip_uri')

  79.  
  80. --uri

  81. local key,flags = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri)

  82. if flags then

  83.  
  84. statistics:count_warn(ip..'_'..key, ngx.shared.dt_st_ip_uri,ngx.shared.dt_intelligent_ban_ip_list,'ip_uri',ip)

  85. end

  86. end

  87. local result = strategy:check_strategy('ip')

  88. --ngx.say(result.ban_type..result.sum )

  89. if result ~= nil and result.activate then

  90. timeout = result.duration

  91. sum = result.sum

  92. maxcount = result.times

  93. ban_type = result.ban_type

  94.  
  95.  
  96. else

  97. util:clear_st_dict()

  98. end

  99.  
  100.  
  101. statistics:new(timeout,maxcount,sum,ban_type)

  102. --ip

  103. statistics:count_ban(ip, ngx.shared.dt_st_ip,ngx.shared.dt_intelligent_ban_ip_list,'ip')

  104.  
  105. if type(WARN_LOG.switch) ~= 'string' or WARN_LOG.switch~= 'ON' then return end

  106.  
  107. --ip

  108. statistics:count_warn(ip, ngx.shared.dt_st_ip_log,ngx.shared.dt_intelligent_ban_ip_list,'ip',ip)

  109.  
  110.  
  111. end

  112. --根据uri来计数

  113. if URI_COUNT and URI_COUNT == 'ON' and ngx.shared.dt_uri:get(uri) then

  114.  
  115. local result = strategy:check_strategy('uri')

  116. --ngx.say(result.ban_type..result.sum )

  117. if result ~= nil and result.activate then

  118. timeout = result.duration

  119. sum = result.sum

  120. maxcount = result.times

  121. ban_type = result.ban_type

  122.  
  123.  
  124. else

  125. util:clear_st_dict()

  126. end

  127.  
  128. statistics:new(timeout,maxcount,sum,ban_type)

  129. --uri

  130. statistics:count_ban(uri, ngx.shared.dt_st_uri,ngx.shared.dt_intelligent_ban_uri_list,'uri')

  131.  
  132. if type(WARN_LOG.switch) ~= 'string' or WARN_LOG.switch~= 'ON' then return end

  133.  
  134. --warn

  135. statistics:count_warn(uri, ngx.shared.dt_st_uri_log,ngx.shared.dt_intelligent_ban_uri_list,'uri',uri)

  136.  
  137. end

这个文件的逻辑是,先判断这个请求的ip地址是不是在黑名单、白名单,如果是在黑名单则拒绝访问,白名单的话则任何情况下不会禁止访问,urihi额白名单类似,也是这样一个判断逻辑。然后呢,就是判断这个ip地址的访问频率是不是超过了要求,这里多加了一个ip和uri共同的这样的一个标准判断,如果同一个ip地址在同一时间内访问某一个uri超过了规定的次数,就返回403。uri的判断标准也是一样的。那么,这个判断的标准怎么产生的呢,是不是记录了访问次数啊?跟这个差不多,但是更复杂一些,也更能适应高并发压力一些。下边以ip为准说明这个问题,首先每一个ip进来都会被记录,键值为ip,value为次数,但是呢,这个统计并不是一个长时间的统计,而是一段时间,比方说只统计3秒内的访问量,然后计算平均值,如果这个平均值超过了这一要求,也并不是就马上封掉这个ip,而是再统计几个相同的时间段,如果这几个时间段内均超过要求的频率,那么这时候才封掉这个ip。为什么要这么做呢,是因为封掉一个ip不是很随意的就封掉,有可能这个ip确实是在某一个时间段内需要这个高频率的访问,但是并不是恶意的,这个时候直接把他封掉,就不合适了,所以要看几个时间段内的访问情况,再来确定是不是要封掉。那怎么让系统知道这个ip已经被疯掉了呢,很简单,把这个ip保存到redis中。上边所说的ip检查就是去redis里查询这个。这里又有一个问题,既然之前保存黑白名单用的是nginx的缓存,也就是本地内存,为什么保存这个标志的时候去用了redis,这个主要是因为在我这个项目中,我们刚好用到了redis,而且服务器的数量偏多,为了做到精确控制,才这么做的。当然也可以直接使用本地缓存来保存这个,这样的话就是只针对这一台服务器,而不是整个集群做限流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值