蘇寧11.11:搜尋引擎Solr在蘇寧易購商品評價系統中的應用
背景說明
蘇寧易購商品評價系統主要提供商品維度評價數量聚合、評價列表展示功能,併為其他業務系統提供商品評價資料支撐服務。功能涉及對億級資料的數量聚合、排序、多維度查詢等複雜的業務場景,關係型資料庫的索引為B-Tree結構,適合數值區分度或離散度高的資料,而評價系統中單個商品評價可以達到數十萬條,相同星級的評價數則為億級,故不適宜使用關係型資料庫。解決此類海量資料準實時聚合的技術選型有以倒排表作為索引結構的Solr和Elasticsearch(底層都是Apache Lucene)搜尋引擎服務,還有以bitmap作為底層索引結構的實時分析統計資料庫Druid,但Druid只是支援資料統計功能並不儲存原始資料,無法滿足商品評價類的功能需求。系統建設之初對團隊對Solr更為熟悉,故在Solr和Elasticsearch兩者之間選擇的Solr作為商品評價索引資料儲存服務。
評價系統架構
蘇寧易購商品評價系統架構如下:
圖2-1
應用服務模組
根據蘇寧易購技術規範要求,應用系統架構劃分為前中後臺三個主模組:
- 前臺:為web、app提供頁面或介面服務,接入蘇寧統一認證系統Passport;
- 中臺:通過蘇寧自研RSF遠端服務框架對前臺服務和其他業務系統提供功能介面服務;
- 後臺:業務系統運營管理功能和任務批處理。
資料儲存層
- SQL/">MySQL:儲存和查詢登入使用者發表的評價,具備ACID特性,按照使用者ID分庫分表,根據業務要求和儲存容量限制只保留指定時間內的評價資料
- Redis叢集:蘇寧在開源Redis中介軟體基礎上自研的支援分散式叢集、熔斷、橫向擴容的高可用、海量快取服務架構,快取商品評價數量和列表資料,承載使用者端的資料訪問請求,降低對業務系統的訪問壓力
- HBase:海量評價內容儲存
- Solr:評價索引儲存和查詢服務
外圍系統
- 商品中心:通過MQ(非同步)和RSF(同步)接收儲存商品主資料如商品資訊、類目資訊、店鋪資訊、商品關係資訊等
- 訂單中心:待評商品主要來源於訂單中心通過MQ下發的訂單資訊和訂單狀態通知,並由評價系統提供訂單待評狀態查詢RSF介面,在訂單頁面展示商品評價發表入口
- 搜尋產品線:通過MQ下發商品評價數量到搜尋產品線,為商品搜尋排序計算提供資料支援
基礎設施
- 分散式任務排程系統(UTS):對各業務系統中的定時任務進行集中管理和排程,解決了叢集環境下任務併發執行的控制,讓業務系統從繁瑣的技術細節中釋放出來,並提供了對任務排程執行情況的監控和異常簡訊告警、重試機制,且提供跨業務系統的後繼任務排程。
- 集中配置管理(SCM):基於Zookeeper自研的應用配置資訊集中統一管理和實時推送服務框架。
- 訊息中介軟體:由於歷史原因,蘇寧內部有多套MQ中介軟體,包括IBM MQ,Kafka和自研的WindQ。IBM MQ已經不建議使用,正在逐漸廢棄;Kafka在大資料場景下使用較多;業務系統推薦使用WindQ,解決了解決多活場景下訊息路由,提供訊息傳送重試熔斷、線上動態擴縮容、順序消費、異構MQ橋接等特性。
監控
- 日誌:改造Solr日誌模組,接入蘇寧統一日誌框架,便於異常問題分析
- 主機:接入Zabbix監控體系,監控主機和應用健康狀態
- 系統:接入蘇寧雲跡系統,監控Solr請求響應TP90/99等指標
Solr介紹
搜尋引擎基礎原理
搜尋引擎的索引稱為反向索引,俗稱倒排表,把文字分詞得到字典,儲存字典項與文件ID(Lucene自身doc ID而非應用端文件ID)的關係,在查詢時根據字典查詢到倒排表文件ID集合,再進行交併集操作即可得到結果,其主要結構是字典域、索引域和欄位儲存域。反向索引如下圖:
圖3-1
在Solr4.0之後為了滿足排序和關鍵字聚合的需求場景,Lucene提供了DocValues特性,又被稱為正排表,使用列式儲存儲存文件ID和字典項的關係,不再使用之前FieldValue Cache機制,提升效能並降低對記憶體的使用和虛擬機器Full GC的風險。在Elasticsearch中所有欄位都是預設開啟此特性,但在Solr中需要使用者進行顯式配置生效。DocValues儲存結構可分為兩種,一種是原值,一種是字典項ID。
- 單數值型和原始位元組型欄位,其基本結構包含欄位原值的long[]陣列,如帶有三個數值的文件DocValues結構:
doc[0] = 1005 doc[1] = 1006 doc[2] = 1005
- 其他可索引型別欄位,其結構也是一個long[]陣列,只不過陣列中的值為字典項ID(可能有多個),例如有3個字元型欄位的文件:
doc[0] = "aardvark" doc[1] = "beaver" doc[2] = "aardvark"
假如“aardvark”在字典表中ID為0,”beaver”在字典中ID為1,則實際結構為:
doc[0] = 0 doc[1] = 1 doc[2] = 0
字典表資料為:
term[0] = "aardvark" term[1] = "beaver"
注:字典表所有term都是有序的,故DocValues可以直接用於排序。
Solr&Lucene架構體系
Solr是一個高效能、基於Lucene的開源全文搜尋服務,提供豐富的查詢語言,同時實現了可配置、可擴充套件並對查詢效能提供了優化,提供完善的功能管理介面;在Lucene基礎上對易用性和可用性進行了大量封裝如Schema配置化、請求分發處理機制、外掛化機制、資料匯入、分散式、監控指標採集等,架構體系如下:
圖3-2
蘇寧商品評價系統結合自身業務特點採用了Solr主從節點和聚合查詢節點的組合架構,而不是通用的Solr Cloud架構,主要考慮到兩點:
- 橫向擴容機制完全可控,根據商品編碼單調遞增特性,可以隨時擴充新節點,節點路由機制由業務系統控制。
- 單獨搭建聚合查詢節點,在跨多個Solr節點查詢時由聚合節點實現聚合查詢功能,特別是在聚合商品評價列表查詢場景下,降低對資料節點的效能壓力。
Solr特性應用
多維度數量聚合
商品詳情頁展示商品/供應商維度稽核通過的好中差評數量、標籤數量、個性化評價項等數量,使用的是Solr facet機制。
圖4-1
-
單個欄位facet如好中差評資料量(上圖方框處)涉及星級欄位直接設定facet.field引數
/solr /select?q=*:*&wt=json&indent=true&facet=true&facet.field=qualityStar
-
多條件facet如有圖評價和已追評(上圖圓圈處)評價數量,應使用facet.query機制把每個facet.qery查詢當做單獨facet值,一次性查詢出結果而不是發起多次普通總數查詢
/solr/select?q=*:*&wt=json&indent=true&facet=true&facet.query=picVideoFlag:1&facet.query=againReviewFlag:1
多維度列表查詢
根據查詢條件直接查詢評價ID即可,限制查詢欄位並支援分頁,因商品評價無需計算文件相似度且開啟快取可提升查詢效能,故使用filter query替代query作為查詢條件:
/solr/select?q=*:*&fq=(auditStat:0+OR+auditStat:1)&start=0&rows=10&fl=commodityReviewId
需要注意的是filter cache是以單個filter query為鍵快取結果,可以根據業務需求要求拆成多個filter query,設定不同快取策略以提升快取利用率。
分組查詢
需要說明的是Solr分組關鍵字group的含義與關係型資料庫的group by中的group並不相同,在Solr中指的是對查詢的結果文件根據指定的欄位進行分組,而非關係型資料庫對欄位分組,如蘇寧商品評價需要展示每個評價的第一條商家回覆內容,通過Solr查詢商家回覆並根據評價ID進行分組後取第一條記錄即可(查詢條件為評價ID列表),引數說明如下:
表4-1
自定義排序
在使用Solr對結果集排序一般有兩種方式:
-
一是在寫入索引時單獨欄位儲存數值,在查詢時使用sort欄位直接排序即可,優點是簡單,但調整排序規則時需要重建所有索引。
-
二是函式查詢機制,在Solr標準查詢解析器中使用solr內建的函式,指定sort欄位為函式內容或在DisMax中指定bf引數都可以滿足業務需求,例如sort=div(popularity,price)desc,score desc格式,div函式表示popularity和price兩個欄位相除。
大部分排序都使用第二種方式,但此方式對效能會有影響,特別是在涉及到多個欄位時且參與排序的文件數較多時,其內部執行過程需要獲取每個匹配doc的欄位值進行計算,即使欄位開啟docValues特性對儲存、IO和記憶體空間也有一定壓力。
提升函式排序的效能也有兩種方式:
- 一是粗略的計算排序值,或寫索引時即有一定順序,查詢時進行截斷,只返回一定數量的文件,再進行二次排序,即通常電商搜尋中粗排和精排過程。
- 二是把涉及到的排序欄位合併到一個欄位並開啟docValues特性,例如【評價時間|會員等級|評價星級|內容長度|圖片個數|內容質量分|圖片質量分|人工稽核權重】此種格式,並編寫自定義函式解析計算這個組合欄位的排序值;繼承org.apache.Lucene.queries.function.ValueSource實現排序值計算,繼承org.apache.solr.search.ValueSourceParser實現此函式的解析建立即可。這也是蘇寧商品評價系統準備採用的方式。
facet.method引數的選擇
Lucene欄位級facet有三種機制:
- enum :遍歷欄位下所有的字典項,對所有字典項的倒排文件ID和查詢結果文件ID進行交集運算得到結果
- fc :根據查詢結果的每個文件ID從Field Cache中查詢欄位值,計算每個欄位值的次數得到結果
- fcs :類似於fc,不同點是基於Lucene中每個段單獨的Field Cache
facet.method引數指定在對欄位進行聚合時使用上述哪種演算法,根據上述實現機制說明可知對於值區分度低的欄位,適合選擇enum機制;對於有大量不同值的欄位合適使用fc機制,若Solr開啟了近實時搜尋(NRT)特性,fcs機制則是更好的選擇,因其在生成新索引段時舊索引段快取不用重新載入。
分散式聚合查詢
蘇寧易購商品電商模型中SPU和SKU可以在商品上架時根據產品特性和銷售情況動態調整組合,查詢SPU商品評價時就會出現跨多個節點的場景,需要支援跨節點的分散式查詢並對聚合結果集進行二次處理,如數量的累加、列表二次排序和分頁等,為此搭建空資料的Solr節點作為聚合查詢節點。業務方根據SPU和SKU關係得出節點編號和節點的地址,改寫查詢請求新增shards引數轉發給聚合節點即可,示例如下: /solr/select?
q=*:*&wt=json&indent=true&facet=true&facet.query=picVideoFlag:1&facet.query=againReviewFlag:1&shards=solr1:8080/,solr2:8080/,solr3:8080/
此特性原理是使用多執行緒查詢多個Solr節點,在記憶體中對結果進行合併,故使用此特性也有一定限制:
- 列表分頁須限制大頁碼,防止列表合併分頁時記憶體溢位或產生Full GC問題,效能急劇下降並可能拖垮服務節點
- 相關性排序和facet特性可能與單節點查詢結果可能不同
- 注意設定執行緒池數量和HTTP超時時長
- 文件在所有節點必須唯一,不允許重複
- 不支援pivot facet和join特性
效能調優
-
開啟filter cache,以查詢條件為key,文件ID列表為值儲存在cache中,可以大幅提升數量聚合效能,但需要注意不適合欄位值離散度較高的查詢,否則產生大量key會把cache中的熱點快取替換出去,快取命中率下降反而影響效能,可以通過設定cache=false或在函式查詢中設定fq={! cache=false}引數遮蔽當前請求的cache機制
-
文件數較多且命中率低,需要關閉documentCache和queryResultCache
-
設定newSearcher和firstSearcher兩個Listener的查詢語句,對重新開啟的IndexSearcher進行預熱
-
JVM使用CMS GC策略,並設定CMSInitiatingOccupancyFraction值為60,保證在主從同步時Solr有足夠的堆記憶體,降低因CMS GC記憶體碎片導致Full GC的風險。
配置示例如下:
<query> <maxBooleanClauses>2000</maxBooleanClauses> <filterCache class="solr.FastLRUCache" size="8192" initialSize="4096" autowarmCount="80%" /> <enableLazyFieldLoading>true</enableLazyFieldLoading> <listener event="newSearcher" class="solr.QuerySenderListener"> <arr name="queries"> <!-- 預載入查詢 --> </arr> </listener> <listener event="firstSearcher" class="solr.QuerySenderListener"> <arr name="queries"> <!-- 預載入查詢 --> </arr> </listener> <useColdSearcher>false</useColdSearcher> <maxWarmingSearchers>1</maxWarmingSearchers> </query>
展望
目前在CDN快取和Redis快取失效情況部分請求的壓力還是回源到Solr,對於Solr的直接依賴較大,且更新索引時偶爾還是會觸發Full GC,影響業務的穩定性。下一步需要對快取架構重新設計,設計快取非同步更新和熔斷機制,限制使用者請求直接落到Solr服務上,提升介面QPS、系統穩定性和評價展示生效實時性。
另外還在規劃引入Elasticsearch作為後臺業務索引,前後臺索引進行分離,降低前後臺耦合度和系統風險,更便於業務開發。
作者介紹
胡正林,蘇寧易購IT總部消費者平臺研發中心高階架構師,十餘年軟體開發經驗,熟悉大型分散式高併發系統架構和開發,目前主要負責易購各系統架構優化與大促保障工作。