最近給暢言加上了單點登錄,也就是可以用網站的帳號來在暢言上發射評論。暢言會通過你設置的一個介面來獲得用戶名,頭像等。但是我發現隊友暢言會頻繁請求這個介面,有時候會達到單個用戶一秒鐘好幾次??
雖然在php層有redis,壓力不會很大,但是這樣頻繁的請求還是會中防CC的策略,影響用戶正常的瀏覽。所以我決定在CDN上做個緩存。
對於請求/user/userinfo.php?callback=abcde,響應應該為:
abcde({一個json})
但是jquery生成的JSONP請求的callback是隨機生成的,所以每次的響應需要根據這個callback的值來變化,沒法直接緩存整個結果。
於是我們把proxy_cache_key設置成只與和用戶相關的cookie有關,下面的例子里是cookie_usr,也就是客戶端發送的Cookie: usr=xxxx。對於大多數未用戶是未登錄的,可以共享同一個cache。
然後我們用body_filter_by_lua*來操作響應。因為body_filter_by_lua*執行的階段是在output-body-filter,是在proxy_pass和proxy_cache之後,所以我們可以用它來修改讀取到緩存的內容再輸出。另外用head_filter_by_lua*來修改Content-Type,使不帶callback時返回的是一個json。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
location ~ /user/userinfo.php { set $_userinfo_bked http://backend/userinfo.php; proxy_pass $_userinfo_bked; proxy_cache_key userinfo-$cookie_usr; proxy_cache_valid 200 204 301 302 1h; proxy_cache_valid 404 500 502 503 504 1m; proxy_cache cache_main; proxy_set_header Host $http_host; proxy_ignore_headers Set-Cookie; header_filter_by_lua_block { if ngx.var.arg_callback ~= nil then ngx.header['content-type'] = "application/javascript" else ngx.header['content-type'] = "application/json" end } set $_userinfo_buf_start 0; body_filter_by_lua_block { if ngx.var.arg_callback ~= nil then if ngx.var._userinfo_buf_start == "0" then ngx.arg[1] = ngx.var.arg_callback .. "(" .. ngx.arg[1] ngx.var._userinfo_buf_start = 1 end if ngx.arg[2] then ngx.arg[1] = ngx.arg[1] .. ")" end end } } |
proxy_cache_valid和proxy_ignore_headers根據實際情況可能會影響是否緩存,所以必要的時候要加上。
另外,因為body_filter_by_lua*是unbuffered的模式,每次有一個chunk到達output-body-filter階段,這個filter就會被執行一次。所以我們得自己維護一個狀態來記錄當前的位置,因為我們只想在整個響應的頭上和尾巴上加上字元串。其中ngx.arg[2]在輸出到達結尾的時候為true,可以通過判斷它來決定要不要加上末尾的括弧。
另外,我們還可以用子請求的方式來完成這個功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
location = /userinfo { internal; proxy_pass http://backend/userinfo.php; proxy_set_header Host $http_host; proxy_cache_key userinfo-$cookie_u2; proxy_cache_valid 200 204 301 302 15m; proxy_cache_valid 404 500 502 503 504 1m; proxy_ignore_headers Set-Cookie; proxy_cache sht_cache_main; } location ~ /user/userinfo.php { content_by_lua_block { local ret = "" local res = ngx.location.capture("/userinfo", {share_all_vars = true}) ret = res.body if ngx.var.arg_callback ~= nil then ngx.say(ngx.var.arg_callback .. "(" .. ret .. ")") else ngx.say(ret) end } } |