Some common current limiting and anti-brush methods under high concurrency

Current limiting and anti-swiping
Internet projects are different from traditional projects. Internet projects are exposed to the Internet and are aimed at all netizens. At this time, the following two access forms may occur, requiring us to take some necessary measures to protect our services.
1. High-frequency access of a large number of normal users leads to server downtime
2. High-frequency access of malicious users leads to server downtime
3. Web crawler
In these cases, we need to limit current access to user access. Limit current.
Nginx is the layer with the largest granularity. We need to operate the frequency setting of this layer carefully. This will affect our entire website access. The frequency setting of the Nginx layer should be below the downtime threshold of our application server. Let's take a look at the details. How to set
Nginx current limit settings
to limit IP/domain name

http {
     limit_conn_zone $binary_remote_addr zone=perip :10m; # 保存IP的缓存为10M;16000个IP地址的状态信息约1MB
     limit_conn_zone $server_name zone=perserver:10m;
     ...
     server {
       limit_conn perserver 100;# 此域名下最多有100个连接
       limit_conn perip 10;# 一个IP最多有10个连接
         ...
     }
}
# $binary_remote_addr 要限流的IP地址
# $server_name 要限流的域名
location / {
        limit_req zone=perip burst=20 nodelay;# urst排队大小,nodelay不限制单个请求间的时间
        proxy_pass http://XXX.com;
}
# 限流白名单
geo $limit {
    default   1;
    192.168.2.0/24  0;# 192.168.2.1-192.168.2.254 且子网掩码是255.255.255.0 网段不限流
    # 24 表示子网掩码 255.255.255.0
    # 16 表示子网掩码 255.255.0.0
    # 8 表示子网掩码 255.0.0.0
}

map $limit $limit_key {
    1 $binary_remote_addr;
    0 "";
}

limit_req_zone $limit_key zone=mylimit:10m rate=1r/s;

location / {
        limit_req zone=perid burst=1 nodelay;
        proxy_pass http://XXXX.com;
}

