トークンバケットアルゴリズムの限定的な例

制限と降格
 
目的を限定することは一般的に、電流制限モード限られた容量下流サービスで使用されるコアサービスの安定性を確保することであるが、トラフィックの突然の急増(例えば、悪意のある爬虫類、高作動休日、等)を恐れている過剰圧力下流サービスに起因しましたサービスシナリオの拒否。コモンモード電流同時実行制御を制限し、速度が制御されているが、レート制限への同時、同時アクセス数の制限です。
 
流れの制限
 
トークンバケット、バケット、カウンターなどを制限するダウングレードする方法については、我々は、トークンバケットに基づいて電流制限アルゴリズムを理解する必要があります
 
実装は、分散場合、一般的に、分散単一流量制限に分け、限定制限、そのようなバックエンドストレージに必要な共通サービス制限 Redisのを に、 nginxの ノードを使用して Luaの リード Redisの 構成情報を
 
ダウングレードについて
 
現在のビジネス状況や流れの圧力がコアのタスクを実行することを保証するために、リンクサーバーとサービスのいくつかのページへ順に、ポリシーをダウングレードするサービス圧力サージ。タスクのいくつかさえほとんどを確保しながら、顧客はそれに応じて、正しいを得ることができること。現在のリクエストが処理されるか、またはエラー、およびデフォルトに戻されません。
 
説明トークンバケット
 
 
トークンバケットアルゴリズムは、固定されたレートに従ってトークンバケットに加え、トークンバケットの固定記憶容量です。
 
  • 仮定するリミットR / Sは、それが当たり発現されるR バケット内のトークン、またはすべての1 / rが2番めのトークンバケットを増加させます
  • まで保存浴槽Bのバケツがいっぱいになったとき、トークン、新しく追加されたトークンは廃棄されるか拒否されます
  • 場合、n個のデータ・パケットの-byteサイズが到着し、バケットから削除N トークン、その後、パケットがネットワークに送信されます。
  • トークンバケット未満であれば、N の数、トークンが削除されず、パケットは、フローリストリクタであるいずれかの廃棄又はバッファ内で待機しています
トークンバケットにトークンを生成するための2つの方法があります。
 
図1は 、トークンは、タイミングタスクによって連続的に生成され、タイミングタスクを開きます。この問題は、このようなニーズは、各ユーザの周波数限界を訪れていたインタフェースなどのシステムリソースの巨大な消費である、システムのこと6Wの仮定があり 、ユーザは、あなたが開く必要が 6Wは、 各バケットに秩序を維持するためのタスクを時限カードの数は、そのコストは莫大です。
 
図2は、 現在時刻がより後であれば、それぞれのトークンを取得する前に計算され、その実装のためのアイデアは、 nextFreeTicketMicros 、期間を生成することができるどのように多くのトークン算出される内に、トークンバケットに生成されたトークンを添加し、データを更新します。したがって、唯一のトークンの取得を計算するときに一度。
 
トークンバケットアルゴリズム
(現在時刻 - 最後の訪問の時)/ * 1000は、毎秒トークン番号を生成し、
 
次のように具体的な手順は次のとおりです。
 
1)バケット内のトークンの最大数を設定し、トークンの数は、第二のRedisのクラスタごとに生成され、バケット内の残りのトークンの現在の数

2)分配層が配置され

user  root;
worker_processes  2;
daemon off;#避免nginx在后台运行
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
   worker_connections  20480;#单个进程允许的客户端最大连接数
}


