來源:有贊技術團隊
鏈接:http://tech.youzan.com

隨着互聯網數據規模的爆炸式增長, 如何從海量的歷史, 實時數據中快速獲取有用的信息, 變得越來越有挑戰性. 一箇中等的電商平臺, 每天都要產生百萬條原始數據, 上億條用戶行爲數據. 一般來說, 電商數據一般有3種主要類型的數據系統:

1、關係型數據庫, 大多數互聯網公司會選用mysql作爲關數據庫的主選, 用於存儲商品, 用戶信息等數據. 關係型數據庫對於事務性非常高的OLTP操作(比如訂單, 結算等)支持良好.

2、hadoop生態, hadoop是數據倉庫主要的載體, 除了備份關係型數據庫的所有版本, 還存儲用戶行爲, 點擊, 曝光, 互動等海量日誌數據, hadoop對於數據分析, 數據挖掘等OLAP支持比關係型數據庫更加具有擴展性和穩定性.

3、搜索引擎, 以elasticsearch和solr爲代表. 搜索引擎是獲取信息最高效的途徑, 幾乎成爲各類網站, 應用的基礎標配設施(地位僅次於數據庫).

目前搜索引擎技術已經有非常成熟的開源解決方案, 最出名的ElasticSearch和Solr都是基於lucence的. 很多中小型互聯網公司搜索引擎都是基於這兩個開源系統搭建的, 但是即便如此, 一個搜索引擎團隊想把搜索引擎質量做到商用標準, 從系統熟悉, 服務搭建, 功能定製, 通常需要花費較長時間. 通用搜索引擎應用在互聯網商用搜索通常會遇到如下幾個問題:

1、搜索引擎與公司現有數據系統的集成. mysql 和 hadoop是電商的兩個主要數據載體, 搜索引擎在全量和增量建索引過程中必須和mysql或hadoop無縫集成, 才能發揮搜索引擎自身的實時性, 水平擴展性(性能與容量和機器數量成正比)等優勢.

2、商用搜索高度定製化與通用搜索引擎的矛盾. 商用搜索的問題有時候超越了搜索引擎本身解決的範圍, 比如商品的去重, 店鋪的去重需要很專業的搜索引擎使用技巧; 商品的權重, 用戶意圖的識別需要算法和模型的支持.

3、在不瞭解搜索引擎專業知識的前提下, 很難創建對性能友好的索引. 結果造成了通用搜索性能很差的假象.

筆者是有贊大數據架構師, 從自身的搜索實踐出發, 分享搜索引擎實際的架構和解決的問題.

有贊搜索引擎實踐分2篇, 第一篇是工程篇, 主要介紹搜索引擎的架構和性能優化方面的經驗; 第二篇是算法篇, 介紹有贊實際需要的搜索算法的問題和解決方案. 文章僅僅介紹一箇中型電商公司實際的使用現狀和筆者個人的經驗, 不代表搜索引擎最佳實踐方法, 也不代表可以適用所有的場景. 如果讀者有問題可以和筆者聯繫, 共同探討.

1. 技術架構

有贊搜索引擎基於分佈式實時引擎elasticsearch(ES). ES構建在開源社區最穩定成熟的索引庫lucence上, 支持多用戶租用, 高可用, 可水平擴展; 並有自動容錯和自動伸縮的機制. 我們同事還實現了es與mysql和hadoop的無縫集成; 我們自主開發了高級搜索模塊提供靈活的相關性計算框架等功能.

有贊搜索引擎實踐(工程篇)

2. 索引構建

互聯網索引的特點是實時性高, 數據量大. 時效性要求用戶和客戶的各種行爲能夠第一時間進入索引; 數據量大要求一個有效分佈式方案可以在常數時間內創建不斷增長的TB數量級索引.

實時索引我們採用面向隊列的架構, 數據首先寫入DB(或文件), 然後通過數據庫同步機制將數據流寫入kafka隊列. 這種同步機制和數據庫主從同步的原理相同, 主要的開源產品有mypipe和阿里推出的canal. es通過訂閱相應的topic實現實時建立索引.

如果數據源是文件, 則使用flume實時寫入Kafka.

另外一個索引問題是全量索引. 有如下幾個場景讓全量索引是一個必要過程: 1. 實時更新有可能會丟數據, 每次很少的丟失時間長了降低搜索引擎的質量. 週期性的全量更新是解決這個問題的最直接的方法;

2. 即使能夠保證實時更新, 業務的發展有可能有重新建索引的需求(比如增加字段, 修改屬性, 修改分詞算法等).

3. 很多搜索引擎是在業務開始後很久才搭建的, 冷啓動必須全量創建索引.

我們採用Hadoop-es利用hadoop分佈式的特性來創建索引. hadoop-es讓分佈式索引對用戶透明, 就像單機更新索引一樣. 一個是分佈式的數據平臺, 一個是分佈式搜索引擎, 如果能把這兩個結合就能夠實現分佈式的全量索引過程. Hadoop-es正式我們想要的工具.