The token algorithm current limiting (excerpted from https://blog.csdn.net/sunnyyoona/article/details/51228456 )
The token bucket algorithm originally originated from the computer network. When transmitting data on the network, in order to prevent network congestion, it is necessary to limit the flow out of the network, so that the flow is sent out at a relatively uniform speed. The token bucket algorithm implements this function, which can control the amount of data sent to the network and allow burst data to be sent.
write picture description here
Algorithm Description:

  • If the average sending rate configured by the user is r, a token is added to the bucket every 1/r second (r tokens are put into the bucket every second);

  • It is assumed that at most b tokens can be stored in the bucket. If the token bucket is full when the token arrives, the token will be discarded;

  • When a packet of n bytes arrives, n tokens are removed from the token bucket (packets of different sizes, the number of tokens consumed is different), and the packet is sent to the network;

  • If there are less than n tokens in the token bucket, the token is not removed and the packet is considered to be outside the traffic limit (n bytes, n tokens required. The packet will be cached or dropped) ;

  • The algorithm allows bursts of up to b bytes, but from the long-run results, the packet rate is limited to a constant r. Packets outside the flow limit can be handled in different ways: (1) they can be dropped; (2) they can be queued for transmission when enough tokens have accumulated in the token bucket; ( 3) They can continue to be sent, but they need to be specially marked, and these specially marked packets will be discarded when the network is overloaded.

Note:
The token bucket algorithm should not be confused with another common algorithm, the leaky bucket algorithm. The main difference between the two algorithms is that the leaky bucket algorithm can forcibly limit the transmission rate of data, while the token bucket algorithm can limit the average transmission rate of data and also allow a certain degree of burst transmission. In the token bucket algorithm, as long as there are tokens in the token bucket, data is allowed to be transmitted in bursts until the user-configured threshold is reached, so it is suitable for traffic with burst characteristics.
Combined with Lua to limit the current count of different interfaces,
for example, the application limits 100 requests per second

http {
    local shared_data = ngx.shared.dict
    shared_data:set("draw", 0)

    content_by_lua_block {
        local request_uri = ngx.var.request_uri;
        if string.sub(request_uri,1,22) == "/activity/lottery/draw" then
              local val, err = ngx.shared.dict:incr("draw", 1); #进来一个请求就加1
              if val > 100 then #限流100
                  ngx.log(ngx.ERR,"draw limit val is:"..val)
                  ngx.exit(503)
              end
              ....业务处理
        end
    }
...
  log_by_lua_block{
        local request_uri = ngx.var.request_uri;
        if string.sub(request_uri,1,22) == "/activity/lottery/draw" then
              local val, err = ngx.shared.dict:incr("draw", -1); #出去一个请求就减1
              if val < 0 then 
                  ngx.shared.dict:set("draw", 0);
              end
        end

  }
}

Next, we are looking at an operation of redis using Lua, and the current limit is realized by setting the key count and validity period of redis.
The following code is configured in nginx.conf and can be used, for example:

server {
    listen  80;

    location / {
        access_by_lua_file /opt/lua/access/rateLimter.lua;
        proxy_pass http://www.xxx.com;
    }
}
# nginx常用lua模块还有
lua_code_cache
语法:lua_code_cache on | off
默认: on
适用上下文:http、server、location、location if
这个指令是指定是否开启lua的代码编译缓存,开发时可以设置为off,以便lua文件实时生效,如果是生产线上,为了性能,建议开启。
lua_package_path
语法:lua_package_path <lua-style-path-str>
默认:由lua的环境变量决定
适用上下文:http
设置lua代码的寻找目录。
例如:lua_package_path "/opt/nginx/conf/www/?.lua;;";
具体的路径设置要参考lua的模块机制
init_by_lua(_file)
语法:init_by_lua <lua-script-str>
适用上下文:http
 init_by_lua 'cjson = require "cjson"';

    server {
        location = /api {
            content_by_lua '
                ngx.say(cjson.encode({dog = 5, cat = 6}))
            ';
        }
    }
从这段配置代码,我们可以看出,其实这个指令就是初始化一些lua的全局变量,以便后续的代码使用。
注:有(_file)的选项代表可以直接引用外部的lua源代码文件,效果与直接写配置文件一样,不过可维护性当然是分开好点。
init_worker_by_lua(_file)
类似于上面的,不过是作用在work进程的,先于work进程启动而调用。
set_by_lua(_file)
语法:set_by_lua $res <lua-script-str> [$arg1 $arg2 ...]
适用上下文:server、location、location if
 location /foo {
        set $diff ''; # we have to predefine the $diff variable here

        set_by_lua $sum '
            local a = 32
            local b = 56

            ngx.var.diff = a - b;  -- write to $diff directly
            return a + b;          -- return the $sum value normally
        ';
        echo "sum = $sum, diff = $diff";
    }
这个指令是为了能够让nginx的变量与lua的变量相互作用赋值。
content_by_lua(_file)
语法:content_by_lua <lua-script-str>
适用上下文:location、location if
        location /nginx_var {
            # MIME type determined by default_type:
            default_type 'text/plain';

            # try access /nginx_var?a=hello,world
            content_by_lua "ngx.print(ngx.var['arg_a'], '\\n')";
        }
通过这个指令,可以由lua直接确定nginx响应页面的正文。
rewrite_by_lua(_file)
语法:rewrite_by_lua <lua-script-str>
适用上下文:location、location if
这个指令更多的是为了替代HttpRewriteModule的rewrite指令来使用的,优先级低于rewrite指令
比如
 location /foo {
          set $a 12; # create and initialize $a
          set $b ''; # create and initialize $b
          rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1';
          if ($b = '13') {
             rewrite ^ /bar redirect;
             break;
          }

         echo "res = $b";
    }
这个并不会像预期的那样子,因为我猜测,rewrite_by_lua是开启一个协程去工作的,可是下面却继续执行下去了,所以得不到预期的结果。
此时如果由lua代码来控制rewrite,那就没有问题了。
    location /foo {
        set $a 12; # create and initialize $a
        set $b ''; # create and initialize $b
        rewrite_by_lua '
            ngx.var.b = tonumber(ngx.var.a) + 1
            if tonumber(ngx.var.b) == 13 then
                return ngx.redirect("/bar");
            end
        ';

        echo "res = $b";
    }
access_by_lua(_file)
语法:access_by_lua <lua-script-str>
适用上下文:http, server, location, location if
 location / {
        deny    192.168.1.1;
        allow   192.168.1.0/24;
        allow   10.1.1.0/16;
        deny    all;

        access_by_lua '
            local res = ngx.location.capture("/mysql", { ... })
            ...
        ';

        # proxy_pass/fastcgi_pass/...
    }
    顾名思义,这个指令用在验证通过或者需要验证的时候。


header_filter_by_lua(_file)
语法:header_filter_by_lua <lua-script-str>
适用上下文:http, server, location, location if
    location / {
        proxy_pass http://mybackend;
        header_filter_by_lua 'ngx.header.Foo = "blah"';
    }
用lua的代码去指定http响应的 header一些内容。


body_filter_by_lua(_file)
语法:body_filter_by_lua <lua-script-str>
适用上下文:http, server, location, location if
   location /t {
        echo hello world;
        echo hiya globe;

        body_filter_by_lua '
            local chunk = ngx.arg[1]
            if string.match(chunk, "hello") then
                ngx.arg[2] = true  -- new eof
                return
            end

            -- just throw away any remaining chunk data
            ngx.arg[1] = nil
        ';
    }
这个指令可以用来篡改http的响应正文的。

Script storage directory: /opt/lua/access/rateLimter.lua

local function close_redis(red)  
    if not red then  
        return  
    end    
    local pool_max_idle_time = 10000  
    local pool_size = 100  
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  

    if not ok then  
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)  
    end  
