如果引荐来源网址的点击次数超过阈值,我是否可以使Nginx通知我?
例如,如果我的网站出现在Slashdot上,突然我在一小时内有2K点击,当我希望每小时超过1000 K时,我想得到通知。
Nginx是否可以做到这一点?可能没有lua?(因为我的产品不是lua编译的)
如果引荐来源网址的点击次数超过阈值,我是否可以使Nginx通知我?
例如,如果我的网站出现在Slashdot上,突然我在一小时内有2K点击,当我希望每小时超过1000 K时,我想得到通知。
Nginx是否可以做到这一点?可能没有lua?(因为我的产品不是lua编译的)
Answers:
最有效的解决方案可能是写一个守护进程,将tail -f
在access.log
和跟踪的$http_referer
领域。
但是,一种快速而肮脏的解决方案是添加一个额外的access_log
文件,仅$http_referer
使用custom 记录日志变量log_format
,并每隔X分钟自动旋转日志。
可以在标准logrotate脚本的帮助下完成此操作,该脚本可能需要正常重启nginx才能重新打开文件(例如,标准过程,请在SO上简单查看一下/ a / 15183322-基于脚本)…
或者,通过使用内的变量access_log
,可能$time_iso8601
是借助map
或if
指令来取消详细说明(取决于您要放置的位置access_log
)。
因此,使用上述方法,您可能有6个日志文件,每个日志文件涵盖10分钟的时间http_referer.Txx{0,1,2,3,4,5}x.log
,例如,通过获取分钟的第一位数字来区分每个文件。
现在,您要做的就是拥有一个简单的shell脚本,该脚本可以每10分钟运行一次cat
,将上述所有文件放在一起,通过管道将其传输到sort
,通过管道将其传输uniq -c
到到sort -rn
,再到head -16
,您将列出16种最常见的Referer
变体—可以自由决定数字和字段的任何组合是否超出您的条件,并执行通知。
随后,在一次成功的通知之后,您可以删除所有这6个文件,并且在以后的运行中,除非所有六个文件都存在(和/或您认为合适的其他编号),否则不发出任何通知。
我认为使用logtail和grep会更好。即使可以使用lua内联,您也不希望每个请求都有这种开销,尤其是当您被Slashdot分隔时,您尤其不希望这样做。
这是5秒的版本。将其粘贴在脚本中,并在其周围放置一些更具可读性的文本,您会感到很满意。
5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"
当然,这完全忽略了reddit.com和facebook.com以及所有其他可能为您带来大量流量的其他百万个网站。更不用说100个不同的站点,每个站点都向您发送20个访问者。您可能应该仅具有一个普通的旧流量阈值,该阈值将导致无论您的推荐人如何都向您发送电子邮件。
-o
选项适用于偏移文件,因此它知道下一次从何处开始读取。
nginx的limit_req_zone指令可以基于任何变量的区域,包括$ HTTP_REFERRER。
http {
limit_req_zone $http_referrer zone=one:10m rate=1r/s;
...
server {
...
location /search/ {
limit_req zone=one burst=5;
}
不过,您将还需要做一些事情来限制Web服务器上所需的状态量,因为引荐来源标头可能会很长且变化很大,并且您可能会看到无限个变量。您可以使用nginx split_clients功能为所有请求(基于引荐来源标头的哈希值)设置变量。下面的示例仅使用10块钱,但您可以轻松地使用1000块钱。因此,如果您用斜线分隔,其引荐来源碰巧与斜线URL混入相同存储桶的人也将被阻止,但是您可以通过在split_clients中使用1000个存储桶将访问者限制为0.1%。
看起来像这样(完全未经测试,但方向正确):
http {
split_clients $http_referrer $refhash {
10% x01;
10% x02;
10% x03;
10% x04;
10% x05;
10% x06;
10% x07;
10% x08;
10% x09;
* x10;
}
limit_req_zone $refhash zone=one:10m rate=1r/s;
...
server {
...
location /search/ {
limit_req zone=one burst=5;
}
split_clients
可能是错误的- limit_req
基于“漏斗”,这意味着总体状态永远不应超过指定区域的大小。
是的,NGINX当然可以!
您可以执行以下DFA:
基于实施速率限制,$http_referer
可能通过使用一些正则表达式map
将值标准化。当超出限制时,将引发一个内部错误页面,您可以根据相关问题通过error_page
处理程序进行处理,并转到一个新的内部位置作为内部重定向(客户端不可见)。
在以上超出限制的位置,您将执行警报请求,让外部逻辑执行通知;随后将缓存此请求,以确保您在给定的时间范围内仅收到1个唯一请求。
捕获先前请求的HTTP状态代码(返回状态代码≥300并使用proxy_intercept_errors on
,或者使用not-default-by-default auth_request
或add_after_body
进行“免费”子请求),然后像完成原始请求一样之前的步骤没有涉及。请注意,我们需要启用递归error_page
处理才能起作用。
这是我的PoC和MVP,也位于https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf:
limit_req_zone $http_referer zone=slash:10m rate=1r/m; # XXX: how many req/minute?
server {
listen 2636;
location / {
limit_req zone=slash nodelay;
#limit_req_status 429; #nginx 1.3.15
#error_page 429 = @dot;
error_page 503 = @dot;
proxy_pass http://localhost:2635;
# an outright `return 200` has a higher precedence over the limit
}
recursive_error_pages on;
location @dot {
proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
# if you don't have `resolver`, no URI modification is allowed:
#proxy_pass http://localhost:2637;
proxy_intercept_errors on;
error_page 429 = @slash;
}
location @slash {
# XXX: placeholder for your content:
return 200 "$uri: we're too fast!\n";
}
}
server {
listen 2635;
# XXX: placeholder for your content:
return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
max_size=64m keys_zone=slashdotted:10m;
server {
# we need to flip the 200 status into the one >=300, so that
# we can then catch it through proxy_intercept_errors above
listen 2637;
error_page 429 @/.;
return 429;
location @/. {
proxy_cache slashdotted;
proxy_cache_valid 200 60s; # XXX: how often to get notifications?
proxy_pass http://localhost:2638;
}
}
server {
# IRL this would be an actual script, or
# a proxy_pass redirect to an HTTP to SMS or SMTP gateway
listen 2638;
return 200 authorities_alerted\n;
}
请注意,这按预期工作:
% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%
您可以看到,第一个请求导致一个前端和一个后端命中,这与预期的一样(我必须向具有的位置添加一个虚拟后端limit_req
,因为a return 200
优先于限制,所以不需要真实的后端对于其余的处理)。
第二个请求超出了限制,因此,我们发送警报(获取200
),并将其缓存,然后返回429
(由于上述限制,无法捕获低于300的请求,因此这是必要的),随后被前端捕获,现在可以免费进行任何所需的操作了。
第三个请求仍超出限制,但是我们已经发送了警报,因此,不会发送新的警报。
做完了! 不要忘记在GitHub上进行分叉!
limit_req
,而另一个是a limit_conn
,则只需使用limit_req_status 429
上面的代码(需要非常新的nginx),我认为您应该是黄金的;可能还有其他选择(可以肯定的是将nginx w /链接起来set_real_ip_from
,但是,根据您要执行的操作,可能会有更有效的选择)。
golang
,或者查看上游的超时选项;同样,可能还需要使用proxy_cache_lock on
,并且可能添加一些错误处理,以防止脚本失败时的操作(例如,使用error_page
和proxy_intercept_errors
再次使用)。我相信我的POC是一个好的开始。:)
limit_req
/ limit_conn
怎么样?例如,只需将上面的配置放在当前的前端服务器之前。您可以set_real_ip_from
在上游nginx中使用,以确保IP正确地沿线计算。否则,如果仍然不合适,我想您必须更清楚地阐明确切的限制条件和规格-我们在说什么流量水平?统计数据需要多久运行一次(1min / 5min / 1h)?旧logtail
解决方案有什么问题?