location / {
curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit' https://google-analytics.com/collect
}
location / {
curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit' https://google-analytics.com/collect
}
ngx_http_lua_module
可以实现这一功能!location / {
access_by_lua_block {
os.execute("/usr/bin/curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit' https://google-analytics.com/collect >/dev/null 2>/dev/null")
}
}
请注意,执行curl命令会阻塞页面加载,直到curl命令执行完成。如果想要在后台运行curl命令并立即继续页面加载,请在命令结尾添加一个空格和一个&
符号,使其看起来像这样:
>/dev/null 2>/dev/null &")
local headers, err = ngx.resp.get_headers();
获取请求头的数组(由浏览器发送),并使用 local headers, err = ngx.req.get_headers()
获取响应头的数组(由nginx发送)- 但是你应该使用 log_by_lua_block
而不是 access_by_lua_block
。 - hanshenrikcurl
实例,以便于Google Analytics操作——是解决问题的一种错误方式:
Nginx本身可以轻松地在任何给定时间服务于10k+并发连接,作为下限,也就是说,如果您做得正确,请参见https://en.wikipedia.org/wiki/C10k_problem。
另一方面,fork
的性能,即创建新进程的底层系统调用,如果您想为每个请求运行curl
,则非常慢,上限为每秒1k个分支,例如,如果您做得正确,那就是它最快的速度,请参见Faster forking of large processes on Linux?。
我的建议是通过批处理来执行此操作。实时进行Google Analytics并没有什么优势,统计数据的5分钟延迟应该足够了。您可以使用编程语言编写一个简单的脚本,查找相关的http://nginx.org/r/access_log,收集所需时间段的数据,并向Google Analytics发出单个批量请求(和/或从单个进程内发出多个单独的请求)以提供有关过去5分钟内每个访问者的必要信息。您可以将其作为守护进程或从cron
作业中运行脚本,请参见crontab(5)
和crontab(1)
。
或者,如果您仍然希望进行Google Analytics的实时处理(我不建议这样做,因为大多数这些服务本身都是基于“最终一致性”实现的,这意味着GA本身不会保证最后XX秒/分钟/小时等的准确实时统计数据),则您可能需要实现某种形式的守护进程来处理实时统计:
我的建议仍然是在这种守护进程中利用access_log
,例如,通过您喜欢的编程语言中的tail -f /var/www/logs/access_log
等效方法,其中您将打开access_log
文件作为流,并在数据到来时处理它。
或者,您可以实现此守护进程具有自己的HTTP请求接口,并将每个传入的请求复制到您的实际后端以及此额外服务器。您可以通过nginx的帮助进行多路复用,使用默认未构建的auth_request
或add_after_body
来为每个请求创建一个“免费”子请求。该子请求将发送到您的服务器,例如,使用Go编写。该服务器将具有至少两个goroutines:一个将将传入的请求处理为队列(通过缓冲字符串通道实现),立即向客户端发出回复,以确保不会延迟nginx上游;另一个将通过第一个chan string
从第一个接收请求,随着其进行处理并向Google Analytics发送适当的请求。
无论你选择哪种方式,你可能仍然想要实现一定程度的批处理和/或限流,因为我想在没有任何批处理实现的情况下,如果你从相同的IP地址过于频繁地向Google Analytics发送请求,它本身很可能会进行限流。根据Google Analytics测量协议API的直接使用速率限制是多少?和https://developers.google.com/analytics/devguides/collection/protocol/v1/limits-quotas,大多数库似乎实现了内部限制,以确定它们每秒钟将发送多少个请求到Google。
</body>
标签之前嵌入GA代码。sub_filter_once on;
sub_filter '</body>' "<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-X', 'auto');
ga('send', 'pageview');
</script></body>";
location / {
}
这个Nginx模块被称为sub
。
text/html
MIME 类型,也可以允许其他类型。 - Alexander Azarov# pick your location
location /example {
# invite lua to the party
access_by_lua_block {
# set request parameters
local request = {
v = 1,
t = "pageview",
# don' forget to put your own property here
tid = "UA-XXXXXXX-Y",
# this is a "unique" user id based on a hash of ip and user agent, not too reliable but possibly best that one can reasonably do without cookies
cid = ngx.md5(ngx.var.remote_addr .. ngx.var.http_user_agent),
uip = ngx.var.remote_addr,
dp = ngx.var.request_uri,
dr = ngx.var.http_referer,
ua = ngx.var.http_user_agent,
# here you truncate the language string to make it compatible with the javascript format - you'll want either the first two characters like here (e.g. en) or the first five (e.g en_US) with ...1, 5
ul = string.sub(ngx.var.http_accept_language, 1, 2)
}
# use the location.capture thingy to send everything to a proxy
local res = ngx.location.capture( "/gamp", {
method = ngx.HTTP_POST,
body = ngx.encode_args(request)
})
}
}
# make a separate location block to proxy the request away
location = /gamp {
internal;
expires epoch;
access_log off;
proxy_pass_request_headers off;
proxy_pass_request_body on;
proxy_pass https://google-analytics.com/collect;
}