Nginx结合Lua实现限流

最近的工作变动比较大,所以很长时间没有写关于nginx+lua的文章了,现在总算是有时间重新开始研究nginx+lua这个使nginx变的更加灵活的技术了。在学习过程中的经验和遇到的问题记录下来。安装过程可以结合之前的文章“nginx服务治理安装篇”,目前采用的是在nginx基础上安装一些模块,进行学习而没有选择使用openresty,是因为基于公司网络架构及部署架构限制。

背景

之前一直在负责一个新的项目,由于发生了比较悲催的服务器宕机的情况,其主因是由于业务量激增导致服务器压力无法承受住导致服务器宕机业务中断,所以工作调整开始研究服务治理相关的技术以支持这种情况,保证业务量暴增的情况下服务器不被压死。

限流模块

采用的是openresty的lua-resty-limit-traffic模块,这个模块不需要随着nginx安装时进行添加,在使用时直接引入即可,还是比较方便的。

设计思路

  1. 初始化限流策略信息,例如按照渠道或者ip地址。每个的处理速度也就是处理的并发量,还有就是桶容量大小(超过每秒处理的量后还看可以缓存多少)
  2. 获取请求参数
  3. 根据参数在策略中查找并获取相应的阀值,若不存在则采用默认值。
  4. 使用lua-resty-limit-traffic进行限流。

代码实现

  1. 先说两个工具类,一个是字符串分割,一个nginx缓存的设置和获取。
  • 字符串分割 splitutil.lua
/** Created by Erick.
**  DateTime: 2019-1-4 16:59
**/
local _M = {}
function _M.split(str,delim)
    if type(delim) ~= "string" or string.len(delim) <= 0 then
        return
    end
    local start = 1
    local tab = {}
    while true do
        local pos = string.find(str,delim,start,true)
        if not pos then
            break
        end
        table.insert(tab,string.sub (str,start,pos-1))
        start = pos+string.len(delim)
    end
    table.insert(tab,string.sub(str,start))
    return tab
end
return _M
  • 缓存的get和set,sharedoperutil.lua
/** Created by Erick.
**  DateTime: 2019-1-9 16:52
**  operate shared_dic
**/
local _M = {}
/** get value in dic
**  dic : nginx shared dic name
**  key : the nginx shared dic's key
**/
function _M.shared_dic_get(dic , key)
    local value = dic:get(key)
    return value
end

/** set value in dic
**  dic : defined nginx shared dic name
**  key : the nginx shared dic's key
**  value : reset value
**  exptime : the key and value exp time
**/
function _M.shared_dic_set(dic , key , value , exptime)
    if not exptime then
        exptime = 0
    end
    local succ , err = dic:set(key , value , exptime)
    return succ
end
return _M
  1. 初始化限流策略信息 limitdata_init.lua 初始化限流策略信息 limitdata_init.lua
/** Created by Erick.
**  DateTime: 2019-1-4 16:35
**  初始化文件限流数据到table中
**/
local limit_table = ngx.shared.my_limit_store
local splitutil = require("splitutil")
file = io.open("../lua/syslimitrate.txt","r")
if nil == file then
    ngx.log(ngx.INFO, "文件读取失败,将采用默认值进行赋值")
    local suc,err = limit_table:set("default" , "15-15")
else
    for line in file:lines() do
        local splitTable = splitutil.split(line , "-")
        local sysFlag = splitTable[1]
        local limitRate = splitTable[2]
        local bursts = splitTable[3]
        local tableVal = string.format("%s-%s" , limitRate , bursts)
        ngx.log(ngx.INFO, "sysFlag = " , sysFlag , "限流阀值:流速-桶容量:" , tableVal)
        limit_table:set(sysFlag , tableVal)
    end
end
file:close()

初始化数据主要是从文件中读取然后放到nginx的定义的内存空间ngx.shared.my_limit_store中,若是读取文件失败则设置一个默认值。

  • syslimitrate.txt文件内容
/**格式为:渠道-每秒处理并发量-缓存容量**/
taobao-10-200
baidu-50-200
  1. 获取请求参数并查询对应的限流阀值进行限流limitrate_access.lua
/** Created by Erick.
**  DateTime: 2019-1-8 16:14
**/
// 获取请求参数
local splitutil = require("splitutil")
ngx.req.read_body()
local args, err = ngx.req.get_post_args()
local sysFlag = args["sys"]
local limit_table = ngx.shared.my_limit_store
local limitRate = limit_table:get(sysFlag)
if not limitRate then
    ngx.log(ngx.INFO, "sysFlag can not found so set defalut value")
    limitRate = limit_table:get("default")