end  

local function wait()  
   ngx.sleep(1)  
end  

local redis = require "resty.redis"  
local red = redis:new()  
red:set_timeout(1000)  
local ip = "redis-ip"  
local port = redis-port  
local ok, err = red:connect(ip,port)  
if not ok then  
    return close_redis(red)  
end  

local uri = ngx.var.uri   
local uriKey = "req:uri:"..uri  
res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)  
while (res > 10)  
do   
   local twait, err = ngx.thread.spawn(wait)  
   ok, threadres = ngx.thread.wait(twait)  
   if not ok then  
      ngx_log(ngx_ERR, "wait sleep error: ", err)  
      break;  
   end  
   res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)  
end  
close_redis(red)  

Below we are looking at a brush-proof Lua code example

local function close_redis(red)  
    if not red then  
        return  
    end   
    local pool_max_idle_time = 10000 
    local pool_size = 100 
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  

    if not ok then  
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)  
    end  
end  

local redis = require "resty.redis"  
local red = redis:new()  
red:set_timeout(1000)  
local ip = "redis-ip"  
local port = redis-port  
local ok, err = red:connect(ip,port)  
if not ok then  
    return close_redis(red)  
end  

local clientIP = ngx.req.get_headers()["X-Real-IP"]  
if clientIP == nil then  
   clientIP = ngx.req.get_headers()["x_forwarded_for"]  
end  
if clientIP == nil then  
   clientIP = ngx.var.remote_addr  
end  

local incrKey = "user:"..clientIP..":freq"  
local blockKey = "user:"..clientIP..":block"  

local is_block,err = red:get(blockKey)
if tonumber(is_block) == 1 then  
   ngx.exit(ngx.HTTP_FORBIDDEN)  
   return close_redis(red)  
end  

res, err = red:incr(incrKey)  

if res == 1 then  
   res, err = red:expire(incrKey,1)  
end  

if res > 200 then  
    res, err = red:set(blockKey,1)  
    res, err = red:expire(blockKey,600)  
end  

close_redis(red)  

Nginx blacklist
When we find some malicious IPs, we can put them in the blacklist, the configuration is as follows:

# 白名单设置,访问根目录
location / {
                allow 123.34.22.155;
                deny  all;# allow 优先级>deny
}

# 黑名单设置,访问根目录,这时候访问Nginx会出现403 forbidden字样
location / {
                deny 123.34.22.155;
}

# 特定目录访问限制
location /tree/list {
                allow 123.34.22.155;
                deny  all;
}

Or we can dynamically manage the blacklist IP through Luau+redis
First modify nginx.conf

lua_shared_dict ip_blacklist 1m;

server {
    listen  80;

    location / {
        access_by_lua_file lua/ip_blacklist.lua;
        proxy_pass http://real_server;
    }
}
local redis_host    = "192.168.1.132"
local redis_port    = 6379
local redis_pwd     = 123456
local redis_db = 2

-- connection timeout for redis in ms.
local redis_connection_timeout = 100

-- a set key for blacklist entries
local redis_key     = "ip_blacklist"

-- cache lookups for this many seconds
local cache_ttl     = 60

-- end configuration

local ip                = ngx.var.remote_addr
local ip_blacklist      = ngx.shared.ip_blacklist
local last_update_time  = ip_blacklist:get("last_update_time");

-- update ip_blacklist from Redis every cache_ttl seconds:
if last_update_time == nil or last_update_time < ( ngx.now() - cache_ttl ) then

  local redis = require "resty.redis";
  local red = redis:new();

  red:set_timeout(redis_connect_timeout);

  local ok, err = red:connect(redis_host, redis_port);
  if not ok then
    ngx.log(ngx.ERR, "Redis connection error while connect: " .. err);
  else
    local ok, err = red:auth(redis_pwd)
    if not ok then
      ngx.log(ngx.ERR, "Redis password error while auth: " .. err);
    else
        local new_ip_blacklist, err = red:smembers(redis_key);
        if err then
            ngx.log(ngx.ERR, "Redis read error while retrieving ip_blacklist: " .. err);
        else
        ngx.log(ngx.ERR, "Get data success:" .. new_ip_blacklist)
          -- replace the locally stored ip_blacklist with the updated values:
            ip_blacklist:flush_all();
          for index, banned_ip in ipairs(new_ip_blacklist) do
            ip_blacklist:set(banned_ip, true);
          end
          -- update time
          ip_blacklist:set("last_update_time", ngx.now());
      end
    end
  end
end

if ip_blacklist:get(ip) then
  ngx.log(ngx.ERR, "Banned IP detected and refused access: " .. ip);
  return ngx.exit(ngx.HTTP_FORBIDDEN);
end

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324730460&siteId=291194637