http {
    include       mime.types;
    #default_type  application/octet-stream;
    lua_code_cache off; #关闭代码缓存上线后去掉
    #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;
    lua_shared_dict load 20k;
    lua_shared_dict redis_cluster_slot_locks 100k;
    lua_shared_dict redis_cluster_addr 20k;
    lua_shared_dict my_upstream_dict 1m;
    lua_package_path "/usr/local/openresty/lualib/project/common/lualib/?.lua;;/usr/local/openresty/lualib/project/common/resty-redis-cluster/lib/?.lua;;";
    lua_package_cpath "/usr/local/openresty/lualib/project/common/resty-redis-cluster/src/?.so;;";
    init_worker_by_lua_file /usr/local/openresty/lualib/project/init.lua;
    access_by_lua_file /usr/local/openresty/lualib/project/access.lua;
    gzip  on;
	#配置上游应用层服务器
	#动态均衡负载 hash
    upstream swoole_server_hash{
        hash $key;
        server 114.67.105.89:8002;
		upsync 114.67.105.89:8700/v1/kv/upstreams/servers upsync_timeout=20s upsync_interval=500ms upsync_type=consul strong_dependency=on;
		upsync_dump_path /usr/local/openresty/nginx/conf/servers.conf; #生成配置文件
		include /usr/local/openresty/nginx/conf/servers.conf;
    }
    #最少连接数
    upstream swoole_server_conn{
        least_conn;
        server 114.67.105.89:8002;
        upsync 114.67.105.89:8700/v1/kv/upstreams/servers upsync_timeout=20s upsync_interval=500ms upsync_type=consul strong_dependency=on;
        upsync_dump_path /usr/local/openresty/nginx/conf/servers.conf; #生成配置文件
        include /usr/local/openresty/nginx/conf/servers.conf;
    }
    #轮询
    upstream swoole_server_round{
        server 114.67.105.89:8002;
        upsync 114.67.105.89:8700/v1/kv/upstreams/servers upsync_timeout=20s upsync_interval=500ms upsync_type=consul strong_dependency=on;
        upsync_dump_path /usr/local/openresty/nginx/conf/servers.conf; #生成配置文件
        include /usr/local/openresty/nginx/conf/servers.conf;
    }
	server {
        listen       80;
		#路由匹配规则为 jd.com/546546.html
        if ( $request_uri ~* \/(\d+).html$) {
            set $key $1;
        }
        location /{
            set_by_lua_file $swoole_server /usr/local/openresty/lualib/project/set.lua
			proxy_set_header Host $host; 
			proxy_set_header X-Real-IP $remote_addr; 
			proxy_set_header REMOTE-HOST $remote_addr; 
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
			client_max_body_size 50m; 
			client_body_buffer_size 256k; 
			proxy_connect_timeout 30; 
			proxy_send_timeout 30; 
			proxy_read_timeout 60; 
			proxy_buffer_size 256k; 
			proxy_buffers 4 256k; 
			proxy_busy_buffers_size 256k; 
			proxy_temp_file_write_size 256k; 
			proxy_max_temp_file_size 128m; 
			proxy_pass http://$swoole_server;		
         }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        location ~ \.php/?.*   {
            root           /var/www/html;#php-fpm容器中的路径,不是nginx路径
            fastcgi_pass   114.67.105.89:9002;#对应容器的端口
            fastcgi_index  index.php;
            #为php-fpm指定的根目录
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name; #加了这一项
            #定义变量$path_info,存放pathinfo信息
            set $path_info "";
            if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
                #将文件地址赋值给变量 $real_script_name
                set $real_script_name $1;
                #将文件地址后的参数赋值给变量 $path_info
                set $path_info $2;
            }
            #配置fastcgi的一些参数
            fastcgi_param SCRIPT_NAME $real_script_name;
            fastcgi_param PATH_INFO $path_info;
            include       /usr/local/openresty/nginx/conf/fastcgi_params;
       }
    }
}

 3)スクリプトinit.lua

    --启动器当中获取redis集群表
    local delay = 5
    local handler
    handler = function (premature)
        local resty_consul = require('resty.consul')
        local consul = resty_consul:new({
              host            = "114.67.105.89",
              port            = 8700,
              connect_timeout = (60*1000), -- 60s
              read_timeout    = (60*1000), -- 60s
          })
          --切换轮询等
        local res, err = consul:get_key("load") -- Get all keys
        if not res then
          ngx.log(ngx.ERR, err)
          return
        end
        ngx.log(ngx.ERR,"获取到是否要切换的标记",res.body[1].Value)
        ngx.shared.load:set("load",res.body[1].Value)
        local res, err = consul:list_keys("redis-cluster") -- Get all keys
        if not res then
            ngx.log(ngx.ERR, err)
            return
        end
        --获取集群
        local keys = {}
        if res.status == 200 then
            keys = res.body
        end
        local ip_addr = ''
        for key,value in ipairs(keys) do
            local res, err = consul:get_key(value)
            if not res then
                ngx.log(ngx.ERR, err)
                return
            end
            if table.getn(keys) == key then
                ip_addr = ip_addr..res.body[1].Value
            else
                ip_addr = ip_addr..res.body[1].Value..","
            end 
            ngx.shared.redis_cluster_addr:set("redis_addr",ip_addr)
        end
    end
   if( 0 == ngx.worker.id() ) then
        --第一次立即执行 ngx.timer.at:只执行一次
         local ok, err = ngx.timer.at(0, handler)
         if not ok then
           ngx.log(ngx.ERR, "第一次执行错误: ", err)
           return
         end
        --第二次定时执行
        local ok, err = ngx.timer.every(delay, handler)
        if not ok then
          ngx.log(ngx.ERR, "定时执行错误: ", err)
          return
        end
        ngx.log(ngx.ERR,"-----进程启动")
    end

