zoukankan      html  css  js  c++  java
  • openresty IP限流

    1、针对大流量大并发网络请求下,为了保证服务的正常运行,不得不针对性采取限流的方式来解决大流量带来的服务器的压力。

    2、在目前项目中对于接入了不同的平台,所以需要针对具体的平台做相对应的限流,或者针对所有的平台做ip白名单的限制,针对ip限流。

    3、以下代码是通过平台上报的ip对平台做相对应的限流,主要使用的是redis+openresty来做处理;涉及代码只做过基本的压测,未投入实际生产

    相关代码记录如下:

      1 --
      2 -- Created by IntelliJ IDEA.
      3 -- User: tiemeng
      4 -- Date: 2019/3/3
      5 -- Time: 10:00
      6 -- To change this template use File | Settings | File Templates.
      7 --
      8 ngx.header.content_type = "text/html;charset=utf8"
      9 
     10 
     11 -- redis配置
     12 local redisConfig = {
     13     redis_a = {
     14         host = '127.0.0.1',
     15         port = 6379,
     16         pass = '',
     17         timeout = 200,
     18         database = 0,
     19     }
     20 }
     21 
     22 local limitCount = 5
     23 
     24 local time = 10000 -- 时间,单位为毫秒
     25 
     26 --[[
     27       获取请求IP
     28  ]]
     29 local function getIp()
     30     local headers = ngx.req.get_headers()
     31     local ip = headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
     32     return ip
     33 end
     34 
     35 
     36 
     37 --[[
     38         连接redis
     39  ]]
     40 local function redisConn()
     41     local redis = require('resty.redis_factory')(redisConfig)
     42     local ok, redis_a = redis:spawn('redis_a')
     43     if ok ~= nil then
     44         return redis, redis_a
     45     end
     46 
     47     return redis, nil
     48 end
     49 
     50 
     51 --[[
     52     通过ip获取平台名称
     53  ]]
     54 local function getPlatformNameByIp(ip)
     55     local handle, redis = redisConn()
     56     if redis == nil then
     57         return nil
     58     end
     59     local platform = redis:hget('iplist', ip)
     60     handle.destruct()
     61     if platform ~= ngx.null then
     62         return platform
     63     end
     64     ngx.log(ngx.ERR, "ip:" .. ip .. ",未在白名单中,禁止访问")
     65     return nil
     66 end
     67 
     68 local function forbid2()
     69     local ip = getIp();
     70     -- 2、获取当前ip是那个平台
     71     local platfromName = getPlatformNameByIp(ip)
     72     if platfromName == nil then
     73         return false
     74     end
     75     -- 3、获取当前平台的总数
     76     local key = 'forbid_' .. platfromName
     77     local handle, redis = redisConn()
     78     if redis == nil then
     79         return nil
     80     end
     81     local curTime = ngx.now() * 1000
     82     local ok, err = redis:eval([[
     83         local len = redis.call('llen',KEYS[1])
     84         if len < 10 then
     85             redis.call('rpush',KEYS[1],ARGV[2])
     86             return true
     87         end
     88         local times = redis.call('lrange',KEYS[1],0,0)
     89         local timeSum = tonumber(times[1])+tonumber(ARGV[1])
     90         if timeSum > tonumber(ARGV[2]) then
     91             return false
     92         end
     93         redis.call('lpop',KEYS[1])
     94         redis.call('rpush',KEYS[1],ARGV[2])
     95         return true
     96     ]], 1, key, time, curTime)
     97     handle.destruct()
     98     return ok
     99 end
    100 
    101 
    102 
    103 if forbid2() ~= 1 then
    104     ngx.exit(403)
    105 end

    测试中出现的问题:

    起初是使用以下代码实现的,从代码表面看是没有任何问题,但是在压力测试下并发数达到50的时候就会出现限流失效;出现失效的主要原因是,在redis中list的操作并不是所谓的原子操作,所以通过翻阅相关资料了解到,可以在redis中嵌入相关的lua脚本,可以达到原子的操作;所以在一开始的代码82-96行使用redis的eval函数来调用lua的脚本,已达到原子操作的要求;修改后经过压测后达到相对用的效果

     1 local function isForbid()
     2     local ip = getIp();
     3     -- 2、获取当前ip是那个平台
     4     local platfromName = getPlatformNameByIp(ip)
     5     if platfromName == nil then
     6         return false
     7     end
     8     -- 3、获取当前平台的总数
     9     local key = 'forbid_' .. platfromName
    10     local handle, redis = redisConn()
    11     if redis == nil then
    12         return nil
    13     end
    14     -- 4、校验是否超过限制
    15     local len = redis:llen(key)
    16 
    17     if len < limitCount then
    18         redis:rpush(key, ngx.now() * 1000)
    19         handle.destruct()
    20         return true
    21     end
    22     local times = redis:lrange(key, 0, 0)
    23     if times == ngx.null then
    24         return false
    25     end
    26 
    27     if tonumber(times[1]) + time >= ngx.now() * 1000 then
    28         ngx.log(ngx.ERR, "forbid_platform :" .. platfromName)
    29         return false
    30     end
    31     os.execute("sleep " .. 1)
    32     redis:lpop(key)
    33     redis:rpush(key, ngx.now() * 1000)
    34     handle.destruct()
    35     return true
    36 end

    nginx部分相关配置:

    http {
        include       mime.types;
        default_type  application/octet-stream;
        lua_package_path '/websys/nginx/lua/?.lua;/websys/lualib/?/init.lua;;';
    
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
        #access_log  logs/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        #keepalive_timeout  65;
        #    resolver 127.0.0.1 192.168.1.1 8.8.8.8;
        #gzip  on;
        access_by_lua_file lua/access.lua;

    此限流主要在openresty的access层做了限制,主要引入方式为上方红色字体

    起初想的是通过redis的incr来实现针对ip做限流,但是其中会有键失效的时间问题;如果使用incr做相对应的操作,如果10秒钟请求量为50的话,是没法保证时间的连续性;所以最后采用了通过list来保证了时间的连续性;

    本文主要记录相关的问题及知识点,如简述和实现方式有问题欢迎吐槽

  • 相关阅读:
    Android学习之天气预报简单版
    Android学习之Json数据的获取与解析
    getPath()返回路径包含的“%20”(空格)的处理
    自学php 之数据库连接编程
    bnuoj 1071 拼图++
    hdu 2489 Minimal Ratio Tree
    hdu 4720 Naive and Silly Muggles
    hdu 4725 The Shortest Path in Nya Graph
    经典算法学习:排序之希尔排序(壳排序)
    经典算法学习:排序之插入排序
  • 原文地址:https://www.cnblogs.com/tm2015/p/10487667.html
Copyright © 2011-2022 走看看