本文主要以 vivo 商城項目的先後端分離經驗,總結先後端分離思路,整理先後端分離方案,以及分離過程當中遇到的問題及解決方案。javascript
vivo官方商城在2015年建立網上商城,開闢網絡銷售渠道,幾年來日活和銷售額持續增加,極大的助力了vivo手機的銷量。前端
而隨着業務版本迭代愈來愈快,業務內容逐漸增多,先後端不分離模式的弊端也逐漸顯露出來,迭代效率沒法跟上逐步增加的業務需求,多端擴展成本高。java
爲此,咱們在2019年開始進行商城項目的架構升級,進行先後端分離,前端技術升級,接口規範化,以便應對將來更多的業務挑戰。後端
架構升級,第一步面臨的問題即是先後端分離,先後端不分離的痛點已經無需贅述,既影響開發效率,又影響開發體驗,但商城仍然處於業務高速發展時期,不能由於技術重構而停下業務版本的迭代。服務器
所以業務版本迭代必需要和先後端分離同時進行,那怎麼才能作到雙線並行,魚和熊掌兼得呢?方案要如何設計?如何應對技術升級帶來的風險和不可控因素呢?網絡
讓咱們帶着這些問題來看看vivo商城是如何一步步實現先後端分離。架構
先分析咱們的業務模塊,是標準的樹形結構,每一個功能模塊包含若干子模塊,每一個子模塊又能夠包含若干更小的子模塊,每一個子模塊對應的頁面地址頁相似於麪包屑,造成層級包含關係,而且與功能模塊的包含層級一一對應,以下圖:前後端分離
那若是咱們能控制某個模塊的頁面請求,讓它返回分離以後的新頁面,別的請求還訪問老頁面,豈不是就能逐個功能模塊進行分離?嗯,理論上是可行的。ide
好比以訂單模塊爲例,咱們能夠攔截訂單相關頁面的請求,使得訂單頁面的請求訪問新的資源,其餘頁面請求還訪問老的資源,以下圖:工具
那麼問題來了,如何實現按訪問路徑去請求不一樣資源?商城目前的頁面請求和接口請求都是經過 Nginx來作統一的門戶入口,咱們可否經過Nginx區分頁面請求路徑,從而達到路由控制的目的?
經過學習研究Nginx配置,發現 Nginx路由匹配有這樣一條特性:
location 匹配首先檢查使用前綴字符定義的 location,選擇最長匹配的項並記錄下來,若是沒有匹配的正則 location 和精確匹配的 location,則使用前面記錄的最長匹配前綴字符 location。
這條信息很是重要,Nginx 的 location 匹配會採用最長的匹配路徑,由於咱們的頁面路徑層級結構跟功能模塊的層級結構是對應的,那咱們 location匹配的路徑越長,匹配的功能模塊的粒度就越細,匹配的相應頁面就越精確。
好比我的中心(路徑爲/my)下包含訂單相關模塊(路徑爲/my/order),根據Nginx最長匹配原則,就能夠經過控制匹配路徑長度,來控制要分離的模塊的大小,好比經過攔截/my/order來攔截全部的訂單相關頁面。
location /my/order { # 匹配全部以/my/order開頭的請求,其餘請求不會被攔截,如/my/coupon則不會被攔截 # 如訂單列表頁面 https://shop.vivo.com.cn/my/order/list 會被攔截 # 將匹配到的頁面請求轉發到新的靜態資源服務器 proxy_pass http://new-download; }
同理,我的中心下的評價模塊下面的頁面路徑都以/my/remark開頭,那能夠經過增長配置 location /my/remark {} 來攔截評價模塊。當我的中心下面的子模塊都分離完畢,即可以經過縮短匹配路徑來擴大匹配範圍。
location /my { # 匹配全部以/my開頭的請求,即我的中心的全部頁面都被攔截 # 如我的中心首頁 https://shop.vivo.com.cn/my 會被攔截 # 將匹配到的頁面請求轉發到新的靜態資源服務器 proxy_pass http://new-download; }
當全部的模塊逐步完成了分離,就能夠直接攔截根路徑,將全部的頁面請求都取新的靜態資源。
技術是服務於業務的,而技術的更迭演進又能夠爲業務帶來提高,業務版本在高速迭代,猶如奮力爬升的航天飛機,而利用上述方案,即可覺得飛行中的飛機更換零件甚至發動機,在業務版本迭代的同時逐步進行模塊重構,業務是按版本逐步迭代的,但每次迭代通常不會涉及太多模塊,而是重點針對其中一到兩個模塊進行迭代或者修改,當某次業務版本涉及某個模塊時,咱們即可以對這個模塊進行分離,在分離的同時進行業務版本內容的開發,即完成了業務功能開發,又完成了模塊的分離重構,而測試同窗只須要測試一次,提升了版本迭代和測試效率。
固然,業務版本通常都是對一些通用模塊的迭代,好比商品,結算,購物車等模塊,總有一些比較穩定或者生僻的模塊很長一段時間甚至一兩年內都不會有大的變更,針對這種狀況,就須要單獨的版本進行迭代,好在這些模塊並不算多,業務也相對比較穩定。
在通常的業務迭代中,只須要對某些模塊代碼進行修改,適配,或者補充,整個模塊徹底重構,無疑會增長額外的開發和測試工做量,而更多的變更和修改帶來的是更多的風險和不可控因素,那怎麼保證重構質量呢?
同時,業務版本策劃可能只涉及這次版本業務內容,不涉及該模塊的歷史功能,那測試該以什麼參考標準來測試這些歷史功能呢,如何能保證測試覆蓋率,確保全部的業務場景都能被覆蓋到呢?
關於參考標準,線上標準就是最好的標準。如今網頁都提供了http和https兩種訪問方式,用戶訪問的內容是同樣的,在服務器配置也基本上是同樣的,將https的配置改成新的配置,而http還保持不變,當用https的形式就能夠訪問到最新的頁面,而用http形式訪問的仍是老頁面,固然,這兩個頁面是能夠同時訪問的,所以咱們能夠進行新舊頁面之間的對比,確保分離先後頁面的一致性。
server { listen 80; server_name shop.vivo.com.cn; # 老的配置不變 ... } server { listen 443; server_name shop.vivo.com.cn; # 分離模塊配置 location /my { proxy_pass http://new-download; } }
爲了保證測試覆蓋率,咱們引入了代碼覆蓋率檢查工具,精確檢測某一行代碼是否被測試用例覆蓋。經過代碼覆蓋率報告,咱們能很清晰的看出哪些代碼被執行了,哪些分支沒有被執行到,爲何沒有被執行到,基於這些反饋對測試用例作調整和補充,確保全面的測試覆蓋。
(1)上線部署順序
上線過程主要有三個部分,分別是服務端接口發佈,前端靜態資源發佈,Nginx修改,這三個操做是有先後依賴關係的,若是順序錯了,那就會形成線上事故,所以必須嚴格遵照如下發布順序:
服務端接口是向前兼容的,在分離過程當中並非直接在老接口上修改,而是新開了接口,保證在發佈期間新老接口都是能夠調用的。
前端頁面依賴於服務端接口,所以必須確保服務端接口已經發布完畢才能夠發前端頁面,不然會出現接口404的問題。
這一步要放到最後,若是Nginx在前端靜態資源發佈以前就進行了修改,那用戶訪問頁面時就會出現頁面404的狀況。
(2)容災措施
當版本上線出現問題時,如何能快速回退,且不對用戶形成影響?由於咱們是直接攔截用戶請求並重定向到了新的靜態資源服務器,那若是出現線上問題,只須要將此部分的攔截配置關閉,就能夠達到快速回退的目的。而服務端接口是向前兼容的,所以無需後退。
根據這個方案,咱們通過一年時間的逐步迭代,迭代8個版本,終於完成wap端的先後端分離,可讓專業的人作專業的事。如今回過頭來看看,此次技術升級咱們到底解決了什麼難題,而它又爲咱們帶來了什麼提高和正向做用呢?
純前端業務上線發佈速度提高10+倍
釋放研發人力,專業的人作專業的事,開發效率最高提高1倍
打好native化、多端渠道拓展基礎
- 積累技術經驗、賦能更多業務
整個先後端分離過程漫長而曲折,在這個過程當中咱們面臨的最大問題就是如何在人力成本,業務需求和技術升級之間取得一個平衡點,這對咱們來講是頗有挑戰性的一個難題,不少時候技術問題均可以找到參考和解決方案,但如何能在複雜的人力,資源,版本,技術積累狀況下制定技術方案,兼顧各方,去主動推進解決問題,提早識別和規避風險纔是對咱們真正的考驗。
做者:vivo官網商城前端團隊