之前用 ngx_lua(openresty) 写了一个处理 HTTP 请求的程序,今天发现当发送的 HTTP 请求 body 很大的时候,发现老是报错,最后定位到 ngx.req.get_body_data()
这个函数返回 nil ,而不是真正的 body。
于是我去 ngx_lua 的文档看这个函数的文档,发现文档中说有三种情况下,这个函数的返回值会是 nil:
This function returns nil if
- the request body has not been read,
- the request body has been read into disk temporary files,
- or the request body has zero size.
第一条,我是读了文档的,知道要 Nginx 默认是不会读 body 的,要么打开读 body 的开关,要么显示调用一下 read。所以代码中已经调用了 ngx.req.read_body()
了,不会是这个原因。
第三条,也不可能,我通过 curl 发送的,body 肯定是发出去了(可以抓包验证)。
那么基本上就确定是这个 request body 被读到硬盘的临时文件里了。看到这里我猜应该是 Nginx 是将大的 HTTP 请求放到磁盘中而不是放到内存中。搜了一下文档,发现有这个参数:
1 2 3 4 |
Syntax: client_body_buffer_size size; Default: client_body_buffer_size 8k|16k; Context: http, server, location |
当请求体的大小大禹 client_body_buffer_size 的时候,Nginx 将会把它存到一个临时文件中,而不是放到内存中。这个值的大小默认是内存页的两倍:32 位系统上是 8k,64位系统上是16k。
OK,问题找到了,现在解决方案有两个:1)调大这个值,我觉得是不合理的,这样会浪费内存。2)可以想办法读到临时文件中的大 body。ngx_lua 提供了配套的方法 ngx_req.get_body_file,注意这只是获得文件,还要在 lua 代码中打开读取文件。
所以最后,处理一个请求且还能正确处理很大 body 的请求的代码是这样的,其中高亮的部分,是核心的逻辑,先尝试从内存中读 body,如果读不到,就去临时文件中读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
location /request_body { content_by_lua ' function read_from_file(file_name) local f = assert(io.open(file_name, "r")) local string = f:read("*all") f:close() return string end ngx.req.read_body() local body_str = ngx.req.get_body_data() if nil == body_str then local body_file = ngx.req.get_body_file() if body_file then body_str = read_from_file(body_file) end end ngx.say(body_str) '; } |
我读这个实际的应用场景是,读出 body,按照 json 解析出来当做路由规则(需求是需要动态设置 HTTP 的路由规则,所以我用 ngx_lua 在 nginx 上新开了个端口监控这样的规则)。现在用很大的规则一测试,发现性能下降很厉害。存到文件、再读出文件是一方面。另一方面是 ngx_lua 是为每一个 HTTP 请求开一个 lua 协程处理,不能共享变量,只能通过 lua_shared_dict 来保存持久的变量。但是 lua_shared_dict 的问题是,这个 dict 只支持 “Lua booleans, numbers, strings, or nil”,解析出来的 json 是一个 lua 的 table,不能保存到 lua_shared_dict 里面,我只好保存一个字符串,然后对每一个 HTTP 请求 json decode 这个字符串了。不知道有没有更好的方法。
动态设置路由规则是高频的操作吗?为啥会有这个需求呢
动态设置倒不是高频操作,最关键的问题是,不能存下来这个 table,只能保存字符串,对于每一个正常的 HTTP 请求都将 json 字符串解析成 table,这里的开销很大。
原先的需求就是动态设置 Nginx 的 proxy_pass 规则,做灰度引流和蓝绿发布之类的。