4)set.luaスクリプト

local flag = ngx.shared.load:get("load")
local load_blance = ''
if tonumber(flag) == 1 then
    load_blance = "swoole_server_round"
elseif tonumber(flag) == 1 then
    load_blance = "swoole_server_conn"
else
    load_blance = "swoole_server_hash"
end
ngx.log(ngx.ERR,load_blance)
return load_blance

5)スクリプトaccess.lua

ngx.header.content_type = "text/html;charset=utf-8"
local ngx_re_split = require("ngx.re").split
local redis_cluster = require "rediscluster"
local redis_list = {}
local ip_addr = ngx.shared.redis_cluster_addr:get("redis_addr")
local ip_addr_table = ngx_re_split(ip_addr,",")
for key,value in ipairs(ip_addr_table) do
    local ip_addr = ngx_re_split(value,":")
    redis_list[key] = {ip=ip_addr[1],port=ip_addr[2]}
end
local config = {
    name = "testCluster",                   --rediscluster name
    serv_list = redis_list,
    keepalive_timeout = 60000,              --redis connection pool idle timeout
    keepalive_cons = 1000,                  --redis connection pool size
    connection_timout = 1000,               --timeout while connecting
    max_redirection = 5,                    --maximum retry attempts for redirection,
    auth = "binleen"                           --set password while setting auth
}
local red_c = redis_cluster:new(config)
ngx.update_time() --更新系统时间
local key = "{api_1_2000}"
--ngx.say(string.format("%.3f",ngx.now())*1000)
 --在redis嵌入lua脚本
local res, err = red_c:eval([[
    -- 通过url判断访问的是哪个服务
    local app_name = KEYS[1]                                         -- 标识是哪个应用
    local rareLimit = redis.call('HMGET',app_name,'max_burst','rate','curr_permits','last_second')
    local max_burst = tonumber(rareLimit[1])          -- 令牌桶存放的最大的容量(需要自己在redis集群里设置)
    local rate = tonumber(rareLimit[2])               --每秒生成令牌的个数(速率)
    local curr_permits = tonumber(rareLimit[3])       --当前令牌桶里剩余令牌个数(跟1S内的消耗有关系,)
    local last_second = tonumber(rareLimit[4])        --最后一次访问的时间
    local curr_second =  ARGV[1]                      --当前访问的时间
    local permits = ARGV[2]                           --当前这次请求消耗的令牌数
    local default_curr_permits = max_burst            --默认令牌数,默认添加10个
    --通过判断是否有最后一次的访问时间,如果满足条件,证明不是第一次获取令牌
    if (type(last_second)) ~= "boolean" and last_second ~= nil then
         --距离上次访问,按照速率大概产生多少个令牌
         local reverse_permits = math.floor((curr_second - last_second)/1000*rate)
         --如果访问时间较短,允许突发的数量
         local expect_curr_permits = reverse_permits + curr_permits
         --不能超过最大的令牌数,最终能使用的令牌数
         default_curr_permits = math.min(expect_curr_permits,max_burst)
    else
       --记录下访问时间 最后一次访问的时间为当前访问的时间 剩余令牌数=默认令牌数量-本次消耗令牌数
       local res,err = redis.call("HMSET",app_name,"last_second",curr_second,"curr_permits",default_curr_permits - permits)
       if res == "ok" then
            return 1
       end
    end
    --当前可使用的令牌 - 请求消耗的令牌 > 0 ,就表示能成功获取令牌
    if (default_curr_permits - permits) >=0 then
        --记录下访问时间 最后一次访问的时间为当前访问的时间 剩余令牌数=默认令牌数量-本次消耗令牌数
        redis.call("HMSET",app_name,"last_second",curr_second,"curr_permits",default_curr_permits - permits)
        return 1
    else
        --如果小于0,证明令牌不够
         redis.call("HMSET",app_name,"curr_permits",default_curr_permits)
         return 0
    end
]],1,key,(string.format("%.3f",ngx.now())*1000),1)
--集群 设置一下key,假设api有编号
if tonumber(res) == 1 then
     ngx.say("有可用令牌")
else
    ngx.say("无可用令牌")
end

 

公開された72元の記事 ウォン称賛7 ビュー10000 +

おすすめ

転載: blog.csdn.net/qq_39399966/article/details/103536497