有贊搜索引擎實踐(工程篇)

我們給出一個通過Hive sql創建索引的例子:

有贊搜索引擎實踐(工程篇)

系統把es映射成hive的一個外部表, 更新索引就像是寫入一個hive表一樣. 實際上所有分佈式問題都被系統透明瞭.

不建議從數據庫或文件系統來全量索引. 一方面這會對業務系統造成很大的壓力, 另一方面因爲數據庫和文件系統都不是真正分佈式系統, 自己寫程序保證全量索引的水平擴展性很容易出問題, 也沒有必要這麼做.

全量索引和增量索引的架構如下圖所示. 另外一點是hadoop也是訂閱kafka備份數據庫和日誌的. 我個人建議一個公司所有DB和文件都存儲在hadoop上, 這樣做起碼有2個好處: 1. hadoop上使用hive或者spark創建的數據倉庫爲大數據提供統一的操作接口.

2. hadoop數據相對於線上更加穩定, 可以作爲數據恢復的最後一個防線.

數據倉庫的話題不在本篇文章的討論範圍, 這裏只是簡單提一下.

有贊搜索引擎實踐(工程篇)

爲什麼我們選擇Kafka? Kafka 是一個以高吞吐著名的消息系統. Kafka開啓了日誌合併(log compaction)功能後, 可以永久保存每條消息. 每一條消息都有一個key, 正好對應數據庫的主鍵, kafka始終保存一個key最新的一條消息, 歷史版本會被垃圾回收掉. 有了這個特性, kafka不僅可以保存數據庫最新的快照, 而且可以實現實時更新的消息系統. 第一次同步的時候, 數據表中每行記錄都轉化成以主鍵爲key的消息進入kafka, 並且可以被任意數量的broker消費. 之後數據庫的每次更新(insert, updated, delete)都會被轉化成kafka的消息. 如果一行記錄頻繁被更改, kafka會識別這些重複的消息, 把舊的消息回收掉.

Kafka既保存數據庫最新的全量數據, 又提供實時數據流的這個特性爲架構的可維護性提供極大便捷. 如果你想從零掃描整個數據庫, 你只需要從開始消費這個kafka的topic即可完成, 當讀到topic末端, 自動獲得實時更新的特性.

Kakfa的另一個特性是支持從任意斷點讀取數據, 比如我們全量索引是從HDFS中讀取, 我們可以根據HDFS保存的數據的最後一條的時間戳, 直接切換到Kafka讀取之後的數據.

3. 高級搜索: 超越ES功能限制

高級搜索模塊(AS)在商業搜索引擎起到至關重要的作用. 在各大商業搜索引擎公司裏面AS已經成爲標配, 也是變更最爲頻繁的模塊.

AS在商業搜索引擎中主要起到如下作用:

1. 反向代理, 實現基於分片的分佈式搜索(實際上es有這個功能); 提供必要的容災支持

2. 提供插件化的相關性計算框架

3. 提供豐富的相關性庫, 比如query分析庫, query改寫庫, 排序庫, 過濾庫等.

4. 管理不同的搜索業務

有贊搜索引擎實踐(工程篇)

AS一個主要的功能是通過一個個業務插件來代表相應的搜索. 一個最簡單的插件只需要包含對應的ES search API, 它實際上就是一個配置項, 說明es的地址. 這樣AS就是一個純代理. 但是商業搜索的需求都是不是ES本身能夠支持的, 所以就需要根據需求寫相應的Query rewriter, rerank等算法插件. 這樣就實現了框架和業務分離, AS具有極強的擴展性和複用性.

AS另一個功能是提供通用算法庫, 實際上它只爲每種算法提供編程框架. 算法也是通過插件的方式加入算法庫的. 這種方法可以讓算法工程師抽象公共算法庫供業務方使用, 避免重新造輪子. 一個具體業務要麼使用已經存在的算法(並修改參數), 要麼自己實現算法.

有贊搜索引擎實踐(工程篇)

上圖是一個實例. 商品搜索和分銷搜索各自實現一個rerank的的算法, 同時都調用了系統提供的rerank1的算法庫, 並加入了自己特有的邏輯.

AS除了基本proxy功能外, 還提供基於query的cache功能用於應用級別的緩存. 內部有一個緩衝隊列, 防止出現雪崩現象. 下一節性能優化中會詳細說明.

4. ES性能優化

下面幾個小結, 我們寫了幾個我們遇到的性能優化場景.

4.1 使用應用級隊列防止雪崩

ES一個問題是在高峯期時候極容易發生雪崩. ES有健全的線程池系統來保證併發與穩定性問題. 但是在流量突變的情況下(比如雙十一秒殺)還是很容易發生癱瘓的現象, 主要的原因如下:

ES幾乎爲每類操作配置一個線程池; 只能保證每個線程池的資源使用時合理的, 當2個以上的線程池競爭資源時容易造成資源響應不過來.

