La limitation et la rétrogradation
La limitation objectif est d'assurer la stabilité des services de base, couramment utilisés en mode de limitation de courant des services en aval de capacité limitée, mais ont peur d'une augmentation soudaine du trafic (par exemple, les reptiles malveillants, des vacances à haut actionnement, etc.) dues à des services en aval excessive de pression le refus du scénario de service. courant de mode commun limitant le contrôle d'accès concurrentiel et ont contrôlé le taux, est une limite du nombre d'accès simultané, et en même temps à un taux limite.
La limite du débit
A propos de la méthode de déclassement de limiter le jeton seau, seau, compteurs, etc., nous devons comprendre l'algorithme de limitation du courant basé sur Token Bucket
Limitation généralement divisés en restricteur d'écoulement unique distribué et en limitant, si la mise en oeuvre répartie, ce qui limite alors un des services communs nécessaires telles que le stockage d' arrière-
Redis
, dans
nginx en
utilisant le noeud
lua
lu
Redis
informations de configuration
A propos de déclassement
Pression de service poussée lorsque la pression de la situation commerciale actuelle et flux de déclasser la politique, afin de serveurs de liens et pages de certains des services pour faire en sorte que d'effectuer des tâches essentielles. Tout en assurant une même la plupart des tâches que les clients peuvent obtenir le bon conséquence. La demande actuelle est pas traitée ou une erreur, et renvoyé à un défaut.
Description seau jeton
![](https://img-blog.csdnimg.cn/20191214102119612.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5Mzk5OTY2,size_16,color_FFFFFF,t_70)
algorithme de seau à jetons est une capacité de stockage fixe du seau à jetons, ajouté au seau à jetons en fonction d'un taux fixe.
- limite Supposons r / S , il sera exprimé par r un jeton dans le seau, ou tous les 1 / r pour augmenter un second seau de jetons
- Baignoire stocker jusqu'à b jetons, quand le seau est plein, les jetons nouvellement ajoutés sont mis au rebut ou rejetées
- Lorsqu'un n taille de -Byte du paquet de données arrive, retiré du seau n jetons, le paquet est transmis au réseau
- Si le seau de jetons est inférieur à n nombre, le jeton n'a pas été supprimé, et le paquet est réducteur de débit ( soit mis au rebut ou en attente dans la mémoire tampon )
Pour générer des jetons dans le seau de jetons, il y a deux approches:
1
, ouvrir une tâche chronométrée, le jeton est généré en continu par la tâche de synchronisation. Ce problème est la consommation énorme de ressources système, comme une interface de besoins visitaient la limite de fréquence pour chaque utilisateur, il y a hypothèse 6W que le système
utilisateur, vous devez ouvrir à
6W
tâche programmée pour maintenir l' ordre dans chaque seau le nombre de cartes, de sorte que le coût est énorme.
2
, chaque calculé avant d' obtenir des jetons, des idées pour sa mise en œuvre, si l'heure actuelle est postérieure à
nextFreeTicketMicros
, dans la période de temps est calculé combien de jetons peuvent être générés, les jetons générés dans le seau de jetons est ajouté et met à jour les données. Ainsi, une seule fois lors du calcul de l'acquisition du jeton.
Token Bucket algorithme
(Heure actuelle - heure de la dernière visite) / * 1000 génère un nombre de jetons par seconde
Des mesures spécifiques sont les suivantes:
1) Définir le nombre maximum de jetons dans le seau, le nombre de jetons sont générés par seconde grappe Redis, le nombre actuel de jetons restants dans le seau
![](https://img-blog.csdnimg.cn/20191214154510393.png)
2) une couche de distribution disposée
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) script 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) script 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) script 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