有關淘寶首頁的基礎知識
很多人都用淘寶,但是對淘寶首頁並不了解,我這裏帶來壹位淘寶首頁的設計師的講解,希望可以讓妳對淘寶首頁有壹個基本的認識。
壹、相關背景介紹
淘寶首頁是淘寶的門面,承載著幾乎淘系所有業務的入口,流量很大,量級單位為億。近幾年無線端崛起,業務重點開始向無線終端偏移(目前不能叫偏移,基本以無線為主了),所以淘寶 PC 端首頁的流量也有削減,不過即便如此,它的日均 PV 依然相當高。
淘寶首頁壹向是內部平臺和技術的試驗田,它壹直在變化著。最新的框架和系統都會找淘寶首頁試點,可以試想下,如果某壹項需要推動的升級或者優化措施在淘寶首頁已經上線,並且拿到了良好的數據和穩定性,其他業務還有什麽理由不去嘗試和更叠呢?同時,去年壹年身在淘寶前端的技術架構組,自然而然也會主動去 push 壹些實驗性的內容到業務上。
淘系的站點頁面包括首頁、其他頻道頁和活動頁等,這些頁面並不都由淘寶前端壹行壹行的代碼碼出來,業務如此之多,這種玩法即便人數 double 也忙不過來。事實上,大多數頁面都是依托內部的搭建平臺——運營或者前端通過模塊搭建的方式——構建的,而前端 focus 的重點在於搭建平臺的建設自身以及模塊的通用性和復用率的保障,當然,還有壹些工程化的東西。
使用搭建平臺搭建的頁面,前端只需要考慮組成頁面的原子模塊的開發,整體的渲染由搭建平臺提供的統壹腳本全權負責。而在淘寶首頁上,考慮到頁面模塊數量巨多,加上還有少量跨部門、跨團隊的溝通,渲染模型略微不同。
二、淘寶首頁的整體變遷
背景中提到,淘寶首頁依托於內部搭建平臺,它的變遷自然也是跟著搭建系統的變化而變化的。
1、PHP 下的淘寶首頁
接手淘寶首頁不久,便遇到了壹年壹度的改版,那時它還運行在 PHP 環境中。這裏需要說明的是,淘寶首頁的所有代碼完全由前端掌控,前端不會直接跟數據庫打交道,其數據來源分為兩部分。
數據來源
壹是運營填寫的數據。 采用前端挖坑的形式,預留坑位讓運營獲取填寫數據,
運營填寫這些坑位就會產生這份 PHP 模板對應的數據,最後渲染出來就是壹個完整的 HTML 片段(實時性渲染)。
舊版搭建系統中就是通過這種方式構造壹個子模塊。我描述得十分簡單,但作為壹個平臺它需要考慮的東西還有很多,比如數據順序的控制、定時發布、回滾機制、過濾機制、篩選機制、數據的同步、數據的更新、版本控制、權限控制、其他系統的引用等等。
二是後端或者個性化平臺提供的數據。 不同的業務有不同的訴求。壹些業務有自己的後端,他們要求使用自己業務產出的數據;有的業務希望用戶看到的內容不壹樣,千人千面,期望接入算法;壹些業務跟賣家直接打交道,期望使用招商數據;而有些業務期望采用運營從數據池篩選出來的數據……總之,淘寶首頁需要對接形形色色的系統,接口繁多。後面會提到對動態數據源的整合。
並且這些系統對應的域名是不壹樣的,JSONP 格式自然也就成了首選。但壹些特殊的系統,比如廣告,它的渲染並不是壹個簡單的 JSONP 請求,可能它還要幹預整個廣告的渲染流程,比如加載他們的 JS,把渲染的控制權交過去。
頁面的架構
上面介紹了數據的來源和子模塊的結構,那麽整個頁面又是如何構成的呢?模塊的搭建分為兩種,壹種是可視化搭建,運營或者前端可以將開發好的模塊(或者模塊庫中選取的模塊)拖拽到容器內,形成壹個頁面:
當然,上圖也只是壹個模型,作為壹個系統需要考慮的問題還有很多很多,如頁面的布局、多終端適配、模塊的臨時隱藏、位置調整、皮膚選擇、模塊的復制等等。
通過模塊 id 將模塊引入,並且添加壹些類似 lazyload 的標記,方便控制渲染節奏和數據入口。源碼搭建和模塊搭建的區別在於,前者更易於控制模塊的結構以及模塊的渲染順序。
動態數據源
首頁面對壹大堆接口和平臺,對接幾十個業務方,接口是個很大的問題,由於後臺系統的差異,基本沒有辦法統壹數據源的格式,壹旦運營哪天心血來潮要換壹個他自己覺得用的更爽的或者數據更好的系統,前後端估計又得溝通和對接幾次。
平臺具備數據源接入的能力,也就是說我們挖的坑不僅僅可以讓運營填數據,還可以從各種數據源中直接導入數據,當然,這裏需要進行壹次數據字段的映射轉換。
綁定之後,數據既可以同步輸出,也可以異步輸出,這些都是平臺提供的能力。這個方案基本上解決了後端系統/接口變化的問題,並且減少了前後端之間的溝通成本。
不過這裏需要註意的是,雖然頁面上的接口都通過平臺統壹梳理了壹次,這也意味著,頁面所有的請求會先流經平臺,然後分發到各個後端,平臺的抗壓能力要求很高。
2、PHP 到 Node 的變遷
淘寶首頁日均請求的這個量級,不可能是十幾二十臺臺服務器抗得住的,支撐它必須有壹個服務集群。
每壹個 CDN 節點上都具備 PHP 渲染的能力,當頁面發布時,我們把該頁面所有的模塊和數據同步到全部 CDN 節點上,基本模式大概就是如此了。看起來還挺不錯,但是經過壹段時間的運維,很多安全、性能問題都慢慢浮現出來了:
性能問題。 每個 PHP 頁面包含多個子模塊,而子模塊也有可能引用了其他的子模塊,PHP 的 include 操作是存在消耗的,每壹次引用都是壹次磁盤 IO,壹個渲染節點上跑了成千上萬個類似淘寶首頁的 PHP 頁面,並發壹高其效率可想而知。
推送機制問題。 文件同步是壹種比較惡心的機制。首先,時間上沒法控制,壹個文件同步到所有的節點,快則幾秒鐘,慢的話耗時會超過壹兩分鐘;並且同步過程還有可能失敗,健康檢測的成本也是相當高的。發布比較緊湊時,需要同步的文件也很多,很容易造成隊列堆積,加劇同步差的體驗。
實時性強需求問題。 文件在推送之前,還可能經過壹些前置系統,發布鏈路越長,線上生效時間越慢,慢的時候大約五分鐘才生效,這樣的延時對於實時性要求很高(如秒殺)的需求來說是完全不能接受的。
當然,還有很多其他問題,如運維成本增高、安全風險增高、PHP 資深人才儲備不足等等。所以 PHP 渲染容器的命運,就是,被幹掉。
服務集群為 Cache CDN,它只有靜態文件處理能力,沒有 PHP/Node 的渲染能力,所以處理效率高,性能也好,抗壓能力相當強,並且扛不住的時候還可以花錢買服務,拓展 Cache 集群。
用戶訪問時,Nginx 轉到 Cache CDN,如果命中緩存則直接返回,沒有命中便回源到源站服務器。源站服務器是具備模塊渲染能力的 Node 服務,它可以做很多事情:
· 控制 Cache 響應頭,通過 max-age 和 s-maxage 控制頁面在客戶端的緩存時間以及在 Cache 上的緩存時間,這個緩存時間可以根據需求隨時做調整,比如大促的時候調長壹些;
· 控制內外網環境,和 AB 測試狀態;
· 融合前端相關的工具鏈,比如檢測、壓縮、過濾等等。
它的優勢有很多,這裏不壹壹列舉了。這個模式中還添加了壹層容災,源站服務器每隔壹段時間將數據推送到於 Cache 同機房的備份服務器,壹點源站掛了,還能夠自動容災到備份數據上。
模式的變化不僅在運維上有了突破,CDN 被攻擊時的安全風險也低了很多,同時也省卻了 sync 所需的各種檢測機制,每年節約成本也是百萬以上,優勢還是相當明顯。
3、Node,不壹樣的模式
上面 PHP 模塊中,我們只說了 HTML 和數據部分,用心的讀者應該已經發現,CSS 和 JS 這些靜態資源都沒提到,那頁面是如何渲染的呢?
舊版 PHP 頁面中,我們是直接引入了壹個 CSS 和壹個 JS,淘寶這邊采用的是 git 版本叠代發布,這些靜態資源都是直接放在壹個 git 倉庫中。也就是這樣:
每次發布完 git 文件,再修改 PHP 的.版本號,然後發布 PHP 代碼。當然,也做了相關的優化,比如發布 git 時自動更新版本號等。
壹個模塊的 CSS/JS 和模板放在壹起,CSS/JS 與頁面其他模塊的靜態資源是相互獨立的,目的就是希望單個模塊也能夠完整的跑起來,更加利於模塊的復用。
而模塊的挖坑,也從模板中獨立了出來,采用 JSON Schema 的形式定義數據格式:
模塊之間相互獨立隔離,所以會存在壹定程度的冗余,不過模塊解偶帶來的收益要比這點冗余要多得多。事實上,我們是通過壹個倉庫去管理單個模塊的。頁面的渲染就比較簡單了,源站 Node 容器會將所有的 index.xtpl 合並成壹個 page.xtpl,為減少頁面請求,css 和 js 也會 combo 成壹個文件。
任何模塊的更新,頁面都會有感知,下次進入系統時,就會提示是否需要升級模塊和頁面。
三、淘寶首頁的性能優化
首頁模塊眾多,如果壹口氣吐出來,DOM 數量必然超過 4k 個,其結果就是首屏時間極長。按照 TMS 的開發規範,每個 TMS 模塊都包含壹個 index.js 和 index.css,最後展示出來兩個 combo 的 js 和 css。首頁加載的時候也不會壹口氣執行所有 index.js,否則剛開始頁面阻塞會十分嚴重。
頁面的渲染邏輯
· 遍歷所有 TMS 模塊(包含壹個 J_Module 的鉤子);
· 部分 TMS 模塊無 JS 內容,但是加載了壹個 index.js,為模塊添加 tb-pass 的 class,用於跳過該模塊 JS 的執行;
· 將頁面分為兩塊,首屏為壹塊,非首屏整體為第二塊,先將首屏模塊加入到懶加載監控;
· 待首屏模塊加載完成,或者用戶處理了頁面交互時(滾動、鼠標移動等),將非首屏模塊加入到懶加載監控;
· 處理壹些特殊模塊,它們會在進入視窗之前幾百像素就開始加載;
· 監控滾動,按照以上邏輯,渲染模塊;
· 部分模塊即便是被執行了,也不壹定渲染出來,因為它的優先級不高,在模塊內部加了事件監聽,比如等到 mouseover/onload 事件觸發的時候再渲染這些內容。
代碼的性能優化是壹個精細活,如果妳要在壹個龐大的未經優化的頁面上做性能優化,可能會面臨壹次重構代碼。上面的文章提到的是頁面內部的細節優化,但是在開發流程中做的規範化、標準化,以及線上訪問通路中的各個環節優化還沒有提及。
四、淘寶首頁的穩定性保障
在大流量下,任何小問題都會被放大成大問題,所以開發環節遇到的任何偶發性問題都需要引起重視。不過很多偶發性問題在我們的測試環境中是找不到的,比如與地域相關的問題(如上海的某個 CDN 節點掛了),用戶屬性問題(如 nickname 最後壹個為字母 s 的用戶頁面天窗),瀏覽器插件問題,運營商廣告註入問題等等。
難以在上線之前把所有問題考慮周全,但是有兩點是必須做好的:兜底容災 + 監控預警。
1、兜底容災機制
兜底容災有兩個層面的考慮:
· 異步接口請求錯誤,包括接口數據格式錯誤,接口請求超時等;
· 同步渲染,源站頁面渲染出錯。
異步接口請求,主要涉及到的是後臺系統,對接系統較多,各個系統的穩定性和抗壓能力各不相同,這方面的保障有多種方案。
每次數據請求都緩存到本地,並且為每個接口都提供壹個硬兜底。還有壹種方案是「重試」,請求壹次不成功那就請求第二次。
對於同步渲染,它只需要頁面模板和同步數據,兩者中任壹種存在錯誤,源站都會報錯,此時回源返回的內容就是壹個 error 頁面,狀態碼為 5xx。這個錯誤不壹定是開發者造成的,有可能是系統鏈路出現同步異常或者斷路問題。
壹旦源站任何異常,Nginx 都會轉到與 Cache CDN 同機房的首頁鏡像上去,這個鏡像內容就是淘寶首頁的 HTML 備份源碼。
2、監控預警機制
監控也有兩個層面:
· 模塊級別的監控,接口請求布點、模塊天窗檢測等;
· 頁面的監控,在頁面上添加特殊標記,定時回歸所有 CDN 節點,查看特殊標記是否存在。
模塊層面的監控,內容還是相當多的,監控的點越多越詳細,到最後定位問題的效率就會越高,比如在壹個稍微復雜的模塊上,我會埋下這些監控:
· 接口請求格式錯誤、請求失敗、請求超時,至少三個埋點;
· 硬兜底數據請求失敗埋點;
· 模塊 5s 內沒有渲染完成統計埋點;
· 模塊內鏈接和圖片黑白名單匹配埋點。
其中部分監控還會自動處理明確的錯誤,比如 https 頁面下出現了 http 的圖片,會立即自動處理掉這些問題。
3、上線前的自動化檢測
這屬於淘寶整個工程化環境的壹部分,前端自動化測試。壹般會在上線之前處理這些問題:
· 檢測 HTML 是否符合規範
· 檢測 https 升級情況
· 檢測鏈接合法性
· 檢測靜態資源合法性
· 檢測 JavaScript 報錯
· 檢測頁面加載時是否有彈出框
· 檢測頁面是否調用 console.*
· 頁面 JS 內存記錄
當然,也可以自己添加測試用例,比如檢測接口數據格式、模塊天窗問題等。自動化檢測也可以設定定時回歸,還是比較有保障的。
五、淘寶首頁的敏捷措施
1、健康檢查
頁面模塊眾多,為了能夠追蹤頁面上每壹個小點的變化,我在請求、渲染的每壹個環節都做了詳細的統計。
壹旦接口請求失敗,或者接口走了容災邏輯,或者模塊渲染超過 5s,控制臺都會有黃色警報,當然此時,也已經向服務器發送了警報統計。
2、接口 Hub
接口 Hub 是對數據請求的管理工具。
頁面很多模塊的渲染都需要壹個以上的數據源,壹旦運營反饋頁面渲染數據異常,可以直接通過 Hub 找到數據,加速 Bug 定位效率。同時 Hub 也可以用來切換環境,將壹個接口的請求切換到日常或者預發環境的接口之中,它是調試的利器。
3、快捷通道
我在頁面腳本執行前後都放了壹個快捷操作通道,壹旦遇到緊急線上問題,比如樣式錯亂溢出、接口報錯導致天窗等,可以通過快捷通道直接修改頁面的 CSS 和 JS,兩分鐘內上線。
不過這類通道只適合緊急問題的修復,畢竟隨意插入 JS 代碼是存在很大風險的。
;