京東的商品評論目前已達到數(shù)十億條,每天提供的服務(wù)調(diào)用也有數(shù)十億次,而這些數(shù)據(jù)每年還在成倍增長,而數(shù)據(jù)存儲是其中最重要的部分之一,接下來就介紹下京東評論系統(tǒng)的數(shù)據(jù)存儲是如何設(shè)計的。
整體數(shù)據(jù)存儲包括基礎(chǔ)數(shù)據(jù)存儲、文本存儲、數(shù)據(jù)索引、數(shù)據(jù)緩存幾個部分。
基礎(chǔ)數(shù)據(jù)存儲
基礎(chǔ)數(shù)據(jù)存儲使用mysql,因用戶評論為文本信息,通常包含文字、字符等,占用的存儲空間比較大,為此mysql作為基礎(chǔ)數(shù)據(jù)庫只存儲非文本的評論基礎(chǔ)信息,包括評論狀態(tài)、用戶、時間等基礎(chǔ)數(shù)據(jù),以及圖片、標簽、點贊等附加數(shù)據(jù)。而不同的數(shù)據(jù)又可選擇不同的庫表拆分方案,參考如下:
評論基礎(chǔ)數(shù)據(jù)按用戶ID進行拆庫并拆表;
圖片及標簽處于同一數(shù)據(jù)庫下,根據(jù)商品編號分別進行拆表;
其它的擴展信息數(shù)據(jù),因數(shù)據(jù)量不大、訪問量不高,處理于同一庫下且不做分表即可。
因人而異、因系統(tǒng)而異,根據(jù)不同的數(shù)據(jù)場景選擇不同存儲方案,有效利用資源的同時還能解決數(shù)據(jù)存儲問題,為高性能、高可用服務(wù)打下堅實基礎(chǔ)。
文本存儲
文本存儲使用了mongodb、hbase,選擇nosql而非mysql,一是減輕了mysql存儲壓力,釋放msyql,龐大的存儲也有了可靠的保障;二是nosql的高性能讀寫大大提升了系統(tǒng)的吞吐量并降低了延遲。存儲的升級過程嘗試了cassandra、mongodb等分布式的nosql存儲,cassandra適用于寫多讀少的情況,而 mongodb也是基于分布式文件存儲的數(shù)據(jù)庫,介于關(guān)系型數(shù)據(jù)庫與非關(guān)系型數(shù)據(jù)庫之間,同時也是內(nèi)存級數(shù)據(jù)庫,mongo寫性能不及 cassandra,但讀寫分離情況下讀性能相當不錯,因此從應(yīng)用場景上我們選擇了mongodb。mongodb確實不錯,也支持了系統(tǒng)穩(wěn)定運行了好幾年。
但從今后的數(shù)據(jù)增長、業(yè)務(wù)擴增、應(yīng)用擴展等多方面考慮,hbase才是最好的選擇,它的存儲能力、可靠性、可擴展性都是毋庸置疑的。選擇了 hbase,只需要根據(jù)評論ID構(gòu)建Rowkey,然后將評論文本信息進行存儲,查詢時只需要根據(jù)ID便能快速讀取評論的文本內(nèi)容,當然也可將評論的其它字段信息進行冗余存儲,這樣根據(jù)評論ID讀取評論信息后不用再從mysql進行讀取,減少數(shù)據(jù)操作,提升查詢性能。
數(shù)據(jù)索引
京東的評論是以用戶和商品兩個維度進行劃分的。對于用戶而言,用戶需要發(fā)表評論、上傳曬圖、查看自己的評論等,因此mysql數(shù)據(jù)庫中只要根據(jù)用戶ID對評論數(shù)據(jù)進行拆庫拆表進行存儲,便能解決用戶數(shù)據(jù)讀寫問題。而對于商品而言,前臺需要將統(tǒng)計商品的評論數(shù)并將所有評論展示出來,后臺需根據(jù)評論的全字段進行檢索同時還帶模糊查詢,而評論數(shù)據(jù)是按userId進行庫表拆分的,現(xiàn)在要按商品去獲取評論,顯然當前的拆分庫是無法實現(xiàn)的。起初考慮過根據(jù)商品編號再進行拆庫拆表,但經(jīng)過多層分析后發(fā)現(xiàn)行不通,因為再按商品編號進行拆分,得再多加一倍機器,硬件成本非常高,同時要保持用戶及商品兩維度的分庫數(shù)據(jù)高度一致,不僅增加了系統(tǒng)維護成本及業(yè)務(wù)復(fù)雜度,同時也無法解決評論的數(shù)據(jù)統(tǒng)計、列表篩選、模糊查詢等問題,為此引入了全文檢索框架solr(前臺)/elasticsearch(后臺)進行數(shù)據(jù)索引。
數(shù)據(jù)索引其實就是將評論數(shù)據(jù)構(gòu)建成索引存儲于索引服務(wù)中,便于進行評論數(shù)據(jù)的模糊查詢、條件篩選及切面統(tǒng)計等,以彌補以上數(shù)據(jù)存儲無法完成的功能。京東評論系統(tǒng)為此使用了solr/elasticsearch搜索服務(wù),它們都是基于Lucene的全文檢索框架,也是分布式的搜索框架(solr4.0后增加了solr cloud以支持分布式),支持數(shù)據(jù)分片、切面統(tǒng)計、高亮顯示、分詞檢索等功能,利用搜索框架能有效解決前臺評論數(shù)據(jù)統(tǒng)計、列表篩選問題,也能支持后臺系統(tǒng)中的關(guān)鍵詞顯示、多字段檢索及模糊查詢,可謂是一舉多得。
搜索在構(gòu)建索引時,屬性字段可分為存儲字段與索引字段,存儲字段在創(chuàng)建索引后會將內(nèi)容存儲于索引文檔中,同時也會占用相應(yīng)的索引空間,查詢后可返回原始內(nèi)容,而索引字段創(chuàng)建索引后不占用索引空間也無法返回原始內(nèi)容,只能用于查詢,因此對于較長的內(nèi)容建議不進行存儲索引。
評論搜索在構(gòu)建索引時,主鍵評論ID的索引方式設(shè)置為存儲,其它字段設(shè)置為索引,這樣不僅減少索引文件的存儲空間,也大大提升了索引的構(gòu)建效率與查詢性能。當然,在使用搜索框架時,業(yè)務(wù)數(shù)據(jù)量比較小的也可選擇將所有字段進行存儲,這樣在搜索中查詢出結(jié)果后將不需要從數(shù)據(jù)庫上查詢其它信息,也減輕了數(shù)據(jù)庫的壓力。
為了更好地應(yīng)對前后臺不同的業(yè)務(wù)場景,搜索集群被劃分為前臺搜索集群和后臺搜索集群。
前臺搜索集群根據(jù)商品編號進行索引數(shù)據(jù)分片,用于解決評論前臺的評論數(shù)統(tǒng)計、評論列表篩選功能。評論數(shù)統(tǒng)計,如果使用常規(guī)數(shù)據(jù)庫進行統(tǒng)計時,需要進行sql上的group分組統(tǒng)計,如果只有單個分組統(tǒng)計性能上還能接受,但京東的評論數(shù)統(tǒng)計則需要對1到5分的評論分別進行統(tǒng)計,分組增加的同時隨著統(tǒng)計量的增加數(shù)據(jù)庫的壓力也會增加,因此在mysql上通過group方式進行統(tǒng)計是行不通的。而使用solr的切面統(tǒng)計,只需要一次查詢便能輕松地統(tǒng)計出商品每個分級的評論數(shù),而且查詢性能也是毫秒級的。切面統(tǒng)計用法如下:
評論列表,只需根據(jù)條件從搜索中查詢出評論ID集合,再根據(jù)評論ID到mysql、Hbase中查詢出評論的其它字段信息,經(jīng)過數(shù)據(jù)組裝后便可返回前臺進行展示。
后臺搜索集群,評論后臺系統(tǒng)需要對評論進行查詢,其中包括關(guān)鍵詞高亮顯示、全字段檢索、模糊查詢等,為此solr/elasticsearch都是個很好的選擇,目前使用elasticsearch。
未來也計劃將前臺搜索集群切換為elasticsearch。
數(shù)據(jù)緩存
面對數(shù)十億的數(shù)據(jù)請求,直接擊穿到mysql、搜索服務(wù)上都是無法承受的,所以需要對評論數(shù)據(jù)進行緩存,在此選擇了高性能緩存redis,根據(jù)不同的業(yè)務(wù)數(shù)據(jù)進行集群劃分,同時采用多機房主從方式部署解決單點問題,這樣只需要對不同的緩存集群進行相應(yīng)的水平擴展便能快速提升數(shù)據(jù)吞吐能力,也有效地保證了服務(wù)的高性能、高可用。
當然,緩存設(shè)計時還有很多細節(jié)可以進行巧妙處理的,如:
當用戶新發(fā)表一條評論,要實現(xiàn)前臺實時展示,可以將新增的評論數(shù)向首屏列表緩存中追加最新的評論信息;
評論數(shù)是讀多寫少,這樣就可以將評論數(shù)持久化到redis當中,只有當數(shù)據(jù)進行更新時通過異步的方式去將緩存刷新即可;評論數(shù)展示可通過nginx+lua的方式提供服務(wù),服務(wù)請求無需回源到應(yīng)用上,不僅提升服務(wù)性能,也能減輕應(yīng)用系統(tǒng)的壓力;
對于評論列表,通常訪問的都是第一屏的數(shù)據(jù),也就是第一頁的數(shù)據(jù),可以將第一頁的數(shù)據(jù)緩存到redis當中,有數(shù)據(jù)更新時再通過異步程序去更新;
對于秒殺類的商品,評論數(shù)據(jù)可以結(jié)合本地緩存提前進行預(yù)熱,這樣當秒殺流量瞬間涌入的時候也不會對緩存集群造成壓力;通過減短key長度、去掉多余屬性、壓縮文本等方式節(jié)省內(nèi)存空間,提高內(nèi)存使用率。
數(shù)據(jù)容災(zāi)與高可用
引入了這么多的存儲方案就是為了解決大數(shù)據(jù)量存儲問題及實現(xiàn)數(shù)據(jù)服務(wù)的高可用,同時合理的部署設(shè)計與相應(yīng)的容災(zāi)處理也必須要有的。以上數(shù)據(jù)存儲基本都使用多機房主從方式部署,各機房內(nèi)部實現(xiàn)主從結(jié)構(gòu)進行數(shù)據(jù)同步。如圖:
mysql集群數(shù)據(jù)庫拆庫后需要對各分庫進行多機房主從部署,系統(tǒng)應(yīng)用進行讀寫分離并根據(jù)機房進行就近調(diào)用,當主機房數(shù)據(jù)庫出現(xiàn)故障后將故障機房的數(shù)據(jù)操作都切換到其它機房,待故障排除后再進行數(shù)據(jù)同步與流量切換。
使用主從機房部署的方式所有數(shù)據(jù)更新操作都要在主庫上進行,而當主機房故障是需要通過數(shù)據(jù)庫主從關(guān)系的重建、應(yīng)用重新配置與發(fā)布等一系列操作后才能解決流量切換,過程較為復(fù)雜且影響面較大,所以這是個單點問題,為此實現(xiàn)數(shù)據(jù)服務(wù)多中心將是我們下一個目標。
多中心根據(jù)特定規(guī)則將用戶分別路由到不同機房進行數(shù)據(jù)讀寫,各機房間通過數(shù)據(jù)總線進行數(shù)據(jù)同步,當某一機房出現(xiàn)故障,只需要一鍵操作便能快速地將故障機房的用戶流量全部路由到其它機房,實現(xiàn)了數(shù)據(jù)的多寫多活,也進一步實現(xiàn)了服務(wù)的高可用。數(shù)據(jù)多中心如下:
hbase集群目前使用的是京東的公有集群,實現(xiàn)了雙機房主備部署,主集群出現(xiàn)故障后自動將流量切換到備用集群,而當hbase整個集群故障時還可對其進行降級,同步只寫入緩存及備用存儲mongo,待集群恢復(fù)后再由后臺異步任務(wù)將數(shù)據(jù)回寫到hbase當中。
搜索集群根據(jù)商品編號進行索引數(shù)據(jù)分片多機房主從部署,并保證至少3個從節(jié)點并部署于多個機房當中,當主節(jié)點出現(xiàn)故障后從這些從節(jié)點選取其中一個作為新的主提供服務(wù)。集群主節(jié)點只提供異步任務(wù)進行索引更新操作,從節(jié)點根據(jù)應(yīng)用機房部署情況提供索引查詢服務(wù)。
Redis緩存集群主從部署仍是標配,主節(jié)點只提供數(shù)據(jù)的更新操作,從節(jié)點提供前臺緩存讀服務(wù),實現(xiàn)緩存數(shù)據(jù)的讀寫分離,提升了緩存服務(wù)的處理能力。當主節(jié)點出現(xiàn)故障,選取就近機房的一個從節(jié)點作為新主節(jié)點提供寫服務(wù),并將主從關(guān)系進行重新構(gòu)建。任何一從節(jié)點出現(xiàn)故障都可通過內(nèi)部的配置中心進行一鍵切換,將故障節(jié)點的流量切換到其它的從節(jié)點上。
總結(jié)
整體數(shù)據(jù)架構(gòu)并沒有什么高大上的設(shè)計,而且整體數(shù)據(jù)架構(gòu)方案也是為了解決實際痛點和業(yè)務(wù)問題而演進過來的。數(shù)據(jù)存儲方案上沒有最好的,只有最適合的,因此得根據(jù)不同的時期、不同的業(yè)務(wù)場景去選擇合適的設(shè)計才是最關(guān)鍵的,大家有什么好的方案和建議可以相互討論與借鑒,系統(tǒng)的穩(wěn)定、高性能、高可用才是王道。