1. 程式人生 > >使用nginx進行ab站點的過程簡單分析

使用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的問題(最終考慮將其部署在虛擬機器上,開啟多個埠來解決該問題,也就是參考連結中的第二條)。