On Monday, January 4, 2016 at 3:33:33 PM UTC+8, doujiang wrote:
Hello,
在 2016年1月4日 下午2:09,johnny wong
<zhanla...@gmail.com>写道:
如果log_dict:incr是atomic的, 我很好奇它是如何实现的?incr操作会不会因为线程同步而失败(类似java对数据结构的non block操作)?
需要补充一点,shdict 是共享内存词典,每个 nginx worker 都是操作同一个共享内存
我们并不需要主动去做线程/进程同步(nginx 是 每 worker 单进程的)
"每个nginx woker操作的都市同一个共享内存" ? 多个nginx worker同时读写操作同一个共享内存,一定 会造成data race的,怎么不需要做线程或进程同步呢 ? 我对nginx的理解: nginx 有多个worker, 每个worker异步的处理某一批http请求,如同我举的那段代码:
location / {
proxy_pass http://mybackend;
log_by_lua ' local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) local sum = log_dict:get("upstream_time-sum") or 0 sum = sum + upstream_time log_dict:set("upstream_time-sum", sum) local newval, err = log_dict:incr("upstream_time-nb", 1) if not newval and err == "not found" then log_dict:add("upstream_time-nb", 0) log_dict:incr("upstream_time-nb", 1) end ';
}
每个worker处理完某一个http请求后都会到达了nginx 的 log_by_lua阶段,再将此段代码运行一次,而且运行此段代码时应该是block方式的运行,跟从后端upstream proxy读响应结果是不同的(这段是epoll事件模型异步处理的), 所以 log_by_lua这段代码运行如果阻塞了,肯定会阻塞这个worker的。 所以在这段代码内读写共享内存(sharedict的方式), 如果有锁的话,会影响nginx worker的处理效率的。不知道我对此的理解对不对?
我们必须检查incr的返回结果,如果失败就重试,直到成功,这样的话就block了当前这次的http请求处理。
Hello,
在 2016年1月4日 上午10:42,Johnny Wong
<zhanla...@gmail.com>写道:
lua_shared_dict log_dict 5M;
server {
location / {
proxy_pass http://mybackend;
log_by_lua ' local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) local sum = log_dict:get("upstream_time-sum") or 0 sum = sum + upstream_time log_dict:set("upstream_time-sum", sum) local newval, err = log_dict:incr("upstream_time-nb", 1) if not newval and err == "not found" then log_dict:add("upstream_time-nb", 0) log_dict:incr("upstream_time-nb", 1) end ';
}
location = /status {
content_by_lua ' local log_dict = ngx.shared.log_dict local sum = log_dict:get("upstream_time-sum") local nb = log_dict:get("upstream_time-nb") if nb and sum then ngx.say("average upstream response time: ", sum / nb, " (", nb, " reqs)") else ngx.say("no data yet") end ';
}
}
@agentzhang
春哥昵称是:agentzh,你这么写春哥会不开心的哦 :(
拿上边的这个例子说,log_dict:get,log_dict:set 两个操作以我的理解不是线程安全的,
这也就是春哥之前提过的,推荐使用 incr 这个原子操作
当然,如果配合上这个 PR,代码会简洁很多(incr 有了默认值)
因为每次http 请求处理完成后,都会执行这个操作,多并发http请求下,某个请求的处理线程执行log_dict:get/set时必然拿到的是脏数据, 如果这个server的 QPS特别高,拿到脏数据的情况就越大。所以这样的计算结果肯定是不准确的.
更加严谨的说,这个跟你配置的 nginx worker 相关,如果你的 worker 是 1,那么也是没有问题的
因为这个 get / set 之间,只有 worker 之间有竞争
On Saturday, January 2, 2016 at 12:14:12 PM UTC+8, agentzh wrote:
Hello!
2016-01-01 20:06 GMT-08:00 Johnny Wong:
> 是这样子, 比如说要计算10分钟之内的所有请求的平均响应时间, 假设我们使用log_by_lua_file 这样的指令,
> 在这个lua脚本文件里做统计所有请求的处理时间总和,但是每累加一次请求的处理时间,我们必须存储总时间到共享内存里,以便下次请求来的时候取出来后再累加,因为请求是并发过来的,这样每次存取共享内存内的『总时间』就会有race
> condition,一个请求在做存储,另外一个请求在做get, 那么后边这个请求或取到的就有可能是脏数据。
> 从这样的角度看,每个请求对共享内存的存取,就是一个多线程的并发操作,必然要引入『锁』的概念,,另外的方法就是可以将所有get/set请求的做成异步的串行化操作。
>
如果你是说总时间的计算,则你可以使用 incr() 来避免 get() + set() 操作序列可能带来的 data race
问题。如果你说的是计算平均响应时间的两次 get() (一次取总时间,一次取总请求数)的操作序列可能带来的 data
race,则使用我先前说的“事务”或者“pipeline”模式倒是可以解决。即每个请求增加总时间和总次数的两次 incr() 在一个
pipeline 操作里面,而取总时间和总次数的两个 get() 也总是在一个 pipeline 序列里面。这样就不存在失准的问题。
不过值得一提的是,如果采样的时间区间已经很长了,比如 10 分钟,那么这个 10 分钟之类的请求数会大到因为 data race
而产生的计量误差小到可以忽略不计(毕竟你 10 分钟才计算一次平均值),除非对应的流量实在太低了。
Regards,
-agentzh
--