与其他 location 配合

nginx 世界的 location 是异常强大的,毕竟 nginx 的主要应用场景是在负载均衡、API server,在不同 server、location 之间跳转是经常有需要的。

利用不同 location 的功能组合,可以完成内部调用、流水线方式跳转、外部重定向等几大不同方式。

内部调用

例如对数据库、内部公共函数的统一接口,可以把它们放到统一的 location 中。

通常情况下,为了保护这些内部接口,都会把这些接口设置为 internal

这样做最主要的好处是可以让这个内部接口相对独立,不受外界干扰。

nginx.conf 文件示例代码:

worker_processes  1;        #nginx worker 数量
error_log logs/error.log;   #指定错误日志文件路径
events {
    worker_connections 1024;
}

http {
    server {
        #监听端口,若80端口已被占用,则需要修改
        listen 80;
        location /internal/sum {
            internal; # 只允许内部调用

             # 这里做了一个求和运算只是一个例子,可以在这里完成一些数据库、
             # 缓存服务器的操作,达到基础模块和业务逻辑分离目的。
            content_by_lua_block {
                local args = ngx.req.get_uri_args()
                ngx.say(tonumber(args.a) + tonumber(args.b))
            }
        }

        location = / {
            content_by_lua_block {
                local res = ngx.location.capture(
                    "/internal/sum", {args={a=11, b=18}} # 给uri传递对应参数
                )
                ngx.say("status: ", res.status, " result: ", res.body)
            }
        }
    }
}

访问查看结果:

curl http://127.0.0.1 # status: 200 result: 11

并行请求

worker_processes  1;        # nginx worker 数量
error_log logs/error.log;   # 指定错误日志文件路径
events {
    worker_connections 1024;
}

http {
    server {
        listen 80; # 监听端口,若80端口已被占用,则需要修改

        location = /internal/sum { # 求和
            internal;
            content_by_lua_block {
                ngx.sleep(0.1)
                local args = ngx.req.get_uri_args()
                ngx.print(tonumber(args.a) + tonumber(args.b))
            }
        }

        location = /internal/sub { # 求差
            internal;
            content_by_lua_block {
                ngx.sleep(0.1)
                local args = ngx.req.get_uri_args()
                ngx.print(tonumber(args.a) - tonumber(args.b))
            }
        }

        location = /parallels {
            content_by_lua_block {
                local start_time = ngx.now()
                local res1, res2 = ngx.location.capture_multi({
                    {"/internal/sum", {args={a=11, b=18}}},
                    {"/internal/sub", {args={a=11, b=18}}}
                })
                ngx.say("status: ", res1.status, " result: ", res1.body)
                ngx.say("status: ", res2.status, " result: ", res2.body)
                ngx.say("time used: ", ngx.now() - start_time)
            }
        }

        location = /queue {
            content_by_lua_block {
                local start_time = ngx.now()
                local res1 = ngx.location.capture_multi( {
                        {"/internal/sum", {args={a=11, b=18}}}
                    })
                local res2 = ngx.location.capture_multi( {
                        {"/internal/sub", {args={a=11, b=18}}}
                    })
                ngx.say("status: ", res1.status, " result: ", res1.body)
                ngx.say("status: ", res2.status, " result: ", res2.body)
                ngx.say("time used: ", ngx.now() - start_time)
            }
        }
    }
}

测试结果:

# 并行请求时间 `curl http://127.0.0.1/parallels`
status: 200 result: 29
status: 200 result: -7
time used: 0.099999904632568

# 队列请求时间 `curl http://127.0.0.1/queue`
status: 200 result: 29
status: 200 result: -7
time used: 0.20099997520447

利用 ngx.location.capture_multi 函数,直接完成了两个子请求并行执行。 当两个请求没有相互依赖,这种方法可以极大提高查询效率。 两个无依赖请求,各自是 100ms,顺序执行需要 200ms,但通过并行执行可以在 100ms 完成两个请求。 实际生产中查询时间可能没这么规整,但思想大同小异,这个特性是很有用的。

流水线方式跳转

现在的网络请求,已经变得越来越拥挤。各种不同 API 、下载请求混杂在一起,就要求不同厂商对下载的动态调整有各种不同的定制策略,而这些策略在一天的不同时间段,规则可能还不一样。这时候还可以效仿工厂的流水线模式,逐层过滤、处理。

示例代码:

location ~ ^/static/([-_a-zA-Z0-9/]+).jpg {
    set $image_name $1;
    content_by_lua_block {
        ngx.exec("/download_internal/images/"
                .. ngx.var.image_name .. ".jpg");
    };
}

location /download_internal {
    internal;
    # 这里还可以有其他统一的 download 下载设置,例如限速等
    alias /some/other/path/download;
}

ngx.exec 方法与 ngx.redirect 是完全不同的,前者是个纯粹的内部跳转并且没有引入任何额外 HTTP 信号。

这里的两个 location 更像是流水线上工人之间的协作关系。第一环节的工人对完成自己处理部分后,直接交给第二环节处理人(实际上可以有更多环节),它们之间的数据流是定向的。

外部重定向

使用 rewrite_by_lua_block 指令可以执行外部重定向。

worker_processes  1;        #nginx worker 数量
error_log logs/error.log;   #指定错误日志文件路径
events {
    worker_connections 1024;
}

http {
    server {
        #监听端口,若80端口已被占用,则需要修改
        listen 80;
        location = /another {
            content_by_lua_block {
                ngx.say([[I am another uri]])
            }
        }

        location = / {
            rewrite_by_lua_block {
                return ngx.redirect('/another');
            }
        }
    }
}

执行测试,结果如下:

curl -i http://127.0.0.1 # 重定向到了 /another
HTTP/1.1 302 Moved Temporarily
Server: openresty/1.21.4.1
Date: Tue, 31 May 2022 10:29:46 GMT
Content-Type: text/html
Content-Length: 151
Connection: keep-alive
Location: /another

<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>openresty/1.21.4.1</center>
</body>
</html>
curl -i http://127.0.0.1/another # 正常响应
HTTP/1.1 200 OK
Server: openresty/1.21.4.1
Date: Tue, 31 May 2022 10:29:54 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

I am another uri

当使用浏览器访问页面 http://127.0.0.1 就可以发现浏览器会自动跳转到 http://127.0.0.1/another

另外外部重定向是可以跨域名的。

例如从 A 网站跳转到 B 网站是绝对允许的。

在 CDN 场景的大量下载应用中,一般分为调度、存储两个重要环节。

调度就是通过根据请求方 IP 、下载文件等信息寻找最近、最快节点,应答跳转给请求方完成下载。g