有没有一种方法可以让Nginx通知我,如果来自推荐人的命中超出门槛?
例如,如果我的网站在Slashdot中突出显示,并且突然间我在一个小时内有2K次点击,我想在超过1K小时的时间内收到通知。
是否有可能在Nginx中做到这一点? 可能没有卢阿? (因为我的产品不是lua编译的)
最有效的解决scheme可能是编写一个守护程序,该守护程序将访问access.log ,并跟踪$http_referer字段。
然而,一个快速和肮脏的解决scheme是添加一个额外的access_log文件,只logging$http_referervariables与自定义log_format ,并自动旋转日志每X分钟。
这可以在标准的logrotate脚本的帮助下完成,这可能需要重新启动nginx,才能重新打开文件(比如,标准的程序,基于脚本)…
或者,通过使用access_logvariables,可能通过map或if指令(取决于您想要放置access_log )的帮助,从$time_iso8601获取分钟规范。
所以,有了上面的内容,你可能会有6个日志文件,每个文件包括一个10分钟的时间, http_referer.Txx{0,1,2,3,4,5}x.log ,例如通过获取第一个数字分钟来区分每个文件。
现在,你所要做的只是一个简单的shell脚本,每10分钟运行一次,把所有上面的文件集合在一起,用pipe道sort ,pipe道uniq -c , sort -rn , head -16 ,并且您有一个最常见的Referer变体的列表 – 可以自由决定数字和字段的任何组合是否超出您的标准,并执行通知。
随后,在一次成功通知后,您可以删除所有这6个文件,并在随后的运行中不发出任何通知,除非所有六个文件都存在(和/或您认为合适的其他数字)。
我认为用logtail和grep可以做得更好。 即使可以用lua进行内联,你也不需要为每个请求开销, 特别是当你被Slashdotted的时候你不需要这个开销。
这是一个5秒的版本。 把它放在一个脚本中,在它周围放一些可读的文本,你就是金。
5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"
当然,这完全忽略了reddit.com和facebook.com以及其他所有可能会给你带来很多stream量的网站。 更不用说100个不同的网站,每个网站给你20个访问者。 您应该只是有一个简单的旧stream量阈值,导致电子邮件发送给您,无论引用。
nginx的limit_req_zone指令可以将它的区域放在任何variables上,包括$ 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_clientsfunction为所有基于引用标头的散列的请求设置一个variables。 下面的示例只使用了10个buckes,但是您可以轻松地使用1000个。 因此,如果您的slashdotted,引荐者恰好与slashdoturl散列到同一个存储分区的用户也会被阻止,但是您可以通过在split_clients中使用1000个存储区来将其限制为0.1%的访问者。
它看起来像这样(完全未经testing,但方向正确):
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; }
是的,NGINX当然是可以的!
您可以做的是实现以下DFA :
实现基于$http_referer速率限制,可能通过一个map使用一些正则expression式来规范化值。 当超出限制时,会引发一个内部错误页面,您可以通过error_page处理程序捕获错误页面,并将其作为内部redirect(客户端不可见)。
在超出限制的上述位置,执行警报请求,让外部逻辑执行通知; 这个请求随后被caching,确保你每个给定的时间窗口只能得到一个唯一的请求。
捕获先前请求的HTTP状态码(通过返回状态码≥300并使用proxy_intercept_errors on ,或者使用非默认的auth_request或add_after_body来创build“空闲”子请求),然后完成原来的要求就好像以前的步骤没有涉及。 请注意,我们需要启用recursionerror_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的位置,因为return 200将优先于极限,真正的后端isn其余的处理都不需要)。
第二个请求超出限制,所以我们发送警报(获得200 ),然后caching它,返回429 (由于前面提到的300以下的请求不能被捕获的限制,这是必须的)结束,现在免费做任何事情都是免费的。
第三个请求仍然超出限制,但是我们已经发送了警报,所以没有发送新的警报。
完成! 别忘了把它放在GitHub上!