ES沒有考慮網絡負載導致穩定的問題.

在AS裏我們實現了面向請求的全局隊列來保證穩定性. 它主要做了3件事情.

有贊搜索引擎實踐(工程篇)

根據業務把請求分成一個個slide, 每個slide對應一個隊列. 默認一個應用就是一個slide, 一個應用也可以區分不同的slide, 這樣可以保護一個應用內重要的查詢.

每個隊列配置一個隊列長度, 默認爲50.

每個隊列計算這個隊列的平均響應時間. 當隊列平均響應時間超過200ms, 停止工作1s, 如果請求溢出就寫入溢出日誌留數據恢復使用. 如果連續10次隊列平均響應時間超過500ms就報警, 以便工程師第一時間處理.

4.2 自動降級

應用級隊列解決雪崩問題有點粗暴, 如果一個應用本身查詢就非常慢, 很容易讓一個應用持續超時很久. 我們根據搜索引擎的特點編寫了自動降級功能.

比如商品搜索的例子, 商品搜索最基本的功能是布爾查詢, 但是還需要按照相關性分數和質量度排序等功能, 甚至還有個性化需求. 完成簡單的布爾查詢, ES使用bitsets操作就可以做到, 但是如果如果需要相關性分, 就必須使用倒排索引, 並有大量CPU消耗來計算分數. ES的bitsets比倒排索引快50倍左右.

對於有降級方案的slide, AS在隊列響應過慢時候直接使用降級query代替正常query. 這種方法讓我們在不擴容的情況下成功度過了雙十一的流量陡增.

4.3 善用filtered query

理解lucence filter工作原理對於寫出高性能查詢語句至關重要. 許多搜索性能優化都和filter的使用有關. filter使用bitsets進行布爾運算, quey使用倒排索引進行計算, 這是filter比query快的原因. bitsets的優勢主要體現在: 1. bitsetcache在內存裏面, 永不消失(除非被LRU).

2. bitsets利用CPU原生支持的位運算操作, 比倒排索引快個數量級

3. 多個bitsets的與運算也是非常的快(一個64位CPU可以同時計算64個DOC的與運算)

4. bitsets 在內存的存儲是獨立於query的, 有很強的複用性

5. 如果一個bitset片段全是0, 計算會自動跳過這些片段, 讓bitsets在數據稀疏情況下同樣表現優於倒排索引.

舉個例子:

有贊搜索引擎實踐(工程篇)

lucence處理這個query的方式是在倒排索引中尋找這三個term的倒排鏈 ,並使用跳指針技術求交, 在運算過程中需要對每個doc進行算分. 實際上tag和region對於算分並沒有作用, 他們充當是過濾器的作用.

這就是過濾器使用場景, 它只存儲存在和不存在兩種狀態. 如果我們把tag和region使用bitsets進行存儲, 這樣這兩個過濾器可以一直都被緩存在內存裏面, 這樣會快很多. 另外tag和region之間的求交非常迅速, 因爲64位機器可以時間一個CPU週期同時處理64個doc的位運算.

一個lucence金科玉律是: 能用filter就用filter, 除非必須使用query(當且僅當你需要算分的時候).

正確的寫法爲:

有贊搜索引擎實踐(工程篇)

lucence的filtered query會智能的先計算filter語句, 然後才計算query語句, 儘可能在進行復雜的倒排算法前減少計算空間.

4.3 其他性能優化

線上集羣關閉分片自動均衡. 分片的自動均衡主要目的防止更新造成各個分片數據分佈不均勻. 但是如果線上一個節點掛掉後, 很容易觸發自動均衡, 一時間集羣內部的數據移動佔用所有帶寬. 建議採用閒時定時均衡策略來保證數據的均勻.

儘可能延長refresh時間間隔. 爲了確保實時索引es索引刷新時間間隔默認爲1秒, 索引刷新會導致查詢性能受影響, 在確保業務時效性保證的基礎上可以適當延長refresh時間間隔保證查詢的性能.

除非有必要把all字段去掉. 索引默認除了索引每個字段外, 還有額外創建一個all的字段, 保存所有文本, 去掉這個字段可以把索引大小降低50%.

創建索引時候, 儘可能把查詢比較慢的索引和快的索引物理分離.

5. 小結

本文介紹了有贊搜索引擎的架構, 重點對索引創建機制, 高級搜索模塊的功能做了闡述, 最後列舉了幾個常見的性能優化的場景. 本文對es本身的優化寫的不多, 因爲es官網和其他的博客有很多es優化的意見, 本文就不在一一枚舉. 本文的主要目的是能夠對搭建商用電商搜索引擎給讀者一個一般性的建議.

擴展閱讀

前端做模糊搜索

電商系統之搜索系統

百度核心搜索面經

談談我與搜索引擎的故事

爲什麼程序員更喜歡用google搜索?因爲正經!

全文搜索引擎選ElasticSearch還是Solr?

相關文章