场景描述:
用户端发起一次 GET 请求,请求某个静态资源,在 nginx 层需要做如下几件事情:
0. 假设后端系统有 2 套静态文件存储系统,分别为 A 和 B,均基于 HTTP REST 协议
1. 首先发起 GET 子请求到 A,获取静态文件响应体
2. 然后在输出到客户端的同时,同步(或异步)发起一个 POST 请求将静态文件响应体传递到 B
目前尝试了以下两种解决方案:
1. 利用 ngx.location.capture 先发起 GET 请求到 A ,然后将 r1.body 作为参数传递给另外一个子请求的 body 参数,最后发起 POST 子请求到 B,最后输出给客户端(此处均为同步方式)
2. 利用 ngx.tcp.socket 连接 A,发送 GET 请求头,然后在接受到正常响应头后,发起另外一个 cosocket 连接到 B,发送 POST 请求头,接着流式地从 A 获取响应体,并立刻将分块的 chunk 响应体发往 B
方案一,简单的可以用如下代码演示:
local r1 = ngx.location.capture("/A", { method = ngx.HTTP_GET })
if r1.status ~= ngx.HTTP_OK then
return ngx.exit(r1.status)
end
for k, v in pairs(r1.header) do
ngx.header[k] = v
end
local r2 = ngx.location.capture("/B", { method = ngx.HTTP_PUT, body = r1.body})
ngx.print(r1.body)
这个方案的一个明显缺陷是,在获取的静态文件比较大的时候(几十 MB 或几百 MB)nginx 的内存占用太严重了,r1.body 似乎是全缓存在内存中的. 但这个简单的方案的好处是,能够利用 ngx_proxy 和 upstream 模块,对于 A, B 配置多个节点的情况下,能很好地解决负载均衡和健康检查等问题.
方案二,我本地自己封装了一个 httpipe 类似的库,能够从一个 cosocket 流式地传递个另外一个 cosocket,但这个方案的问题,我觉得主要有两点:
1. A, B 服务有多个节点配置的情况下,需要手动自己解决容错和均衡问题,不知关于这点,各位有什么好的解决方案,目前我有尝试过多个 ip 简单后备轮询的方式进行,即当前 ip 出问题,就切换到下一个尝试,特别地,此处用一个 ngx.DICT 共享内存变量保存当前切换到的节点,以便多个 worker 状态一致
2. 其实也是 1 的问题,就是在进行这种全流式的数据传输时,当下游(B 的其中一个节点)网络出现异常波动时,需要切换到另外一个 B 节点进行重传,但因为这里是全流式的,故障前传输的数据并没有 buffer 住,本次请求内无法重试到另外一台去,只能等下次请求切换 B 节点;但这里我也尝试了另外一种补全方案,即利用 ngx.req.init_body(), ngx.req.append_body(), ngx.req.finish_body() 在全流式传输的时候全缓存一份到 nginx 核心的 buffer,在流式传输失败的时候,直接 ngx.exec('/B') 出去(这里,需要另外配置一个 proxy_pass 到 B 的 location ),此时当前客户端的请求会继续维持,其他的请求体会附加到 nginx 核心的 buffer,即整个请求都由 ngx_proxy 接管了,这样一来的好处就是失败传重能在当次请求内完成,而不需要让客户端重试
我的问题:
1. 对于这种大文件传输,从 A 传输到 B,或者接管用户上传文件的请求到某个后端(不走 proxy_pass),采用全流式进行传输在哪些业务场景下比较合适?
2. 大家对于利用 ngx cosocket 进行后端连接的时候,如何很好地解决多个 ip 连接的负载均衡和健康检查(类似 nginx upstream 模块提供的功能)?