end
// 获取到的值进行拆分并限流
local limitValue = splitutil.split(limitRate,"-")
local rate = tonumber(limitValue[1])
local burst = tonumber(limitValue[2])
local limit_req = require "resty.limit.req"
ngx.say("rate====" , rate , "---burst=====",burst)
// 根据配置项创建一个限流的table。
local lim, err = limit_req.new("my_limit_req_store", rate, burst)
if not lim then
    ngx.log(ngx.ERR,
            "failed to instantiate a resty.limit.req object: ", err)
    return ngx.exit(500)
end
// 根据渠道标识进行限流
local delay, err = lim:incoming(sysFlag, true)
if not delay then
    if err == "rejected" then
        ngx.say("rejected access service..............")
        ngx.log(ngx.INFO,"rejected access service..............",err)
        return ngx.exit(503)
    end
    ngx.log(ngx.INFO, "failed to limit req: ", err)
    return ngx.exit(500)
end
ngx.log(ngx.INFO,"access received..............")
ngx.say("access received..............")
  1. 动态调整阀值信息limitvaluereset_content.lua
/** Created by Erick.
**  DateTime: 2019-1-9 14:27
**  this lua script can reset limitrate value
**/
local splitutil = require("splitutil")
local sharedoperutil = require("sharedoperutil")
// get request param
local args, err = ngx.req.get_uri_args()
local limitvalue = args["limitvalue"]
ngx.say("-----------limitvalue-----------" , limitvalue)
local limitParam = splitutil.split(limitvalue , "-")
local sysFlag = limitParam[1]
ngx.say(sharedoperutil.shared_dic_get(ngx.shared.my_limit_store,sysFlag))
local rateBurst = string.format("%s-%s" , limitParam[2] , limitParam[3])
ngx.say("-------------rateBurst:" , rateBurst)
sharedoperutil.shared_dic_set(ngx.shared.my_limit_store , sysFlag , rateBurst , 0)
ngx.say("---------------" , sharedoperutil.shared_dic_get(ngx.shared.my_limit_store,sysFlag))

以上是所有关于Lua脚本的内容,下面与nginx进行整合。

整合

以下是nginx.conf的配置。

worker_processes  2;
pid        logs/nginx.pid;

events {
    worker_connections  10240;
}
error_log  logs/access.log info;

http {
	// 定义以及引入限流模块
	lua_shared_dict my_limit_store 10m;
	lua_shared_dict my_limit_req_store 100m;
	lua_package_path "nginx-1.12.1/lua-resty-limit-traffic-0.05/lib/?.lua;nginx-1.12.1/nginx-1.12.1/lua/?.lua;;";
		
    include       mime.types;
   
    log_format  main  '$remote_addr - $time_local - $request_method - $request_uri - $status - $body_bytes_sent - $request_time';
    access_log  logs/access.log  main;

    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay    on;

    keepalive_timeout  10;
    keepalive_requests 500;
    
    client_header_timeout 60;
    client_body_timeout   60;
    send_timeout          60;
    
    open_file_cache max=1024  inactive=120s;
    open_file_cache_valid 30;
    open_file_cache_min_uses 5;
  
    gzip on;
    gzip_http_version 1.0;
    gzip_comp_level 2;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/json;
    gzip_disable msie6;

    client_body_in_file_only off;    
    client_body_temp_path  http_body_dir 1 2;
    client_body_in_single_buffer on;
    client_max_body_size 10m;
    client_body_buffer_size 128k;
    client_header_buffer_size 4k;
    large_client_header_buffers 4 16k;
    // ---------初始化限流信息start-------------
    init_by_lua_file lua/limitdata_init.lua;
    // ---------初始化限流信息end-------------
  	// 这部分用到一个自动上线下的模块,在文章最后简单说下
	upstream myupstream {
		zone zone_for_myupstream 1m;
	    server 192.168.1.10:8080;
		server 192.168.1.11:8080;
	}
	server {
        listen       8890;
        server_name  localhost;

		location /dynamic {
			#allow 127.0.0.1;
			#deny all;
			dynamic_upstream;
         }
		location /limit{
			#allow 127.0.0.1;
			#deny all;
			// --------重置限流阀值start--------
			content_by_lua_file lua/limitvaluereset_content.lua;
			//-------重置限流阀值end----------
		}
		location / {
			//--------访问控制进行限流start---------
			access_by_lua_file lua/limitrate_access.lua;
			//---------访问控制进行限流end-----------
			proxy_pass http://myupstream;
			root   html;
			index  index.html index.htm;
		}
		error_page   500 502 503 504  /50x.html;
		location = /50x.html {
			root   html;
		}
	}
}

引入lua脚本的地方增加了注释说明,至此限流也就说完了,上边有个地方是自动上下线服务在这里说明下。

ngx_dynamic_upstream

调用api接口可以实现例如服务器的上线,下线,配置权重,增加/删除机器,调整后不用修改比较方便,不做详细说明了,感兴趣的可以去github上查看,点击这里学习。


Nginx结合Lua实现限流_第1张图片

你可能感兴趣的:(Nginx)