针对接口进行总流量限制(熔断) 有些朋友可能会联想到用Nginx自带的limit_req功能来限制访问频率, 这种方案通常是基于IP限制, 是为了防止用户恶意的刷请求量访问.
这里我要介绍的是另一种情况, 接口的qps已经很高, 后端无法接受更多的请求. 这个是时候需要的就是针对整个接口进行限速.
我贴一种比较简单的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 local limit = {}local function safe_incr (dict, key, timeout) local ok local newval, err = dict:incr(key, 1 ) if not newval and err == "not found" then newval = 1 ok, err = dict:safe_add(key, newval, timeout) if not ok then if err == "exists" then newval, err = dict:incr(key, 1 ) elseif err == "no memory" then dict:add(key .. "|no memory" , 0 , timeout) end end end return newval end function limit.limit (dict, key, freq, err_code, err_msg, content_type) dict:set(key .. '|freq' , freq) local time = ngx.time () local k = key .. '|' .. tostring (time ) local newval = safe_incr(dict, k, 70 ) if newval > freq then ngx.sleep(1 ) ngx.status = err_code ngx.header.content_type = content_type or 'application/json' ngx.print (err_msg) ngx.exit (ngx.status ) end end return limit
然后在Nginx的配置中这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lua_shared_dict xxx_0 10M ;server { listen 80 ; server_name aaa.bbb.com; location = /redis/incr { access_by_lua_block { local limit = require("limit" ) limit.limit(ngx.shared.xxx_0, 'PSAvcmVkaXMvaW5jcg==' , 5 , ngx.HTTP_FORBIDDEN, '{"reason": "请求排队中,请稍后重试……", "code": 500}' , 'application/json' ) } proxy_pass http://xxxxx; } }
强制重定向https功能的实现 对于一般的网站, 重定向https一般是通过rewrite来实现的, 比如下面这样的配置:
1 2 3 4 5 6 7 8 9 10 server { listen 80 ; ... return 301 https://$server_name $request_uri ; } server { listen 443 ; ... }
这样的配置只适合放在最外层的Nginx, 假如没有放在用户访问的的入口Nginx,
那么会出现类似下图所示的循环重定向问题.
所以, 出现多层Nginx的情况, 最简单的方案就是在首层Nginx进行https重定向, 内部全部使用http流量.
但是考虑下面一种情况:
而且对于一个PaaS平台来说, 并不是所有的应用都想要强制https, 首层的Nginx也不由我们来控制,
不过它会给我们传递一个这样的header, 表明请求为https的形式:X-Forwarded-Proto: https
,
这种时候比较合适的选择是在平台的这一层Nginx中增加对于请求方法的判断, 根据header中的forwarded-proto
, 有两种方案在中间层解决:
一种使用Nginx的if语句
1 2 3 4 5 6 7 8 9 server{ listen *:80 ; server_name mydomain.com www.mydomain.com; if ($http_x_forwarded_proto = "http" ) { return 301 https://$server_name $request_uri ; } // location xxx }
一种更加优雅的方案(使用Lua):
1 2 3 4 5 6 7 8 9 10 11 12 13 server { listen 80 ; add_header Strict-Transport-Security "max-age=31536000" always; rewrite_by_lua_block { local _request_uri = ngx.var.request_uri local _host = ngx.var.host if ngx.var.http_x_forwarded_proto == nil or ngx.var.http_x_forwarded_proto == 'http' then return ngx.redirect ('https://' .._host.._request_uri, ngx.HTTP_MOVED_PERMANENTLY) end } // location xxx }
由于我们的项目本身已经用了Nginx+Lua, 所以直接采用了第二种方案, 如果你使用的是普通的Nginx, 就用if语句吧.