基於Nodejs的前端灰度釋出方案_20190228

zhiqiang21發表於2019-02-28

基於Nodejs的前端灰度釋出方案

1. 灰度釋出和A/B測試簡介

灰度釋出

將某個功能灰度釋出(逐漸放量)給特定線上人群,避免新功能全量上線帶來的風險。

上面的圖可以通過兩個方面來理解

  1. 藍色實線和藍色虛線訪問Nginx伺服器,nginx通過負載均衡將流量分攤到後端伺服器。
  2. 黃色的線是應用了灰度的流量(配置Nginx規則)可以將特定流量分發到特定的機房,以達到對特定使用者應用產品新功能;
舉個簡單的例子:將http請求cookie中含有test=1欄位的請求都轉發到灰度程式碼的機房;

上面通過通過配置特定Nginx規則的方法來達到產品灰度的方法雖然可以滿足一定業務量的需求,但是他也有很多的缺點

  1. 不靈活,每次上線新業務程式碼需要做灰度都要重新更新nginx規則,造成開發和運維負擔;
  2. 上線的程式碼要做機房區分,不能夠將程式碼全量。本地的Git程式碼也要區分開發分支和測試分支,線上分支等若干分支,管理起開麻煩;
  3. 不能滿足業務量大或者業務需要頻繁迭代,需要頻繁做測試的業務;

那麼有沒有更好的方法來做灰度釋出呢?當然是有的,A/B測試就能夠彌補上面通過Nginx規則來做灰度的缺點。

A/B測試

將線上一部分真實人群流量隨機拆分成多個組,對每個分組的人群應用不同策略或功能,通過計算每組人群的業務指標(轉化率、成交率等)來衡量策略或功能的實際效果。

我們通過下面的這張圖簡單的瞭解下A/B測試的原理:

由上圖我們可以知道A/B和傳統的灰度方法的區別:

傳統的灰度是通過Nginx分發流量到伺服器,A/B測試是通過業務程式碼區分流量訪問不同的程式碼塊。

那麼A/B測試的優缺點是什麼呢?

優點:

  1. 隨著業務的變化不用頻繁的變化Nginx規則,不用分機房上線業務程式碼,本地git分支不用為了做灰度而建專門的分支;
  2. 流量區分是業務程式碼做的。所以上線程式碼的時候可以全量上線到所有機房;

缺點:

  1. 因為流量區分是業務程式碼做的。所以在程式碼中會存在很多的if...else分支語句。但是這樣還好,因為根據SDK的規範來書寫程式碼,還是很好管理的。

2. 基於A/B測試的前端灰度怎麼做

前端跟後端很大的區別就是直接面對使用者,就算很簡單的修改一次按鈕的顏色就需要一次上線。這種操作對使用者是可感知的。

現代前端有個特點就是脫離了後端模板引擎的渲染,大多數是使用React、Vue這種MVVM框架的前端(瀏覽器)渲染。這種情況下後端其實僅僅是給使用者提供一個空的html檔案(工作中經常稱作為殼)。大多數業務程式碼開發完以後都是作為靜態檔案上線到伺服器,經過使用者訪問後快取到CDN節點上的。而且這個過程大多數是增量上線的。

其實我們每次上線完之後伺服器上快取的html檔案就包含不同的版本資訊。如果我們把這些版本資訊管理起來,並且通過特定的手段(對使用者請求應用A/B測試)就可以完成前端不同版本的灰度釋出。

使用Nodejs靈活控制前端釋出

我們可以觀察下Webpack或者是其它打包工具打包後的html檔案。每次外聯的靜態檔案都包含不同的hash戳。這些外鏈的檔案又都是增量快取到服務其上的。

index.html (我們頁面的“殼”)
一些 xxx.js檔案 (渲染頁面+頁面的業務邏輯)
xxx.css 檔案 (控制頁面顯示樣式)

大概就是下面的這個樣子

基於以上的特點,我們能不能儘量減少對業務程式碼侵入,而可以覆蓋業務改動較大的需求進行灰度或者是A/B測試呢?

看下下面的這個這個請求的圖:

每次我們打包編譯完之後,就將相關的css檔案和js檔案資訊儲存到本地的一個json檔案中。這些資訊的key可以是我們的git的tag資訊(主要來描述本次發版資訊包含的功能等)。

基本上json檔案包含的資訊如下:

const version = {
  // 可以描述本次的上線內容/ 或者是git tag
  'tag1': {
    'css': 'xxxxxxx.css',
    'app': 'app_xxx.js',
    'ventor': 'ver_xxx.js'
  }
}
這裡僅僅是一個簡單的demo示例,可以使用Nodejs寫檔案的特性直接將檔案版本號寫入到index.html返回給前端瀏覽器

