使用nginx進行ab站點的過程簡單分析
由於業務需要,在官網上部署兩套前端頁面,通過特定的欄位(例如手機號碼)進行分流,來達到a/b站的要求,後續對a/b站最終資料進行分析,選出哪部分頁面對使用者體驗來說會更優秀。
nginx請求分流
考慮利用nginx的分流功能:
http://neoremind.com/2012/03/nginx%E6%A0%B9%E6%8D%AEcookie%E5%88%86%E6%B5%81/
在mac下使用brew install nginx,安裝完成後的目錄為:/usr/local/Cellar/nginx/1.10.3(根據不同的版本會有所不同)/,nginx配置檔案所在目錄:/usr/local/etc/nginx。
在Postman中需要安裝下載Postman Interceptor 擴充套件程式,此時就可以通過傳送Headers中的內容,來達到傳送Cookie的目的:
在nginx中,可以根據該cookie進行匹配判斷,決定要傳送的伺服器upstream:
match cookie
set $stream stream0;
if ($http_cookie ~* "phone=([^;]+)(1$)"){
set $stream stream1;
}
if ($http_cookie ~* "phone=([^;]+)(2$)"){
set $stream stream2;
}
在上面的示例中,僅能匹配單個http_cookie的最後一行,如果我們想要根據手機尾號進行使用者劃分的話,必須要匹配多個屬性:
match cookie
set $stream stream0;
if ($http_cookie ~* "phone=([^;]+)([5-9]$)"){
set $stream stream1;
}
if ($http_cookie ~* "phone=([^;]+)([0-4]$)"){
set $stream stream2;
}
進行範圍查詢,如果在5-9之間,對應stream1,否則對應stream2,如果沒有該cookie,需要給定一個預設值stream0。
上述情況出現在使用者已經登入的情況下,如果請求是處於註冊/登入的過程中,此時並沒有cookie資料,但這兩種操作都是通過POST請求,在form表單中存在對應的欄位手機號(phone),考慮是否可以根據request body中的欄位進行填充。
nginx中的變數介紹主要如連結中:
https://moonbingbing.gitbooks.io/openresty-best-practices/content/openresty/inline_var.html
可以在日誌中將 $request_body 打印出來,只要加上 $request_body 屬性即可,如果我們加上的資料為“phone=111”
------WebKitFormBoundaryq2rbBAdTrAuTi6IG\x0D\x0AContent-Disposition: form-data; name=\x22phone\x22\x0D\x0A\x0D\x0A111\x0D\x0A------WebKitFormBoundaryq2rbBAdTrAuTi6IG--\x0D\x0A
可見這些欄位是已經經過了額外的轉義處理,如果想要分析request body中的欄位比較麻煩,nginx只有在修改外掛執行的情況下(對nginx本身進行程式設計),才能訪問到request body中的欄位。
因此我們的方案調整為,註冊/登入完成後寫Cookie,但不能馬上重新整理快取,但可以通過頁面上的ajax請求success回撥,去強制重刷整個頁面來獲取a/b站點對應js/css資源,但可能造成額外的流量損耗。
內部域名解析/轉換
但我們部署的服務理論上是在兩臺docker容器上,並無固定ip,是通過不同的內部域名進行處理的,因此在upstream出現域名時,就會發生無法轉發的問題,即定義的 http://${url}並不進行替換。
upstream main {
server web1.local:80;
server web2.local:80;
server web3.local:80;
}
通過問題查詢,參考下面的一篇文章:
曾經嘗試了第一種方式,設定proxy_set_header,並沒有起作用:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
第二種方式理論上應該可行,是通過開放多個埠的方式,建立幾個virtual server,但由於我們將系統部署在lain(docker的一種實踐)上,限制條件比較多,只能開放一個web埠,因此該方式在lain環境上不可行。
server {
listen 8001 default_server;
server_name web1.example.com;
location / {
proxy_pass http://web1.local:80;
proxy_set_header Host web1.local:80;
}
}
server {
listen 8002 default_server;
server_name web2.example.com;
location / {
proxy_pass http://web2.local:80;
proxy_set_header Host web2.local:80;
}
}
server {
listen 8003 default_server;
server_name web3.example.com;
location / {
proxy_pass http://web3.local:80;
proxy_set_header Host web3.local:80;
}
}
upstream main {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://main;
}
}
Tengine提供此支援,http://tengine.taobao.org/document_cn/http_upstream_dynamic_cn.html,但通過測試發現tengine支援的這種方式可能只能利用外網可解析的域名來處理,如果是內網域名仍然是與沒有配置該模組的結果相同。
upstream stream80 {
dynamic_resolve fallback=next fail_timeout=30s;
#server www.xxx.cn;
server xxx.xxapp.xyz;
}
轉移到xxx.xxapp.xyz,此為內部解析的域名:
我們將轉移到 www.xxx.cn,會發現已經進行了轉換(錯誤是由於servername名稱不匹配)
基本判斷tengine的這個模組應該是可用的,但域名解析可能用到了一些特殊的條件或演算法,導致無法解析我們內網的域名,所以在只能部署單個對外埠的docker容器下,暫時不能解決內網upstream帶server_name的問題(最終考慮將其部署在虛擬機器上,開啟多個埠來解決該問題,也就是參考連結中的第二條)。