1. 程式人生 > >使用OpenResty控制CDN回源主機

使用OpenResty控制CDN回源主機

http://scyuan.info/2016/03/07/openresty-cdn-original.html

年前粗略看了一下《OpenResty最佳實踐》,感覺OpenResty是個好東西呀,但是一下子又找不到使用場景,所以就放到一邊了。最近遇到一個需求,感覺用OpenResty正合適,所以終於在生產環境實踐了一把。

需求

一個JavaScript指令碼分發服務:


        key
瀏覽器 --------------> 分發服務
GET /js?key=xxxx

        302 CDN地址
瀏覽器 <-------------- 分發服務
Location: cdn.x.com/digest/xxxx.js

//分發服務根據key獲取使用者的配置(使用者可以通過web介面修改,需要儘快生效)
//以及配置對應的靜態js檔案(分佈在分發服務的本地硬碟),
//計算配置和靜態內容的摘要,拼接到CDN的URL中,
//當配置或靜態內容更新後,重定向新的URL,CDN將觸發回源流程。


        CDN地址              回源
瀏覽器 --------------> CDN --------------> 分發服務

由於該服務的請求量還是挺大的(每天的重定向請求量在七百六十萬左右,回源請求量在八萬左右),所以部署了兩個分發服務,前面擋了一個Nginx做負載均衡。

本來做Nginx是為了容錯,結果就因為這個還帶來了一個小問題:

由於兩臺分發服務的配置更新存在時間差,特別是靜態檔案,所以設想一下,服務A配置已經更新,而服務B沒有更新,一個請求來到服務A,重定向到新的URL,而回源的請求來到服務B,B返回舊的JS指令碼,被CDN快取,那麼新配置的生效時間將會推遲到CDN快取失效。。。

解決方案

首先想到的是將回源服務獨立出來,如果有配置更新,首先等待回源服務生效,然後再更新重定向服務。這個方案的缺點就是麻煩,得多部署一套服務,前端人員更新JS靜態檔案得等挺久,所以否掉了。

然後想到的一個方案是,重定向的時候在URL中標示出回源主機,舉例來說,A返回重定向地址,回源請求來到Nginx,Nginx根據URL判斷需要轉發到A,而不是B,如此就不會出現上面提到的問題,另外即使某臺服務宕機,另外一臺也可以正常提供服務。

這塊邏輯不應該耦合到原有的分發服務,所以就是想到了使用OpenResty解決:

http {

...

    upstream js_backend {
        server 10.1.1.20:8080;
        server 10.1.1.21:8080;
        keepalive 64;
    }

...

    server {
        listen       80;

...

        location ^~ /20/ {
            proxy_pass http://10.1.1.20:8080/;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }

        location ^~ /21/ {
            proxy_pass http://10.1.1.21:8080/;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }

        location = /js {
            proxy_pass http://js_backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";

            header_filter_by_lua_block {
                if ngx.status == 302 then
                    local regex = "^([0-9]+).([0-9]+).([0-9]+).([0-9]+):([0-9]+)$"
                    local m, err = ngx.re.match(ngx.var.upstream_addr, regex)
                    if m then
                        local loc = ngx.header["Location"]
                        local s = loc:find("/", 9)
                        ngx.header["Location"] = table.concat({loc:sub(1, s), m[4], "/", loc:sub(s+1, -1)})
                    else
                        ngx.log(ngx.ERR, err)
                    end
                end
            }
        }
        
        ...
        
    }
}
    

這裡沒有把upstream_addr完全拼進去,然後根據該段轉發,主要是考慮到安全上的問題。

服務切了過來,一切正常,效能也沒受到影響。(剛切過來時,回源請求比較多,受了點兒影響)

總的來說,對於OpenResty印象相當好,如果場景合適,以後會多多使用。

更新 0603

最近有一個後端tomcat掛掉了,然後看到error.log中大量的

2016/06/02 15:00:25 [error] 5128#0: *412855176 connect() failed (111: Connection refused) while connecting to upstream, client: 49.117.113.178, server: *.touclick.com, request: "GET /xxx HTTP/1.1", upstream: "http://10.47.64.40:8099/xxx", host: "js.touclick.com", referrer: "http://x"
2016/06/02 15:00:25 [warn] 5128#0: *412855176 upstream server temporarily disabled while connecting to upstream, client: 49.117.113.178, server: *.touclick.com, request: "GET /xxx HTTP/1.1", upstream: "http://10.47.64.40:8099/xxx", host: "js.touclick.com", referrer: "http://x"
2016/06/02 15:00:25 [error] 5128#0: *412855176 [lua] xxx_url_rewrite.lua:15: nil while reading response header from upstream, client: 49.117.113.178, server: *.touclick.com, request: "GET /xxx HTTP/1.1", upstream: "http://10.168.234.54:8099/xxx", host: "js.touclick.com", referrer: "http://x"

前兩行容易理解,是由於後端tomcat掛掉了,但是第三行有點兒奇怪,意思是正則表示式無法匹配ngx.var.upstream_addr,然後回想起平時偶爾也會看到一兩條,但是因為太偶然所以沒繼續關注。

ngx.var.upstream_addr打印出來發現是10.47.64.40:8099, 10.168.234.54:8099這個,查了一下文件:

$upstream_addr

keeps the IP address and port, or the path to the UNIX-domain socket of the upstream server. If several servers were contacted during request processing, their addresses are separated by commas, e.g. “192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock”. If an internal redirect from one server group to another happens, initiated by “X-Accel-Redirect” or error_page, then the server addresses from different groups are separated by colons, e.g. “192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock : 192.168.10.1:80, 192.168.10.2:80”.

啊…… 發現了一個bug……

修復如下:

if ngx.status == ngx.HTTP_MOVED_TEMPORARILY then
    local regex = "^([0-9]+).([0-9]+).([0-9]+).([0-9]+):([0-9]+)$"
    local upstream_addr = ngx.var.upstream_addr
    local i, j = upstream_addr:find(", ")
    while i do
        upstream_addr = upstream_addr:sub(j+1, -1)
        i, j = upstream_addr:find(", ")
    end
    local m, err = ngx.re.match(upstream_addr, regex, "o")
    if m then
        local loc = ngx.header["Location"]
        local i, j = loc:find("://")
        local s = loc:find("/", j+1)
        ngx.header["Location"] = table.concat({ngx.var.scheme, loc:sub(i, s), m[4], "/", loc:sub(s+1, -1)})
    else
        ngx.log(ngx.WARN, "upstream_addr: ", ngx.var.upstream_addr)
        ngx.log(ngx.ERR, err)
    end
end