Nodejs服務的特點是每次更新完程式碼需要重啟之後才能生效。每次上完線重啟服務就會先檢查原生程式碼根目錄下的這個json檔案。看下這個其中包含的tag是否在DB中儲存,如果有儲存就不做操作,如果沒有就將它儲存DB做持久化。

上面圖上面的Apollo就是用來配置那些使用者訪問新功能的平臺。在Nodejs端,每次接收到使用者請求的時候都會判斷使用者的資訊是否滿足相關條件,然後從DB中讀取相關靜態檔案資訊渲染到index.html中去。

簡單總結下:將每次打包的靜態檔案資訊先儲存下來,之後請求到達Nodejs的時候判斷使用者是否滿足相關條件,如果滿足就讀取DB將相關的靜態檔案資訊返回給Nodejs,Nodejs將靜態頁渲染好之後返回給使用者,達到灰度的目的。

3.其它細節問題

使用Nodejs之前我們的頁面就是直接部署在服務其上,這次使用了Nodejs後,會有很多其它的問題需要做,比如說Nodejs服務的監控,多機房部署等。這些在大部分的公司應該都有相關的運維工程師來做。我這裡簡單介紹一些其它的內容

規範的確定

這裡的規範包括本地開發時工程目錄的規範和線上使用者訪問url的規範。

  • 開發目錄規範

在筆者寫這篇文章的時候最新的Nodejs版本已經是 11.10版本了,最新的LTS版本是10.15.1版本。建議使用Nodejs的同學都升級自己的Node到8.0版本以上,因為8.0版本是一個官方原生支援async...await語句的版本。

.
├── client // 放置客戶端的程式碼
├── index.html
├── index.js
├── node_modules
├── output
├── package-lock.json
├── package.json
├── server // 放置服務端Nodejs程式碼
├── test.sh

需要注意的就是在編寫webpack打包工具的時候將server目錄下的給排除掉。放置不必要的編譯和產出,增加打包速度。
  • 線上url的約定

當使用了新的服務的時候為了防止跟舊業務的衝突肯定需要使用新的url。這個時候就需要做一些約定。目前我們是這麼約定的

// 域名/產品線/模組/
http://wwww.aaa.com/driver/bus/index.html
// 域名/產品線/模組/靜態檔案目錄
http://wwww.aaa.com/driver/bus/static/js/index.js
http://wwww.aaa.com/driver/bus/static/css/index.html

相容老業務需要做的工作

前面提到這次業務升級我們使用了新的url,但是為了保證業務的穩定性我們不是一次性將所有的流量都切到新服務上去的。我們也是通過批量的切的,所以會存線上上使用者有的地區訪問新服務有地區訪問舊服務。那麼有一天會有全部切換的一天,但是還是會有一些使用者訪問到舊連結,這個時候可以通過配置Nginx 的`rewrite來講舊連結都轉成新的連結。

  1. 升級後的業務怎麼訪問新的連線
  2. 已經請求相關的配置

避免不必要的請求

前端路由可以分為兩種方式,hash和path切換。因為對於前端渲染頁面來說,當第一次請求完成後,其實所有的頁面都已經下載到了本地(頁面非同步載入除外)。在我們通過path切換頁面的時候,每次都會向服務端傳送請求,其實這些請求是不需要到達Nodejs服務的。我們可以通過Nginx配置將這些無用的流量抵擋在Nginx這一層,減少伺服器的壓力。

如果是使用hash的方式則不存在這樣的問題,但是會有另外的問題就是對搜尋引擎不友好。當然前端路由切換還是應該根據自己的業務做取捨。

4. 前端業務擴充

當我們應用了Nodejs服務之後,可以擴充的技術點有哪些,一下簡單列舉一些:

  1. 服務端渲染:提高首屏渲染時間,提升使用者體驗。
  2. 前端介面校驗:增加前端訪問後端介面,後端介面返回資料的安全性。
  3. 前後端分離,前端工程師的靈活性更加的高。

5. 技術升級帶來的收益

  1. 前端上線可以實現小流量、灰度、釋出,可以對線上流量做A/B測試,減少線上問題;
  2. 可以定製化對部分使用者推動新功能;
  3. 加快首屏的渲染時間,提升使用者體驗;
  4. 多機房部署前端程式碼,降低前端服務不可用的風險;
  5. 團隊成員技術能力的提升;

6. 最後

當然這種方案也不僅僅是可以使用Nodejs來做,也可以使用其它語言。因為我們公司已經有基於A/B測試的Nodejs-SDK。我這我就不具體介紹原理了。原理可以參考百度百科。如果有問題需要一起討論可以留言或者是郵箱聯絡我:hpuhouzhiqiang@gmail.com

相關文章