背景
京東活動系統 是一個可線上編輯、實時編輯更新和釋出新活動,並對外提供頁面訪問服務的系統。其高時效性、靈活性等特徵,極受青睞,已發展成京東幾個重要流量入口之一。近幾次大促,系統所承載的pv已經達到數億級。隨著京東業務的高速發展,京東活動系統的壓力會越來越大。急需要一個更高效,穩定的系統架構,來支援業務的高速發展。本文主要對活動頁面瀏覽方面的效能,進行探討。
活動頁面瀏覽效能提升的難點:
1. 活動與活動之間差異很大,不像商品頁有固定的模式。每個頁面能抽取的公共部分有限,可複用性差。
2. 活動頁面內容多樣,業務繁多。依賴大量外部業務介面,資料很難做到閉環。外部介面的效能,以及穩定性,嚴重製約了活動頁的渲染速度、穩定性。
經過多年在該系統下的開發實踐,提出“頁面渲染、瀏覽非同步化”的思想,並以此為指導,對該系統進行架構升級改造。通過近幾個月的執行,各方面效能都有顯著提升。在分享"新架構"之前,先看看我們現有web系統的架構現狀。
一、web架構發展與現狀
1.開發階段
以京東活動系統架構的演變為例,這裡沒有畫出具體的業務邏輯,只是簡單的描述下架構:
2.第二步,一般是在消耗效能的地方加快取,這裡對部分查庫操作加redis快取
3.對頁面進行整頁redis快取:由於活動頁面內容繁多,渲染一次頁面的成本是很高。這裡可以考慮把渲染好的活動內容整頁快取起來,下次請求到來時,如果快取中有值,直接獲取快取返回。
以上是系統應用服務層面架構演進的,簡單示意。為了減少應用伺服器的壓力,可以在應用伺服器前面,加cdn和nginx的proxy_caxhe,降低迴源率。
4.整體架構(老)
除了前3步講的“瀏覽服務”,老架構還做了其他兩個大的優化:“介面服務”、靜態服務
1.訪問請求,首先到達瀏覽服務,把整個頁面框架返回給瀏覽器(有cdn、nginx、redis等各級快取)。
2.對於實時資料(如秒殺)、個性化資料(如登陸、個人座標),採用前端實時介面呼叫,前端介面服務。
3.靜態服務:靜態資源分離,所有靜態js、css訪問靜態服務。
要點:瀏覽服務、介面服務分離。頁面固定不變部分走瀏覽服務,實時變化、個性化採用前端介面服務實現。
介面服務:分兩類,直接讀redis快取、呼叫外部介面。這裡可以對直接讀redis的介面採用nginx+lua進行優化( openresty ),不做詳細講解。 本次分享主要對“瀏覽服務”架構
二、新老架構效能對比
在講新架構之前先看看新老架構下的新能對比
1.老架構瀏覽服務新能:
擊穿cdn快取、nginx快取,回源到應用伺服器的流量大約為20%-40%之間,這裡的效能對比,只針對回源到應用伺服器的部分。
2015雙十一, 瀏覽方法tp99如下:(物理機)
Tp99 1000ms左右,且抖動幅度很大,記憶體使用近70%,cpu 45%左右。
1000ms內沒有快取,有阻塞甚至掛掉的風險。
2.新架構瀏覽服務新能
本次2016 618採用新架構支援,瀏覽tp99如下(分app端活動和pc端活動):
移動活動瀏覽tp99穩定在8ms, pc活動瀏覽tp99 穩定在15ms左右。全天幾乎一條直線,沒有效能抖動。
新架構支援,伺服器(docker)cpu效能如下
cpu消耗一直平穩在1%,幾乎沒有抖動。
對比結果:新架構tp99從1000ms降低到 15ms,cpu消耗從45%降低到1%,新架構效能得到質的提升。
why!!!
下面我們就來揭開新架構的面紗。
三.新架構探索
1. 頁面瀏覽,頁面渲染 非同步化
再來看之前的瀏覽服務架構,20%-40%的頁面請求會重新渲染頁面,渲染需要重新計算、查詢、建立物件等導致 cpu、記憶體消耗增加,tp99效能下降。
如果能保證每次請求都能獲取到redis整頁快取,這些效能問題就都不存在了。
即:頁面瀏覽,與頁面渲染 非同步。
2.直接改造後的問題以及解決方案:
理想情況下,如果頁面資料變動可以通過 手動觸發渲染(頁面釋出新內容)、外部資料變化通過監聽mq 自動觸發渲染。
但是有些外部介面不支援mq、或者無法使用mq,比如活動頁面置入的某個商品,這個商品名稱變化。
為了解決這個問題,view工程每隔指定時間,向engine發起重新渲染請求-最新內容放入redis。下一次請求到來時即可獲取到新內容。由於活動很多,也不能確定哪些活動在被訪問,所以不建議使用timer。通過加一個快取key來實現,處理邏輯如下:
好處就是,只對有訪問的活動定時重新發起渲染。
四、新架構講解:
整理架構(不包含業務):
view工程職責:
a.直接從快取或者硬碟中獲取靜態html返回,如果沒有返回錯誤頁面。(檔案系統的存取效能比較低,超過 100ms級別,這裡沒有使用)
b.根據快取key2是否過期,判斷是否向engine重新發起渲染。(如果,你的專案外面介面都支援mq,這個 功能就不需要了)
engine工程職責:渲染活動頁面,把結果放到 硬碟、redis。
publish工程、mq 職責:頁面發生變化,向engine重新發起渲染。 具體的頁面邏輯,這裡不做講解
Engine工程的工作 就是當頁面內容發生變化時,重新渲染頁面,並將整頁內容放到redis,或者推送到硬碟。
2.view工程架構(redis 版)
View工程的工作,就是根據連結從redis中獲取頁面內容返回。
3.view工程架構 (硬碟 版)
兩個版本對比
a.Redis版
優點:接入簡單、 效能好,尤其是在大量頁面情況下,沒有效能抖動 。單個docker tps達到 700。
缺點:嚴重依賴京東redis服務,如果redis服務出現問題,所有頁面都無法訪問。
b.硬碟版
優點:不依賴任何其他外部服務,只要應用服務不掛、網路正常 就可以對外穩定服務。
在頁面數量不大的情況下,效能優越。單個docker tps達到 2000。
缺點:在頁面資料量大的情況下(系統的所有活動頁有xx個G左右),磁碟io消耗增加(這裡採用的java io,如果採用nginx+lua,io消耗應該會控制在10%以內)。
解決方案:
a. 對所有頁面訪問和儲存 採用url hash方式,所有頁面均勻分配到各個應用伺服器上。
b. 採用nginx+lua 利用nginx的非同步io,代替java io。
4.Openresty+硬碟版
現在通過nginx+lua做應用服務,所具有的高併發處理能力、高效能、高穩定性已經越來越受青睞。通過上述講解,view工程沒有任何業務邏輯。可以很輕易的就可以用lua實現,從redis或者硬碟獲取頁面,實現更高效的web服務。
通過測試對比,view工程讀本地硬碟的速度,比讀redis還要快(同一個頁面,讀redis是15ms,硬碟是8ms)。所以終極版架構我選擇用硬碟,redis做備份,硬碟讀不到時在讀redis。
這裡前置機的url hash是自己實現的邏輯,engine工程採用同樣的規則推送到view伺服器硬碟即可,具體邏輯這裡不細講。後面有時間再單獨做一次分享。
優點:具備硬碟版的全部優點,同時去掉tomcat,直接利用nginx高併發能力,以及io處理能力。各項效能、以及穩定性達到最優。
缺點:1、硬碟壞掉,影響訪問。2.方法監控,以及日誌列印,需使用lua指令碼重寫。
五:總結
無論是redis版、硬碟版、openresty+硬碟版,基礎都是頁面瀏覽與頁面渲染非同步化。
優勢:
1、所有業務邏輯都剝離到engine工程,新view工程理論上永遠無需上線。
2、災備多樣化(redis、硬碟、檔案系統),且更加簡單,外部介面或者服務出現問題後,切斷engine工程渲染,不再更新redis和硬碟即可。
3、新view工程,與業務邏輯完全隔離,不依賴外部介面和服務,大促期間,即便外部介面出現新能問題,或者有外部服務掛掉,絲毫不影響view工程正常訪問。
4、效能提升上百倍,從1000ms提升到10ms左右。詳見前面的效能截圖。
5、穩定性:只要view伺服器的網路還正常,可以做到理論上用不掛機。
6、大幅度節省伺服器資源,按此架構,4+20+30=54個docker足以支援10億級pv。(4個nginx proxy_cache、20個view,30個engine)
六:結束語
從事開發已有近10載,一直就像寄生蟲一樣吸取著網路上的資源。前段時間受“張開濤”大神所託,對活動系統新架構做了一次簡單整理分享給大家,希望能給大家帶來一絲幫助。第一次在網上做分享,難免有些沒有考慮周全的地方,以後會慢慢的多分享一些自己的心得,大家一起成長。最後再來點心靈雞湯。。。
轉載請標明出處: