• nginx做白名单和限流


    ​ 在我们生产环境中使用到了地图服务,每个月有免费请求次数,近一个月请求次数突然暴涨,导致直接开启付费模式,一个月上百刀的花销着实难扛,根据实际我们的业务使用情况,远达不到付费标准,故考虑做白名单和限流措施,基于以上情况并遇到春节急需快速处理,所以选择了最简单方便的方式,通过nginx做限流

    ​ 我们都知道nginx里面是可以用lua脚本做一些稍微复杂些的逻辑处理的,要使用lua脚本需要编译lua解释器,时间有限我直接用了openresty,它集成了lua和nginx

    1、openresty是什么?

    OpenResty是一个基于Nginx的高性能Web平台,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。具备下列特点:

    具备Nginx的完整功能
    基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块,允许使用Lua自定义业务逻辑、自定义库

    二、OpenResty的安装

    1、添加OpenResty仓库

    # 由于公共库中找不到openresty,所以需要添加openresty的源仓库
    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    
    # 注意,如果上面命令提示不存在,那就先安装一下
    yum install -y yum-utils
    

    2. 安装OpenResty

    # 安装openresty
    yum install -y openresty
    # 安装OpenResty管理工具,帮助我们安装第三方的Lua模块
    yum install -y openresty-opm
    

    3、目录结构

    ​ 默认安装在/usr/local/openresty

    看到里面有一个nginx目录,进去可以看到跟我们平常用的nginx是一模一样的,OpenResty就是在Nginx基础上集成了一些Lua模块

    到这里我们就安装好了

    7. 启动和运行

    OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,结构与windows中安装的nginx基本一致:

    所以这个里面的nginx和平常的nginx是一样的

    1)nginx配置文件
    worker_processes 1;
    events {
        worker_connections 1024;
    }
    http {
        server{
            listen 999;
            server_name  localhost;
            location /mapbox/ {
                access_by_lua_file "/usr/local/openresty/nginx/lua_script/rule.lua";
                proxy_pass https://api.mapbox.com/;
                proxy_ssl_server_name on;
            }
        }
    }
    
    
    2)lua脚本文件(白名单加限流)

    通过两个redis的key,map_request_limitation:存放令牌数量,map_request_white_list:白名单列表;白名单的IP,无需限流,只有白名单之外的才需要限流

    -- 其实这两个值可以从redis取  甚至可以给每个qrcode设置单独的速率和容积
    -- 但如果想监听桶的状态  需要持续的请求, 只有每次请求后才重新计算并更新桶状态 否则桶状态不变
    local tokens_per_second = 0.2  -- 生成速率 /s
    local max_tokens = 10  -- 最大溶剂
    
    local current_time = ngx.now()
    local path = ngx.var.uri
    local redis_key = "map_request_limitation"
    local redis_key_white_list = "map_request_white_list"
    
    local client_ip = ngx.var.remote_addr
    -- local redis_key = "path:" .. path
    
    -- 连接Redis
    local redis = require "resty.redis"
    local red = redis:new()
    red:set_timeout(1000)
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "Redis连接失败: ", err)
        return ngx.exit(500)
    end
    -- 权限校验
    local res, err = red:auth("123456")
    if not res then
        ngx.say("failed to authenticate: ", err)
        return
    end
    -- 发送 Lua 脚本(保证redis原子性操作)
    local script = [[
    
        local redis_key = KEYS[1]
        local redis_white_list_key = KEYS[2]
        local tokens_per_second = tonumber(ARGV[1])
        local max_tokens = tonumber(ARGV[2])
        local current_time = tonumber(ARGV[3])
        local client_ip = ARGV[4]
        
        -- ip是否存在列表中
        local is_in_whitelist, err = redis.call('sismember', redis_white_list_key, client_ip)
        if is_in_whitelist == 1 then
            return 1
        end
    
        -- 获取上次访问时间和令牌数量
        local res = redis.call('HMGET', redis_key, 'last_access_time', 'tokens')
        local last_access_time
        local last_tokens
        if res[1] and res[2] then
    	last_tokens = res[2]
    	last_access_time =  res[1]
        end
    
        -- 计算时间间隔
        local time_passed = current_time - (tonumber(last_access_time) or 0)
    
        -- 计算新的令牌数量
        last_tokens = last_tokens and tonumber(last_tokens) or max_tokens
        local new_tokens = math.min(max_tokens, last_tokens + time_passed * tokens_per_second)
    
        -- 判断令牌数量是否足够
        if new_tokens >= 1 then
            -- 消耗令牌
            redis.call('HMSET', redis_key, 'last_access_time', current_time, 'tokens', new_tokens - 1)
            return 1
        else
            return 0
        end
    ]]
    
    -- 执行脚本
    local result = red:eval(script, 2, redis_key, redis_key_white_list,tokens_per_second, max_tokens,current_time,client_ip)
    
    if result == 1 then
        -- 成功
        ngx.log(ngx.INFO, "成功")
    else
        -- 令牌不足
        ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
        ngx.say("OVERLOAD!!!!",result)
        return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
    end
    
    -- 返还redis连接到连接池
    local ok, err = red:set_keepalive(10000, 100)
    if not ok then
        ngx.log(ngx.ERR, err)
    end
    

    启动之后当通过这个999端口访问之后,我们在redis里面可以看到以下两个key,白名单可以自行添加,即时生效

  • 相关阅读:
    提高FLASH的擦写次数 “空间换时间”
    Java 18为什么要指定UTF-8为默认字符集
    C++:运算符重载简介
    【一起来学C++】————(11)STL之vector容器容器
    YOLOv5-seg数据集制作、模型训练以及TensorRT部署
    服务器是什么
    win10系统任务栏图标变成白色的解决办法
    c语言经典算法—二分查找,冒泡,选择,插入,归并,快排,堆排
    leetcode-剑指 Offer II 001. 整数除法
    注意力机制 - Bahdanau注意力
  • 原文地址:https://www.cnblogs.com/KingArmy/p/18019489