多feature測試時後端測試環境管理的一種方案設想
假如我們有一組後端服務為使用者提供某些功能,各服務之間的呼叫關係(簡化的)可能如下圖

在實際的工作中,我們經常遇到這種場景:同時有多個feature在開發,比如feature_x,feature_y,feature_z,
這些feature的程式碼改動可能涉及到若干個相同或者不同的的後端服務,例如:
1)feature_x修改了app_srv0的<使用者賬戶展示> 介面(假設該介面的名字是user_account_show),user_account_show介面依賴app_srv1的 <賬戶餘額介面>user_account_balance
2)feature_y特性修改了user_account_balance介面,這個修改承諾介面的對外輸出不改變,只是修改了使用者的賬戶餘額計算公式。
在feature_y測試通過之前”user_account_balance介面對外輸出不改變“這個承諾是無法得到保障的,如果我們只有一個測試環境,多個feature都只能在這個測試環境上進行測試,那麼feature_x,feature_y的測試只能序列。如果並行進行測試的話,服務部署會是下面這個樣子的(淺綠表示feature_x,紫色表示feature_y):

測試過程中如果feature_y有bug導致user_account_balance返回的資料不對,就會間接導致user_account_show介面返回的展示資訊也不對,從而導致測試認為。但實際feature_x的修改。
常用的解決辦法
1)測試序列,缺點:時間週期常,專案feature上線慢
2)並行部署多套測試環境,缺點:資源成本和維護成本都很高,如果服務眾多或者並行開發的版本多那麼基本不具備可執行性
3)使用一套環境強行並行測試,缺點:特性之間的相互影響可能導致多個特性的測試都需要返工,拖長各個feature測試的耗時
解決方案設想
先不廢話,上個圖

由上面的分析我們可以知道要考慮的是“怎麼已較小的代價實現測試環境特性隔離”,“代價小”也就是儘可能少的部署服務,只部署有修改的服務即可;“特性隔離”就是不同特性分支的請求分別路由到支援對應特性的服務版本上。參考上圖:
feature_x修改了app_srv1,app_srv4,app_srv6,分別得到新版本服務app_srv1_x,app_srv4_x,app_srv6_x
feature_y修改了app_srv4,app_srv7,分別得到新版本服務app_srv4_y,app_srv7_y
假設feature_x版本的請求為Rx,feature_y版本的請求為Ry,基線版本的請求為R0,只要做到Rx,Ry請求分別走不同的呼叫鏈即可保證feature_x和feature_y的測試相互不影響,也就是:
Rx的呼叫鏈:app_srv0 —> app_srv1_x —> app_srv2 —> app_srv4_x —> app_srv6_x —> app_srv7
Ry的呼叫鏈:app_srv0 —> app_srv1 —> app_srv2 —> app_srv4_y —> app_srv6 —> app_srv7_y
如何做到這一點
讓請求攜帶路由資訊(以下格式為非標準json格式,只是類似格式方便展示而已)
在請求的公共頭部攜帶一個版本繫結的路由資訊,比如編號feature_x_20180101,feature_y_20180101
//feature_x //comm_head { "route_id":"feature_x_20180101" } //feature_y //comm_head { "route_id:feature_y_20180101" } 複製程式碼
儲存一張路由表
//route_table { "feature_x_20180101": [ { "appid": "app_srv1", "srv_version": "app_srv1_xxx", "addr": "ip:port" }, { "appid": "app_srv4", "srv_version": "app_srv4_xxx", "addr": "ip:port" }, { "appid": "app_srv1", "srv_version": "app_srv6_xxx", "addr": "ip:port" } ], "feature_y_20180101": [ { "appid": "app_srv4", "srv_version": "app_srv4_yyy", "addr": "ip:port" }, { "appid": "app_srv7", "srv_version": "app_srv7_yyy", "addr": "ip:port" } ] } 複製程式碼
每個服務維護自己的appid,發起rpc的服務通過某種方式獲取路由表配置,從獲取的路由表中尋找對映的appid,和被呼叫目標appid(呼叫方一定知道被掉方的appid)進行對比,找到需要呼叫的服務地址和埠,例如:
feature_x的請求Rx到達app_srv0之後,app_srv0需要呼叫app_srv1,app_srv0解析出請求頭部(comm_head)的route_id:feature_x_20180101,使用route_id獲取到路由表feature_x_20180101項下面的所有配置,從獲得的配置中尋找appid=app_srv1的項(這裡服務的數量是很有限的,不必擔心遍歷的效能),獲取對應的ip和埠發起呼叫,這樣就可以是Rx請求觸發的第一次rpc呼叫的是feature_x版本的app_srv1_x而不是基線版本的app_srv1或者feature_y版本的app_srv1_y,如果找不到對應的想就採用預設路由呼叫,也就是呼叫基線版本;發起呼叫的時候透傳請求頭裡面的route_id,這樣後續呼叫鏈的每個服務都能使用上述過程找到請求要呼叫的版本分支,這裡需要增加的就是對路由表的維護工作以及客戶端發起請求的時候需要增加請求頭的寫入。釋出上線之後也可以採用這種方法進行AB test,如果不需要可以對配置進行修改或者前端發起請求的時候不攜帶路由資訊。