大型电商网站设计——商品详情页面静态化
分布式系統(tǒng)的cap理論首先把分布式系統(tǒng)中的三個特性進(jìn)行了如下歸納:
1.一致性? : 在分布式系統(tǒng)中的所有數(shù)據(jù)備份,在同一時刻是否是同樣的值(等同于所有節(jié)點訪問同一份最新的數(shù)據(jù)副本)
2.可用性(A) : 在集群中一部分節(jié)點故障后,集群整體是否還能響應(yīng)客戶端的讀寫請求(對數(shù)據(jù)更新具備高可用性)
3.分區(qū)容錯§ : 以實際效果而言,分區(qū)相當(dāng)于對通信的時限要求。系統(tǒng)如果不能在時限內(nèi)達(dá)成數(shù)據(jù)一致性,就意味著發(fā)生了分區(qū)的情況,必須就
當(dāng)前操作在C和A之間做出選擇。
應(yīng)用數(shù)據(jù)靜態(tài)化架構(gòu)高性能單頁web應(yīng)用
在電商網(wǎng)站中,單頁web是非常常見的一種形式,比如首頁,廣告頁等都屬于單頁應(yīng)用。這種頁面是由模板+數(shù)據(jù)組成,傳統(tǒng)的構(gòu)建方式一般通過靜態(tài)化實現(xiàn)。
而這種方式的靈活性不是很好,比如頁面模板部分變更了需要重新全部生成。因此最好能有一種實現(xiàn)方式是可以實時動態(tài)渲染的,以支持模板的多樣性。另外
也需要考慮如下問題:
1.動態(tài)化模板渲染支持
2.數(shù)據(jù)和模板的多版本化:生產(chǎn)版本,灰度版本和預(yù)發(fā)布版本
3.版本回滾問題
4.異常問題,假設(shè)渲染模板時遇到了異常情況如何處理
5.灰度發(fā)布問題,比如切20%量給灰度版本
6.預(yù)發(fā)布問題,目的是在正式版本測試數(shù)據(jù)和模板的正確性
1.整體架構(gòu)
靜態(tài)化單頁如圖,直接將生成的靜態(tài)頁推送到相關(guān)服務(wù)器即可。使用這種方式要考慮文件操作的原子性,即從老版本切換到新版本如何做到文件操作原子化。
而動態(tài)化方案的整體架構(gòu)如下,分為三大系統(tǒng):CMS系統(tǒng),控制系統(tǒng)和前端展示系統(tǒng)。
CMS系統(tǒng)
在cms系統(tǒng)中可以配置頁面的模板和數(shù)據(jù)。模板動態(tài)在cms系統(tǒng)中維護(hù),即模板不是一個靜態(tài)文件,而是存儲在cms中的一條數(shù)據(jù),最終發(fā)布到’發(fā)布數(shù)據(jù)存儲redis中,前端展示系統(tǒng)從redis中獲取該模板進(jìn)行渲染,從而前端展示系統(tǒng)更換了模板也不需要重啟,純動態(tài)維護(hù)模板數(shù)據(jù)。
原始數(shù)據(jù)存儲到’元數(shù)據(jù)存儲mysql’中即可,比如頻道頁一般需要前端訪問的url,分類,輪播圖,商品樓層數(shù)據(jù)等,這些數(shù)據(jù)按照相應(yīng)維度存儲在cms系統(tǒng)中。cms系統(tǒng)提供發(fā)布到’發(fā)布數(shù)據(jù)存儲redis’的控制。將cms系統(tǒng)中的原始數(shù)據(jù)和模板數(shù)據(jù)組裝成聚合數(shù)據(jù)(json存儲)同步到’發(fā)布數(shù)據(jù)存儲redis’,以便前端展示系統(tǒng)獲取進(jìn)行展示。此處提供3個按鈕:正式版本,灰度版本和預(yù)發(fā)布版本。
cms目前存在幾個問題:
1.用戶訪問諸如 http://channel.jd.com/fashion.html 怎么定位到對應(yīng)的聚合數(shù)據(jù)呢?我們可以在cms元數(shù)據(jù)中定義url作為key,如果沒有url,則用id
作為key,或者自動生成一個url。
2.多版本如何存儲呢?使用redis的Hash結(jié)構(gòu)存儲即可,key作為url,字段按照維度存儲:正式版本使用當(dāng)前時間戳存儲(這樣前端系統(tǒng)可以根據(jù)時間戳排序然后獲得
最新的版本),預(yù)發(fā)布版本使用’predeploy’作為字段,灰度版本使用’abVersion’作為字段即可,這樣就區(qū)分開了多版本。
3.灰度版本如何控制呢?可以通過控制系統(tǒng)的開關(guān)來控制如何灰度
4.如何訪問預(yù)發(fā)布版本呢?比如在url參數(shù)總是帶上predeploy=true,另外可以限定只有內(nèi)網(wǎng)可以訪問或者訪問時帶上訪問密碼,比如pwd=abc234
5.模板變更的歷史數(shù)據(jù)校驗問題?比如模板變更了,但是使用歷史數(shù)據(jù)渲染該模板會出現(xiàn)問題,即模板總是要兼容歷史數(shù)據(jù)的;此處的方案不存在這個問題,因為每次存儲的是當(dāng)時的模板快照,即數(shù)據(jù)快照和模板快照推送到’發(fā)布數(shù)據(jù)存儲redis’中。
3.前端展示系統(tǒng)
前端展示系統(tǒng)可以獲取當(dāng)前url,使用url作為key首先從本機’發(fā)布數(shù)據(jù)存儲redis’獲取數(shù)據(jù)。如果沒有數(shù)據(jù)或者異常則從主’發(fā)布數(shù)據(jù)存儲redis’獲取。如果主’發(fā)布數(shù)據(jù)
存儲redis’也發(fā)生了異常,那么會直接調(diào)用cms系統(tǒng)暴露的api直接從元數(shù)據(jù)存儲mysql中獲取數(shù)據(jù)進(jìn)行處理。
前端展示系統(tǒng)的偽代碼如下:
從上述代碼可知,模板和數(shù)據(jù)都是動態(tài)獲取的,然后使用動態(tài)獲取的模板和數(shù)據(jù)進(jìn)行渲染。
此處假設(shè)最新版本的模板和數(shù)據(jù)都有問題怎么辦?可以從流程上避免:
a)首先進(jìn)行預(yù)發(fā)布版本發(fā)布,測試人員嚴(yán)重沒問題后進(jìn)行下一步
b)接著發(fā)布灰度版本,在灰度的時候自動去掉cdn功能(即不設(shè)置頁面的緩存時間),發(fā)布驗證
c)最后發(fā)布正式版本,正式版本發(fā)布的前5分鐘內(nèi)是不設(shè)置頁面緩存的,這樣就可以防止發(fā)版時遇到問題,但是若問題版本已經(jīng)在cdn上,問題會影響到全部用戶,且無法快速回滾。
控制系統(tǒng)
控制系統(tǒng)是用于版本降級和灰度發(fā)布的,當(dāng)然也可以把這個功能放在cms系統(tǒng)中。
a)版本降級:假設(shè)當(dāng)前線上的版本遇到問題了,想要快速切換到上個版本,可以使用控制系統(tǒng)實現(xiàn),選擇其中一個歷史版本然后通知給前端展示系統(tǒng),使用url和當(dāng)前版本的字段即可,
這樣前端展示系統(tǒng)就可以自動切換到選中的那個版本;當(dāng)問題修復(fù)后,再刪除該降級配置切換回最新版本。
b)灰度發(fā)布:在控制系統(tǒng)控制哪些url需要灰度發(fā)布和灰度發(fā)布的比例,與版本降級類似,將相關(guān)的數(shù)據(jù)推送到前端展示系統(tǒng)即可,當(dāng)不想灰度發(fā)布時刪除相關(guān)數(shù)據(jù)。
數(shù)據(jù)和模板動態(tài)化
我們將數(shù)據(jù)和模板都進(jìn)行動態(tài)化存儲,這樣可以在cms進(jìn)行數(shù)據(jù)和模板的變更;實現(xiàn)了前端和后端人員的分離;前端開發(fā)人員進(jìn)行cms數(shù)據(jù)配置和模板開發(fā),而后端開發(fā)人員只進(jìn)行
系統(tǒng)的維護(hù)。另外,因為模板的動態(tài)存儲,每次發(fā)布新的模板不需要重啟前端展示系統(tǒng),后端開發(fā)人員更好的得到了解放。
模板和數(shù)據(jù)可以是一對多的關(guān)系,即一個模板可以被多個數(shù)據(jù)使用。假設(shè)模板發(fā)生變更后,我們可以批量推送模板關(guān)聯(lián)的數(shù)據(jù),首先進(jìn)行預(yù)發(fā)布版本的發(fā)布,由測試人員進(jìn)行驗證,驗證沒問題發(fā)布正式版本。
多版本機制
我們將數(shù)據(jù)和模板分為多版本后,可以實現(xiàn):
1.預(yù)發(fā)布版本:更容易讓測試人員在實際環(huán)境進(jìn)行驗證
2.灰度版本:只需要簡單的開關(guān),就可以進(jìn)行ab測試
3.正式版本:存儲多個歷史正式版本,假設(shè)最新的正式版本出現(xiàn)問題,可以非常快速的切換回之前的版本
應(yīng)用多級緩存模式支撐海量讀服務(wù)
多級緩存介紹
所謂的多級緩存,即在整個系統(tǒng)架構(gòu)的不同系統(tǒng)層級進(jìn)行數(shù)據(jù)緩存,以提升訪問效率
1.首先接入nginx將請求負(fù)載均衡到應(yīng)用nginx,此處常用的負(fù)載均衡算法是輪詢或者一致性哈希,輪詢可以使服務(wù)器的請求更加均衡,而一致性哈希可以提升應(yīng)用nginx的緩存命中率。
相對于輪詢,一致性哈希會存在單機熱點問題,一種解決辦法是熱點直接推送到接入層nginx,另一種辦法是設(shè)置一個閾值,當(dāng)超過閾值,改為輪詢。
2.接著應(yīng)用nginx讀取本地緩存,如果本地緩存命中則直接返回。應(yīng)用nging本地緩存可以提升整體的吞吐量,降低后端的壓力,尤其應(yīng)對熱點問題非常有效。本地緩存可以使用Lua Shared Dict,Nginx Proxy Cache(磁盤/內(nèi)存),Local Redis實現(xiàn)
3.如果nginx本地緩存沒有命中,則會讀取相應(yīng)的分布式緩存(如redis緩存,另外可以考慮主從架構(gòu)來提升性能和吞吐量),如果分布式緩存命中則直接返回相應(yīng)的數(shù)據(jù)(并回寫到nginx
本地緩存)
4.如果分布式緩存也沒有命中,則會回源到tomcat集群,在回源到tomcat集群時也可以使用輪詢或一致性哈希作為負(fù)載均衡算法
5.在tomcat應(yīng)用中,首先讀取本地堆緩存,如果有則直接返回(并會寫到主redis集群)
6.作為可選部分,如果步驟4沒有命中可以再嘗試一次讀取主redis集群,目的是防止從redis集群有問題時的流量沖擊
7.如果所有的緩存都沒有命中,只能查詢db或相關(guān)服務(wù)獲取數(shù)據(jù)并返回
8.步驟7返回的數(shù)據(jù)異步寫到主redis集群,此處可能有多個tomcat實例同時寫主redis集群,造成數(shù)據(jù)混亂。
應(yīng)用整體分了3部分的緩存:應(yīng)用nginx本地緩存,分布式緩存,tomcat堆緩存。每一層緩存都用來解決相關(guān)的問題,如應(yīng)用nginx本地緩存用來解決熱點緩存問題,分布式緩存用來減少訪問回源率,tomcat堆緩存用于防止緩存失效/崩潰之后的沖擊。
如何緩存數(shù)據(jù)
過期與不過期
對于緩存的數(shù)據(jù)我們可以考慮不過期緩存和帶過期時間緩存,什么場景應(yīng)該選擇哪種模式則需要根據(jù)業(yè)務(wù)和數(shù)據(jù)量等因素來決定。
a)不過期緩存場景一般思路如下:
業(yè)務(wù)邏輯:
1.開始事務(wù)
2.執(zhí)行sql
3.提交事務(wù)
4.寫緩存
使用cache-aside 模式,首先寫數(shù)據(jù)庫,如果成功,則寫緩存。這種場景下存在事務(wù)成功,緩存寫失敗但無法回滾事務(wù)的情況。另外,不要把寫緩存放在事務(wù)中,尤其寫分布式緩存,因為網(wǎng)絡(luò)抖動可能導(dǎo)致寫緩存響應(yīng)時間很慢,引起數(shù)據(jù)庫事務(wù)阻塞。如果對緩存數(shù)據(jù)一致性要求不是很高,數(shù)據(jù)量也不是很大,則可以考慮定期全量同步緩存。也有提到以下思路:先刪緩存,然后執(zhí)行數(shù)據(jù)庫事務(wù);不過這種操作對于如商品這種查詢非常頻繁的業(yè)務(wù)不適用,因為你在刪緩存的同時,已經(jīng)有另外一個系統(tǒng)讀緩存了,此時事務(wù) 還沒提交。當(dāng)然對于如用戶維度的業(yè)務(wù)是可以考慮的。
不過為了更好的解決以上多個事務(wù)的問題,可以考慮使用訂閱數(shù)據(jù)庫日志的架構(gòu),如使用canal訂閱mysql的binlog實現(xiàn)緩存同步。
對于長尾訪問的數(shù)據(jù),大多數(shù)數(shù)據(jù)訪問頻繁都很高的場景,若緩存空間足夠則可以考慮不過期緩存,如用戶,分類,商品,價格,當(dāng)緩存滿了可以考慮LRU機制驅(qū)逐老的緩存數(shù)據(jù)。
b)過期緩存機制,即采用懶加載,一般用于緩存其他系統(tǒng)的數(shù)據(jù)(無法訂閱變更消息或者成本很高),緩存空間有限,低頻熱點緩存等場景。常見的步驟是:首先讀取緩存,如果不命中
則查詢數(shù)據(jù),然后異步寫入緩存并設(shè)置過期緩存,設(shè)置過期時間,下次讀取將命中緩存。熱點數(shù)據(jù)經(jīng)常使用,即在應(yīng)用系統(tǒng)上緩存比較短的時間。這種緩存可能存在一段時間的數(shù)據(jù)
不一致,需要根據(jù)場景來決定如何設(shè)置過期時間。如庫存數(shù)據(jù)可以在前端應(yīng)用上緩存幾秒鐘,短時間的不一致是可以忍受的。
維度化緩存與增量緩存
對于電商系統(tǒng),一個商品可能拆成諸如基礎(chǔ)屬性,圖片列表,上下架,規(guī)格參數(shù),商品介紹等;如果商品變了,要把這些數(shù)據(jù)都更新一遍,那么整個更新成本(接口調(diào)用量和帶寬)很高。因此最好將數(shù)據(jù)進(jìn)行維度化并增量更新(只更新變更的部分)。尤其是上下架這種只是一個狀態(tài)變更,但是每天頻繁調(diào)用的,維度化后能減少服務(wù)很大的壓力。按照不同維度接收MQ進(jìn)行更新。
大value緩存
要警惕緩存中的大value,尤其是使用redis的時候。遇到這種情況時可以考慮使用多線程實現(xiàn)的緩存(如memcached)來緩存大value;或者對大value進(jìn)行壓縮;或者將value拆分為多個小value,客戶端再進(jìn)行查詢,聚合。
熱點緩存
對于那些訪問非常頻繁的熱點緩存,如果每次都去遠(yuǎn)程緩存系統(tǒng)中獲取,可能會因為訪問量太大導(dǎo)致遠(yuǎn)程緩存系統(tǒng)請求太多,負(fù)載過高或者帶寬過高等問題,最終可能會導(dǎo)致緩存響應(yīng)慢, 使客戶端超時。一種解決辦法是通過掛更多的從緩存,客戶端通過負(fù)載均衡機制讀取從緩存系統(tǒng)數(shù)據(jù)。不過也可以在客戶端所在的應(yīng)用/代理層本地存儲一份,從而避免訪問遠(yuǎn)程緩存,即使像庫存這種數(shù)據(jù),在有些應(yīng)用系統(tǒng)中也可以進(jìn)行幾秒的本地緩存,從而降低遠(yuǎn)程系統(tǒng)的壓力。
分布式緩存與應(yīng)用負(fù)載均衡
此處說的分布式緩存一般采用分片實現(xiàn),即將數(shù)據(jù)分散到多個實例或多臺服務(wù)器。算法一般采用取模和一致性哈希。如之前說的做不過期緩存機制可以考慮取模機制,擴(kuò)容時一般是新建一個集群;而對于可以丟失的緩存數(shù)據(jù)則可以采用一致性哈希,即使其中一個實例出問題只是丟失一小部分,對于分片實現(xiàn)可以考慮客戶端實現(xiàn),或者使用如Twemproxy中間件進(jìn)行代理(分片對 客戶端是透明的)。如果使用redis可以考慮使用redis-cluster分布式集群方案。
應(yīng)用負(fù)載均衡一般采用輪詢和一致性哈希,一致性哈希可以根據(jù)應(yīng)用請求的url或者url參數(shù)將相同的請求轉(zhuǎn)發(fā)到同一個節(jié)點;而輪詢即將請求均勻的轉(zhuǎn)發(fā)到每一個服務(wù)器。
整體流程如下:
1.首先請求進(jìn)入接入層nginx
2.根據(jù)負(fù)載均衡算法將請求轉(zhuǎn)發(fā)給應(yīng)用nginx
3.如果應(yīng)用nginx本地緩存命中,則直接返回數(shù)據(jù),否則讀取分布式緩存或者回源到tomcat集群。
輪詢的優(yōu)點是:應(yīng)用nginx的請求更加均勻,使得每個服務(wù)器負(fù)載基本均衡,不會因為熱點問題導(dǎo)致其中一臺服務(wù)器負(fù)載過重。
輪詢的缺點是:隨著應(yīng)用nginx服務(wù)器的增加,緩存的命中率會下降。
一致性哈希的優(yōu)點是:相同的請求都會轉(zhuǎn)發(fā)到同一臺服務(wù)器,命中率不會因為服務(wù)器的增加而降低。
一致性哈希的缺點是:因為相同的請求都會轉(zhuǎn)發(fā)到同一臺服務(wù)器,因此可能造成某臺服務(wù)器負(fù)載過重。
那么如何選擇:
a)負(fù)載低的時候選擇一致性哈希,比如普通商品訪問
b)熱點請求時降級一致性哈希為輪詢,比如京東首頁的商品訪問
當(dāng)然,某些場景是將熱點數(shù)據(jù)直接推送到接入層nginx,直接響應(yīng)給用戶,比如秒殺。
熱點數(shù)據(jù)與更新緩存
1.打擊全量緩存+主從
如圖所示,所有緩存都存儲在應(yīng)用本地,回源之后把數(shù)據(jù)更新到主redis集群,然后通過主從復(fù)制到其他從redis集群。緩存的更新可以采用懶加載或者訂閱消息進(jìn)行同步。
2.分布式緩存+應(yīng)用本地?zé)狳c
對于分布式緩存,我們需要在nginx+lua 應(yīng)用中進(jìn)行應(yīng)用緩存來減少redis集群的訪問沖擊,即首先查詢應(yīng)用本地緩存,如果命中則直接返回,如果沒有命中則接著查詢redis集群,回源到tomcat,然后將數(shù)據(jù)緩存到應(yīng)用本地。此處到應(yīng)用nginx的負(fù)載機制:正常情況下才有一致性哈希,如果某個請求類型訪問量突破了一定閾值,則自動降級為輪詢機制。另外對于一些秒殺活動之類的熱點我們是可以提前知道的,可以把相關(guān)的數(shù)據(jù)預(yù)先推送到接入層nginx并將負(fù)載均衡機制降級為輪詢。
另外可以考慮建立實時熱點發(fā)現(xiàn)系統(tǒng)來發(fā)現(xiàn)熱點:
1.接入層nginx將請求轉(zhuǎn)發(fā)給應(yīng)用nginx
2.應(yīng)用nginx首先讀取本地緩存,如果命中則直接返回,不命中會讀取分布式緩存,回源到tomcat集群
3.應(yīng)用nginx會將請求上報給實時熱點發(fā)現(xiàn)系統(tǒng)(如使用udp上報請求,或者將請求寫到kafka,或者使用flume訂閱本地nginx日志),上報給實時熱點發(fā)現(xiàn)系統(tǒng)后,它將進(jìn)行
熱點統(tǒng)計(可以考慮storm實時計算)
4.根據(jù)設(shè)計的閾值將熱點數(shù)據(jù)推送到應(yīng)用nginx本地緩存。
因為做了本地緩存,因此對數(shù)據(jù)一致性需要我們?nèi)タ紤],即何時失效或更新緩存:
1.如果可以訂閱數(shù)據(jù)變更消息,那么可以訂閱變更消息進(jìn)行緩存更新
2.如果無法訂閱或者成本比較高,并且對短暫的數(shù)據(jù)一致性要求不嚴(yán)格(比如在商品詳情頁看到的庫存,可以短暫不一致,只要保證下單時一致即可),那么可以設(shè)置合理的過期時間,過期后再查詢更新數(shù)據(jù)。
3.如果是秒殺之類的,可以訂閱活動開啟消息,將相關(guān)數(shù)據(jù)提取推送到前端應(yīng)用,并將負(fù)載機制降級為輪詢
4.建立實時熱點發(fā)現(xiàn)系統(tǒng)來對熱點進(jìn)行統(tǒng)一推送和更新
更新緩存與原子性
如果多個應(yīng)用同時操作一份數(shù)據(jù)可能造成緩存是臟數(shù)據(jù),解決的辦法有:
1.更細(xì)數(shù)據(jù)時使用更新時間戳或者版本對比,如果使用redis可以利用其單線程機制進(jìn)行原子化更新
2.使用如canal訂閱數(shù)據(jù)庫binlog,此時把mysql看成發(fā)布者,binlog是發(fā)布的內(nèi)容,canal看成消費者,canal訂閱binlog然后更新到redis。
3.將更新請求按照相應(yīng)的規(guī)則分散到多個隊列,然后每個隊列進(jìn)行單線程更新,更新時拉取最新的數(shù)據(jù)保存
4.用分布式鎖,更新前獲取相關(guān)的鎖。
緩存崩潰與快速恢復(fù)
當(dāng)我們使用分布式緩存的時候,應(yīng)該考慮如何對應(yīng)其中一部分緩存實例宕機的情況。當(dāng)數(shù)據(jù)可丟失的情況,我們可以選擇一致性哈希。
1.取模
對于取模機制如果其中一個實例故障,如果摘除此實例將導(dǎo)致大量緩存不命中,瞬間大流量可能導(dǎo)致后端db/服務(wù)出現(xiàn)問題。對于這種情況可以采用主從機制來避免實例故障的問題,即其中一個實例故障可以用從/主頂上來。但是取模機制下如果增加一個節(jié)點將導(dǎo)致大量緩存不命中,所以一般是建立另一個集群,然后把數(shù)據(jù)遷移到新集群,然后把流量遷移過去。
2.一致性哈希
對于一致性哈希機制如果其中一個實例故障,摘除此實例只影響一致性哈希的部分緩存不命中,不會導(dǎo)致瞬間大量回源到后端db/服務(wù),但是也會產(chǎn)生一定影響。
快速恢復(fù)
- 主從機制,做好冗余
- 如果因為緩存導(dǎo)致應(yīng)用可用性已經(jīng)下降可以考慮:部分用戶降級,然后慢慢減少降級量后臺通過worker預(yù)熱緩存數(shù)據(jù)。
也就是說如果整個緩存集群故障,而且沒有備份,那么只能去慢慢將緩存重建。為了讓部分用戶還是可用的,可以根據(jù)系統(tǒng)的承受能力,通過降級方案讓一部分用戶先用起來,將這些用戶的相關(guān)緩存重建。另外根據(jù)后臺worker進(jìn)行緩存數(shù)據(jù)的預(yù)熱。
構(gòu)建需求響應(yīng)式億級商品詳情頁
商品詳情頁架構(gòu),主要包括三部分:
1.商品詳情頁系統(tǒng) : 負(fù)責(zé)靜態(tài)部分
2.商品詳情頁同意服務(wù)系統(tǒng) : 負(fù)責(zé)動態(tài)部分
3.商品詳情頁動態(tài)服務(wù)系統(tǒng) : 給內(nèi)網(wǎng)其他系統(tǒng)提供一些數(shù)據(jù)服務(wù)
商品詳情頁前端結(jié)構(gòu)
前端展示可以分為這么幾個維度:商品維度(標(biāo)題,圖片,屬性等),主商品維度(商品介紹,規(guī)格參數(shù)),分類維度,商家維度,店鋪維度等,另外還有一些實時性要求比較高的如實時價格,實時促銷,廣告詞,配送至,預(yù)售等都是通過異步加載的。
單品頁技術(shù)架構(gòu)發(fā)展
單品頁技術(shù)架構(gòu)經(jīng)歷了4個階段的發(fā)展:
架構(gòu)1.0
IIS+C#+SQL Server ,最原始的架構(gòu),直接調(diào)用商品庫獲取響應(yīng)的數(shù)據(jù),扛不住時加一層memcached來緩存數(shù)據(jù)。
架構(gòu)2.0
該方案采用了靜態(tài)化技術(shù),按照商品維度生成靜態(tài)化html。
主要思路:
1.通過MQ得到變更通知
2.通過java worker調(diào)用多個依賴系統(tǒng)生成詳情頁html
3.通過rsync同步到其他機器
4.通過nginx直接輸出靜態(tài)頁
5.接入層負(fù)責(zé)負(fù)載均衡
該方案的缺點是:
1.假設(shè)只有分類,面包屑變了,那么所有相關(guān)的商品都要重新刷
2.隨著商品數(shù)量的增加,rsync會成為瓶頸
3.無法迅速響應(yīng)一些頁面需求變更,大部分都是通過JavaScript動態(tài)改變頁面元素
隨著商品數(shù)量的增加,這種架構(gòu)的存儲容量達(dá)到了瓶頸,而且按照商品維度生成整個頁面會存在如分類維度變更就要全部刷一遍這個分類下所有信息的問題,因此又改版了
一次按照尾號路由到多臺機器。
主要思路:
1.容量問題通過按照商品尾號做路由分散到多臺機器,按照自營商品單獨一臺,第三方商品按照尾號分散到11臺。
2.按維度生成html片段,而不是一個大的html
3.通過nginx SSI 合并片段并輸出
4.接入層負(fù)責(zé)負(fù)載均衡
5.多機房部署也無法通過rsync同步,而是使用部署多套相同的架構(gòu)來實現(xiàn)
該方案的缺點是:
1.碎片文件太多,導(dǎo)致無法rsync
2.機械盤做SSI合并時,高并發(fā)時性能差,此時我們還沒嘗試SSD
3.模板如果要變更,數(shù)億商品需要數(shù)天才能刷完
4.到達(dá)容量瓶頸時,我們會刪除一部分靜態(tài)化商品,然后通過動態(tài)渲染輸出,動態(tài)渲染輸出在高峰時會導(dǎo)致依賴系統(tǒng)壓力大,扛不住
5.還是無法迅速響應(yīng)一些業(yè)務(wù)需求
我們的痛點是:
1.之前的架構(gòu)的問題存在容量問題,很快就會出現(xiàn)無法全量靜態(tài)化問題,所以還是需要動態(tài)渲染;不過對于全量靜態(tài)化渲染還可以通過分布式文件系統(tǒng)解決,沒嘗試。
2.最主要的問題是隨著業(yè)務(wù)的發(fā)展,無法迅速滿足變化的需求
架構(gòu)3.0
我們要解決的問題:
1.能迅速響應(yīng)瞬變的需求和其他需求
2.支持各種垂直化頁面改版
3.頁面模塊化
4.ab測試
5.高性能,水平擴(kuò)容
6.多機房多活,異地多活
主要思路:
1.數(shù)據(jù)變更還是通過mq
2.數(shù)據(jù)異構(gòu)worker得到通知,然后按照一些維度進(jìn)行數(shù)據(jù)存儲,存儲到數(shù)據(jù)異構(gòu)JIMDB集群(JIMDB:redis+持久化引擎),存儲的數(shù)據(jù)都是未加工的原子化數(shù)據(jù),如商品基本信息,商品擴(kuò)展屬性,商品其他一些信息,商品規(guī)格參數(shù),分類,商家信息等。
3.數(shù)據(jù)異構(gòu)worker存儲成功后,會發(fā)送一個mq給數(shù)據(jù)同步worker,數(shù)據(jù)同步worker也可以叫做數(shù)據(jù)聚合worker,按照相應(yīng)的維度聚合數(shù)據(jù)存儲到相應(yīng)的JIMDB集群;三個維度信息:基本信息(基本信息+擴(kuò)展屬性等的一個聚合),商品介紹(PC版,移動版),其他信息(分類,商家等維度,數(shù)據(jù)量小,直接redis存儲)
4.前端展示分為2個:商品詳情頁和商品介紹,使用nginx+lua技術(shù)獲取數(shù)據(jù)并渲染模板輸出。
另外我們目前的架構(gòu)的目標(biāo)不僅僅是為商品詳情頁提供數(shù)據(jù),只要是key-value結(jié)構(gòu)獲取而非關(guān)系結(jié)構(gòu)的我們都可以提供服務(wù),我們叫做動態(tài)服務(wù)系統(tǒng)。該動態(tài)服務(wù)分為前端和后端,即公網(wǎng)還是內(nèi)網(wǎng),如目前該動態(tài)服務(wù)為列表頁,商品對比,微信單品頁,總代等提供相應(yīng)的數(shù)據(jù)來滿足和支撐其業(yè)務(wù)。
3.詳情頁架構(gòu)設(shè)計原則
1.數(shù)據(jù)閉環(huán)
數(shù)據(jù)閉環(huán)即數(shù)據(jù)的自我管理,或者說是數(shù)據(jù)都在自己的系統(tǒng)里維護(hù),不依賴于任何其他系統(tǒng),去依賴化。這樣的好處是別人抖動跟我沒關(guān)系。
a)數(shù)據(jù)異構(gòu)是數(shù)據(jù)閉環(huán)的第一步,將各個依賴系統(tǒng)的數(shù)據(jù)拿過來,按照自己的要求存儲起來
b)數(shù)據(jù)原子化,數(shù)據(jù)異構(gòu)的數(shù)據(jù)是原子化數(shù)據(jù),這樣未來我們可以對這些數(shù)據(jù)進(jìn)行再加工再處理而想要變化的需求
c)數(shù)據(jù)聚合,將多個原子化的數(shù)據(jù)聚合為一個大json數(shù)據(jù),這樣前端展示只需要一次get,當(dāng)然要考慮系統(tǒng)架構(gòu),比如我們使用的redis改造,redis又是單線程系統(tǒng),我們需要
部署更多的redis來支持更高的并發(fā),另外存儲的值要盡可能的小。
d)數(shù)據(jù)存儲,我們使用JIMDB,redis持久化存儲引擎,可以存儲超過內(nèi)存N倍的數(shù)據(jù)量,我們目前一些系統(tǒng)是redis+LMDB引擎的存儲,是配合ssd進(jìn)行存儲;另外我們使用hash tag
機制把相關(guān)的數(shù)據(jù)哈希到同一個分片,這樣mget時不需要跨分片合并。
我們目前的異構(gòu)數(shù)據(jù)是鍵值結(jié)構(gòu)的,用于按照商品維度查詢,還有一套異構(gòu)是關(guān)系結(jié)構(gòu)的,用于關(guān)系查詢使用。
數(shù)據(jù)維度化
對于數(shù)據(jù)應(yīng)該按照維度和作用進(jìn)行維度化,這樣可以分離存儲,進(jìn)行更有效的存儲和使用。
拆分系統(tǒng)
將系統(tǒng)拆分為多個子系統(tǒng)雖然增加了復(fù)雜度,但是可以得到更多的好處,比如數(shù)據(jù)異構(gòu)系統(tǒng)存儲的數(shù)據(jù)是原子化數(shù)據(jù),這樣可以按照一些維度對外提供服務(wù);而數(shù)據(jù)同步系統(tǒng)存儲的 是聚合數(shù)據(jù),可以為前端展示提供高性能的讀取。前端展示系統(tǒng)分離為商品詳情頁和商品介紹,可以減少互相影響;目前商品介紹系統(tǒng)還提供其他的一些服務(wù),比如全站異步腳本服務(wù)。
worker無狀態(tài)化+任務(wù)化
worker無狀態(tài)化+任務(wù)化,可以幫助系統(tǒng)做水平擴(kuò)展。
1.數(shù)據(jù)異構(gòu)和數(shù)據(jù)同步worker無狀態(tài)化設(shè)計,這樣可以水平擴(kuò)展
2.應(yīng)用雖然是無狀態(tài)的,但是配置文件是有狀態(tài)的,每個機房一套配置,這樣每個機房只讀取當(dāng)前機房數(shù)據(jù)
3.任務(wù)多隊列化,等待隊列,排重隊列,本地執(zhí)行隊列,失敗隊列
4.隊列優(yōu)先級化,分為:普通隊列,刷數(shù)據(jù)隊列,高優(yōu)先級隊列,例如一些秒殺商品會走高優(yōu)先級隊列保證快速執(zhí)行。
5.副本隊列,當(dāng)上線后業(yè)務(wù)出現(xiàn)問題時,修正邏輯可以回放,從而修復(fù)數(shù)據(jù);可以按照比例如固定大小隊列或者小時隊列設(shè)計
6.在設(shè)計消息時,按照維度更新,比如商品信息變更和商品上下架分離,減少每次變更接口的調(diào)用量,通過聚合worker去做聚合。
異步化+并發(fā)化
我們系統(tǒng)使用了大量的異步化,通過異步化機制提升并發(fā)能力。首先使用了消息異步化進(jìn)行系統(tǒng)解耦,通過消息通知變更,然后再調(diào)用相應(yīng)接口獲取相關(guān)數(shù)據(jù);之前老系統(tǒng)使用同步推送機制,這種方式系統(tǒng)是緊耦合的,出問題需要聯(lián)系各個負(fù)責(zé)人重新推送還要考慮失敗重試機制。數(shù)據(jù)更新異步化,更新緩存時同步調(diào)用服務(wù),然后異步更新緩存。可使得任務(wù)并行化。
商品數(shù)據(jù)來源有多處,但是可以并發(fā)調(diào)用聚合,經(jīng)過這種方式我們可以把原先串行1s的時間提示到300ms內(nèi)。異步請求合并,一次請求調(diào)用就能拿到所有的數(shù)據(jù)。前端服務(wù)異步化/聚合,實時價格,實時庫存異步化,使用如線程或協(xié)程機制將多個可并發(fā)的服務(wù)聚合。異步化還有一個好處是可以對異步請求做合并,原來的n個調(diào)用可以合并為一次,還可以做請求的排重。
多級緩存化
1.瀏覽器緩存,當(dāng)頁面之間來回跳轉(zhuǎn)時走local cache,或者打開頁面時拿著Last-Modified去CDN驗證是否過期,減少來回傳輸?shù)臄?shù)據(jù)量2.cdn緩存,用戶去離自己最近的cdn節(jié)點拿數(shù)據(jù),而不是全部回源到北京機房獲取數(shù)據(jù),提升訪問性能3.服務(wù)端應(yīng)用本地緩存,我們使用nginx+lua架構(gòu),使用HttpLuaModule模塊的shared dict做本地緩存(reload不丟失)或內(nèi)存級Proxy Cache,從而減少帶寬。另外我們還可以使用一致性哈希(如商品編號/分類)做負(fù)載均衡內(nèi)部對url重寫提升命中率我們對mget做了優(yōu)化,如取商品其他維度數(shù)據(jù),分類,面包屑,商家等差不多8個維度數(shù)據(jù),如果每次mget獲取性能差而且數(shù)量很大,30kb以上;而這些數(shù)據(jù)緩存半小時也是沒有問題的,那么我們可以設(shè)計為先讀local cache,然后把不命中的再回源到remote cache獲取,這個優(yōu)化減少了一半的remote cache 流量。4.服務(wù)端分布式緩存,我們使用內(nèi)存+ssd+JIMDB持久化存儲動態(tài)化
數(shù)據(jù)獲取動態(tài)化,商品詳情頁:按維度獲取數(shù)據(jù),如商品基本數(shù)據(jù),其他數(shù)據(jù);而且還可以根據(jù)數(shù)據(jù)屬性,按需做邏輯,比如虛擬商品需要自己定制的詳情頁,那么我們就可以跳轉(zhuǎn)走,比如全球購的需要走jd.hk域名,也是沒問題。模板渲染實時化,支持隨時變更模板需求。重啟應(yīng)用秒級化,使用nginx+lua架構(gòu),重啟速度快,重啟不丟失共享字段緩存數(shù)據(jù);需求上線速度化,因為我們使用了nginx+lua架構(gòu),可以快速上線和重啟應(yīng)用,不會產(chǎn)生抖動;另外lua本身是一個腳本語言,我們也嘗試把代碼如何版本化存儲,直接內(nèi)部驅(qū)動lua代碼更新而不需要重啟nginx。彈性化
我們所有的應(yīng)用業(yè)務(wù)都接入了docker容器,存儲還是物理機。我們會制作一些基礎(chǔ)鏡像,把需要的軟件打成鏡像,這樣就不用每次去運維那安裝部署軟件了。未來可以支持自動擴(kuò)容,比如按照cpu或帶寬自動擴(kuò)容機器。降級開關(guān)
推送服務(wù)器推送降級開關(guān),開關(guān)集中化維護(hù),然后通過推送機制推送到各個服務(wù)器。可降級的多級讀服務(wù)為:前端數(shù)據(jù)集群 -> 數(shù)據(jù)異構(gòu)集群 -> 動態(tài)服務(wù)(調(diào)用依賴系統(tǒng)),這樣就可以保證服務(wù)質(zhì)量,假設(shè)前端數(shù)據(jù)集群壞了一個磁盤,還可以回源到數(shù)據(jù)異構(gòu)集群獲取數(shù)據(jù)。開關(guān)前置化,如nginx->tomcat,在nginx做開關(guān),請求就到不了后端,減少后端壓力。可降級的業(yè)務(wù)線程池隔離。我們可以把處理過程分解為一個個的事件。通過這種將請求劃分事件的方式我們可以進(jìn)行更多的控制。如,我們可以為不同的業(yè)務(wù)再建立不同的線程池進(jìn)行控制:即我們只依賴tomcat線程池進(jìn)行請求的解析,對請求的處理可以交給我們自己的線程池去完成。這樣tomcat線程池就不是我們的瓶頸了。通過使用這種異步化事件模型,我們可以提高整體的吞吐量,不讓慢速的A業(yè)務(wù)影響到其他業(yè)務(wù)。慢的還是慢,但是不影響其他業(yè)務(wù)。多機房多活
應(yīng)用無狀態(tài),通過在配置文件中配置各個資方的數(shù)據(jù)集群來完成數(shù)據(jù)讀取。數(shù)據(jù)集群采用一主三從結(jié)構(gòu),防止當(dāng)一個機房掛了,另一個機房壓力大產(chǎn)生抖動。多種壓測方案
線下壓測使用apache ab,apache Jmeter,這種方式是固定url壓測,一般通過訪問日志收集一些url進(jìn)行壓測,可以簡單壓測單機峰值吞吐量,但是不能作為最終的壓測結(jié)果,因為這種壓測會存在熱點問題。線上壓測,可以使用Tcpcopy直接把線上流量導(dǎo)入到壓測服務(wù)器,這種方式可以壓測出機器的性能,還可以把流量放大,也可以使用nginx+lua協(xié)程機制把流量分發(fā)到多臺壓測服務(wù)器,或者直接在頁面埋點,讓用戶壓測,此種壓測方式可以不給用戶返回內(nèi)容。遇到的一些問題
SSD性能差
使用SSD做KV存儲時發(fā)現(xiàn)磁盤IO非常低。初步懷疑:線上系統(tǒng)用的是消費級硬盤。RAID卡設(shè)置,write back 和 write through策略。實驗用的是dd壓測,嚴(yán)格測試應(yīng)該用FIO等工具。鍵值存儲選型壓測
對于存儲選型,嘗試過LevelDB,RocksDB,BeansDB,LMDB,Riak等,最終選擇LMDB。數(shù)據(jù)量大時JIMDB同步不動
JIMDB 數(shù)據(jù)同步時要 dump 數(shù)據(jù),ssd磁盤容量用了50%以上,dump到同一塊磁盤容量不足。切換主從之前的架構(gòu)是一主二從(主機房一主一從,備機房一從)切換到備機房時,只有一個主服務(wù),讀寫壓力大時有抖動,因此改造為一主三從。分片配置
之前的架構(gòu)分片邏輯分散到多個子系統(tǒng)的配置文件中,切換時需要很多系統(tǒng),解決方案:1.引入Twemproxy中間件,我們使用本地部署的 Twemproxy 來維護(hù)分片邏輯2.使用自動部署系統(tǒng)推送配置和重啟應(yīng)用,重啟之前暫停mq消費保證數(shù)據(jù)一致性3.用unix domain socket減少連接數(shù)和端口占用不釋放啟動不了服務(wù)的問題模板元數(shù)據(jù)存儲HTML
起初不確定lua做邏輯和渲染模板性能如何,就盡量減少for,if/else之類的邏輯;通過java worker組成html片段存儲到j(luò)imdb,html片段會存儲諸多問題,假設(shè)未來變了也是需要全量刷出的,因此存儲的內(nèi)容最好就是元數(shù)據(jù)。因此通過線上不斷壓測,最終jimb只存儲元數(shù)據(jù),lua做邏輯和渲染,邏輯代碼在3000行以上,模板代碼1500以上,其中包括大量for,if/else語句,目前渲染性能可以接受庫存接口訪問量600w/分鐘,因為是詳情頁展示的數(shù)據(jù),緩存幾秒是可以接受的,因此開啟nginx proxy cache 來解決該問題。目前用的是nginx+lua架構(gòu)改造服務(wù),數(shù)據(jù)過濾,url重寫等在nginx層完成。 通過url重寫+一致性哈希負(fù)載均衡,不怕隨機url,一些服務(wù)提升了10%以上的緩存命中率。微信接口調(diào)用鏈暴增
通過訪問日志發(fā)現(xiàn)某ip頻繁抓取,而且按照商品編號遍歷,但是會有一些不存在的編號,解決方案是:1.讀取kv存儲的部分不限流2.回源到服務(wù)接口的進(jìn)行請求限流,保證服務(wù)質(zhì)量開啟nginx proxy cache 性能不升反降
開啟nginx proxy cache 后,性能下降,而且過一段時間內(nèi)存使用率達(dá)到98%,解決方案是:1.對于內(nèi)存占有率高的問題是內(nèi)核問題,內(nèi)核使用lru機制,本身不是問題,不過可以修改內(nèi)核參數(shù):sysctl -w vm.extra_free_bytes=6436787sysctl -w vm.vsf_cache_pressure=100002.使用proxy cache在機械盤上性能差可以通過tmpfs緩存或nginx共享字典緩存元數(shù)據(jù),或者使用ssd,目前使用內(nèi)存文件系統(tǒng)。配送至’讀服務(wù)因依賴太多,響應(yīng)時間偏慢
'配送至'服務(wù)每天有數(shù)十億調(diào)用量,響應(yīng)時間慢,解決方案是:1.串行獲取變并發(fā)獲取,這樣一些服務(wù)可以并發(fā)調(diào)用2.預(yù)取依賴數(shù)據(jù)回傳網(wǎng)絡(luò)抖動時,返回502錯誤
Twemproxy 配置的timeout時間太長,之前設(shè)置5s,而且沒有分別對連接,讀,寫設(shè)置超時。后來我們減少了超時時間,內(nèi)網(wǎng)設(shè)置150ms以內(nèi),當(dāng)超時時訪問動態(tài)服務(wù)。機器流量太大
雙11期間,服務(wù)器網(wǎng)卡流量到了400Mbps,cpu30%左右。原因是我們所有的壓縮都在接入層完成,因此接入層不再傳入相關(guān)請求頭到應(yīng)用,隨著流量的增大,接入層壓力過大。因此我們把壓縮下方到各個業(yè)務(wù)應(yīng)用,添加了相應(yīng)的請求頭,nginx gzip壓縮級別在2~4吞吐量最高,應(yīng)用服務(wù)器流量降了差不多5倍。總結(jié)
以上是生活随笔為你收集整理的大型电商网站设计——商品详情页面静态化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python excel centos_
- 下一篇: ASP.NET(c#)常用类函数