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来进行计数,达到一定数量之后就 限制访问。这应该是限流算法中的计数法,另外还有令牌算法和漏桶算法,不再详细介绍。
模块的流程图如下:
下面根据流程图来分解代码。
首先,是系统初始化,初始化主要做一些内存分配,名单载入的工作,
-
<div style="text-align: left;"><pre name="code" class="html">--require and loadfile to shm
-
require("config.config")
-
cjson = require("cjson")
-
local readfile = require("common.readfile")
-
cookie = require('common.cookie')
-
--preload in init
-
require('common.util')
-
require('common.count')
-
require('common.strategy')
-
redis = require("cache.redis")
-
ngx.shared.dt_ip_whitelist:flush_all()
-
ngx.shared.dt_ip_blacklist:flush_all()
-
ngx.shared.dt_uri_whitelist:flush_all()
-
ngx.shared.dt_uri_blacklist:flush_all()
-
ngx.shared.dt_st_ip:flush_all()
-
ngx.shared.dt_st_ip_uri:flush_all()
-
ngx.shared.dt_st_ip_log:flush_all()
-
ngx.shared.dt_st_web:flush_all()
-
ngx.shared.dt_uri:flush_all()
-
ngx.shared.dt_config:flush_all()
-
ngx.shared.dt_intelligent_ban_ip_list:flush_all()
-
ngx.shared.dt_intelligent_ban_uri_list:flush_all()
-
ngx.shared.dt_intelligent_ban_ip_uri_list:flush_all()
-
---add switch
-
if not LUA_MAIN_SWITCH or LUA_MAIN_SWITCH ~= 'ON' then return end
-
if IP_WHITE_LIST_SWITCH == "ON" then
-
local filename = ROOT_PATH..'lua/config/ip_whitelist'
-
readfile:readfileforshm(filename,ngx.shared.dt_ip_whitelist)
-
end
-
if IP_BLACK_LIST_SWITCH == "ON" then
-
local filename = ROOT_PATH..'lua/config/ip_blacklist'
-
readfile:readfileforshm(filename,ngx.shared.dt_ip_blacklist)
-
end
-
--uri b and w
-
if URI_BLACK_LIST_SWITCH == "ON" then
-
local filename = ROOT_PATH..'lua/config/uri_blacklist'
-
readfile:readfileforuri(filename,ngx.shared.dt_uri_blacklist)
-
end
-
if URI_WHITE_LIST_SWITCH == "ON" then
-
local filename = ROOT_PATH..'lua/config/ip_whitelist'
-
readfile:readfileforshm(filename,ngx.shared.dt_uri_whitelist)
-
end
-
--ansys uri
-
local filename = ROOT_PATH..'lua/config/statistics_uri'
-
readfile:readfileforuri(filename,ngx.shared.dt_uri)
可以看得出来,刚开始做的就是把几个缓存区刷新,然后根据要求载入ip黑白名单、uri黑白名单等,最后加入了一个叫做statics_uri的文件,这个文件的作用是把需要统计监测的uri记录下来,并不是把所有的uri都监测起来,那样肯定不现实,一个大的web项目会有很多uri,只能说找出几个比较重要的来监测和控制。对了,这些内存实在配置文件中设置好的,使用了nginx-lua模块,具体的环境配置可以看我另外一篇教程。
-
lua_package_path "/usr/servers/nginx/conf/lua/?.lua;;";
-
lua_package_cpath "/usr/servers/lualib/?.so";
-
# add dict to share memcache
-
lua_shared_dict dt_ip_whitelist 10m;
-
lua_shared_dict dt_uri_whitelist 10m;
-
lua_shared_dict dt_uri_blacklist 10m;
-
lua_shared_dict dt_ip_blacklist 10m;
-
lua_shared_dict dt_intelligent_ban_ip_list 10m;
-
lua_shared_dict dt_intelligent_ban_uri_list 10m;
-
lua_shared_dict dt_intelligent_ban_ip_uri_list 10m;
-
lua_shared_dict dt_uri 10m;
-
lua_shared_dict dt_config 2m;
-
lua_shared_dict dt_st_ip 10m;
-
lua_shared_dict dt_st_uri 10m;
-
lua_shared_dict dt_st_web 10m;
-
lua_shared_dict dt_st_ip_uri 10m;
-
lua_shared_dict dt_st_ip_log 10m;
-
#lua_shared_dict shared_data 10m;
-
#init model and load config
-
init_by_lua_file /usr/servers/nginx/conf/lua/init/init.lua;
-
log_by_lua_file /usr/servers/nginx/conf/lua/statistics/statistics.lua;
-
init_worker_by_lua_file conf/lua/statistics/check.lua;
-
#init_worker_by_lua_file
配置文件上来先配置了lua文件的根目录,还有lua库的目录,然后根据需要分配了ip黑名单、ip白名单等几个缓存区,以及初始化lua文件的指定,log阶段的处理文件,还有一个定时任务的lua文件。其中初始化文件就是刚才说的那个文件,叙述顺序有点乱哈,见谅!
定义好这些准备工作之后,就可以来看看系统是怎么做到限流的,要使用这个模块,在需要检测的location中添加配置:
access_by_lua_file ,这个配置项的值是access.lua的路径,access是整个项目进行限流的核心文件,当一个请求到达nginx时候,会首先由这个文件来判断是否能进行下一步的访问,如果能,就提供服务,不能的话返回403。下边贴出access的代码:
-
if not LUA_MAIN_SWITCH or LUA_MAIN_SWITCH ~= 'ON' then return end
-
local statistics = require('common.count')
-
local util = require('common.util')
-
local strategy = require('common.strategy')
-
local ip = ngx.var.remote_addr
-
local uri = ngx.var.uri
-
local timeout = 0
-
local maxcount = 0
-
local sum = 0
-
local ban_type = "all"
-
local _, in_whitelist = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri_whitelist)
-
local _, in_blacklist = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri_blacklist)
-
--检测ip是否在黑白名单或者是否被限制
-
local result = util:check_config_access(ip,in_blacklist,in_whitelist)
-
if result == 2 then
-
ngx.exit(ngx.HTTP_FORBIDDEN)
-
return
-
elseif result == 1 then
-
return
-
else
-
local banlist_ip = util:check_banlist_access_ip(ip)
-
if banlist_ip == 2 then
-
ngx.exit(ngx.HTTP_FORBIDDEN)
-
return
-
end
-
end
-
----检测uri是否在黑白名单或者是否被限制
-
local result_uri = util:check_config_access(uri,in_blacklist,in_whitelist)
-
if result_uri == 2 then
-
ngx.exit(ngx.HTTP_FORBIDDEN)
-
return
-
elseif result_uri == 1 then
-
return
-
else
-
local banlist_uri = util:check_banlist_access_uri(uri)
-
if banlist_uri == 2 then
-
ngx.exit(ngx.HTTP_FORBIDDEN)
-
return
-
end
-
end
-
--检测发帖动作是否被限制
-
local banlist_ip_uri = util:check_banlist_access_ip_uri(ip..uri)
-
if banlist_ip_uri == 2 then
-
ngx.exit(ngx.HTTP_FORBIDDEN)
-
return
-
end
-
---check type
-
local re = util:check_st_type()
-
if re == 1 then return end
-
--获取 配置文件 中的策略. 根据 策略 来统计 数据,智能封ip uri
-
--根据ip地址来计数
-
if IP_COUNT and IP_COUNT == 'ON' then
-
--如果是发帖的请求,则开始计数
-
if uri == '/forum.php?mod=ajax&action=checkpostrule&ac=newthread&inajax=yes' and IP_URI_COUNT and IP_URI_COUNT == 'ON' then
-
local result = strategy:check_strategy('ip_uri')
-
--ngx.say(result.ban_type..result.sum )
-
if result ~= nil and result.activate then
-
timeout = result.duration
-
sum = result.sum
-
maxcount = result.times
-
ban_type = result.ban_type
-
else
-
util:clear_st_dict()
-
end
-
statistics:new(timeout,maxcount,sum,ban_type)
-
--ip
-
statistics:count_ban(ip..uri, ngx.shared.dt_st_ip,ngx.shared.dt_intelligent_ban_ip_list,'ip_uri')
-
--uri
-
local key,flags = statistics:check_uri_inlist(ngx.var.uri,ngx.req.get_uri_args(),ngx.shared.dt_uri)
-
if flags then
-
statistics:count_warn(ip..'_'..key, ngx.shared.dt_st_ip_uri,ngx.shared.dt_intelligent_ban_ip_list,'ip_uri',ip)
-
end
-
end
-
local result = strategy:check_strategy('ip')
-
--ngx.say(result.ban_type..result.sum )
-
if result ~= nil and result.activate then
-
timeout = result.duration
-
sum = result.sum
-
maxcount = result.times
-
ban_type = result.ban_type
-
else
-
util:clear_st_dict()
-
end
-
statistics:new(timeout,maxcount,sum,ban_type)
-
--ip
-
statistics:count_ban(ip, ngx.shared.dt_st_ip,ngx.shared.dt_intelligent_ban_ip_list,'ip')
-
if type(WARN_LOG.switch) ~= 'string' or WARN_LOG.switch~= 'ON' then return end
-
--ip
-
statistics:count_warn(ip, ngx.shared.dt_st_ip_log,ngx.shared.dt_intelligent_ban_ip_list,'ip',ip)
-
end
-
--根据uri来计数
-
if URI_COUNT and URI_COUNT == 'ON' and ngx.shared.dt_uri:get(uri) then
-
local result = strategy:check_strategy('uri')
-
--ngx.say(result.ban_type..result.sum )
-
if result ~= nil and result.activate then
-
timeout = result.duration
-
sum = result.sum
-
maxcount = result.times
-
ban_type = result.ban_type
-
else
-
util:clear_st_dict()
-
end
-
statistics:new(timeout,maxcount,sum,ban_type)
-
--uri
-
statistics:count_ban(uri, ngx.shared.dt_st_uri,ngx.shared.dt_intelligent_ban_uri_list,'uri')
-
if type(WARN_LOG.switch) ~= 'string' or WARN_LOG.switch~= 'ON' then return end
-
--warn
-
statistics:count_warn(uri, ngx.shared.dt_st_uri_log,ngx.shared.dt_intelligent_ban_uri_list,'uri',uri)
-
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,而且服务器的数量偏多,为了做到精确控制,才这么做的。当然也可以直接使用本地缓存来保存这个,这样的话就是只针对这一台服务器,而不是整个集群做限流。