一般情況下,類似關(guān)系型數(shù)據(jù)庫中影響返回結(jié)果的方式袭异,在elasticsearch中也存在钳垮,包括sort惑淳、size等。但是這都是簡(jiǎn)單粗暴的對(duì)返回結(jié)果進(jìn)行直接處理饺窿,很大的可能會(huì)影響返回文檔的相關(guān)度歧焦。比如,通過sort操作對(duì)搜索結(jié)果按時(shí)間排序肚医,這時(shí)排在前面的文檔很可能相關(guān)度非常小绢馍,而相關(guān)度大的文檔則因?yàn)闀r(shí)間排序被放在了下面。這顯然不是我們想要的結(jié)果肠套。elasticsearch提供了方法舰涌,允許我們用除了搜索之外的其他因素影響返回文檔的順序,同時(shí)兼顧文檔的相關(guān)度你稚。
簡(jiǎn)單粗暴地評(píng)分
首先先說一下elasticsearch的搜索評(píng)分邏輯瓷耙。
查詢的權(quán)重基于三個(gè)因素:詞頻、逆向文檔頻率和字段長(zhǎng)度歸一值刁赖。
- 詞頻:查詢?cè)~在該文檔中出現(xiàn)的頻率搁痛。頻率越高,權(quán)重越高宇弛。
- 逆向文檔頻率:查詢?cè)~在所有文檔中出現(xiàn)的頻率鸡典。頻率越高,權(quán)重越低枪芒〕箍觯可以降低日常使用的高頻率詞的權(quán)重。
- 字段長(zhǎng)度歸一值:查詢字段的長(zhǎng)度舅踪。字段長(zhǎng)度越長(zhǎng)纽甘,查詢?cè)~權(quán)重越高,反之越低抽碌。
不同詞對(duì)搜索結(jié)果的影響基本取決于以上三個(gè)因素贷腕,這里不列出詳細(xì)的計(jì)算公式。
如何影響文檔評(píng)分
首先,影響文檔評(píng)分的操作推薦是查詢的時(shí)候進(jìn)行泽裳,這樣靈活性更好瞒斩。這里不介紹簡(jiǎn)單的評(píng)分提升或者降低,直接介紹elasticsearch中控制文檔評(píng)分的終極武器:function_scor涮总。
function_score
function_score是query結(jié)構(gòu)的一個(gè)子集胸囱,它對(duì)每一個(gè)符合查詢的文檔應(yīng)用一個(gè)或一組函數(shù),達(dá)到影響甚至替換原始查詢?cè)u(píng)分的目的瀑梗。這個(gè)操作可以很方便的實(shí)現(xiàn)復(fù)雜的查詢邏輯烹笔。
Elasticsearch 預(yù)定義了一些函數(shù):
- weight:為每個(gè)文檔應(yīng)用一個(gè)簡(jiǎn)單而不被規(guī)范化的權(quán)重提升值:當(dāng) weight 為 2 時(shí),最終結(jié)果為 2 * _score 抛丽。
- field_value_factor:使用這個(gè)值來修改 _score 谤职,如將 popularity 或 votes (受歡迎或贊)作為考慮因素。
- random_score:為每個(gè)用戶都使用一個(gè)不同的隨機(jī)評(píng)分對(duì)結(jié)果排序亿鲜,但對(duì)某一具體用戶來說允蜈,看到的順序始終是一致的。
- 衰減函數(shù) —— linear 蒿柳、 exp 饶套、 gauss:將浮動(dòng)值結(jié)合到評(píng)分 _score 中,例如結(jié)合 publish_date 獲得最近發(fā)布的文檔垒探,結(jié)合 geo_location 獲得更接近某個(gè)具體經(jīng)緯度(lat/lon)地點(diǎn)的文檔妓蛮,結(jié)合 price 獲得更接近某個(gè)特定價(jià)格的文檔。
- script_score:如果需求超出以上范圍時(shí)圾叼,用自定義腳本可以完全控制評(píng)分計(jì)算蛤克,實(shí)現(xiàn)所需邏輯。
以上函數(shù)開箱即用夷蚊,一般情況下咖耘,elasticsearch提供的函數(shù)就可以滿足需求,如果有特殊要求撬码,也可以使用最后一個(gè)script_score自己寫控制評(píng)分腳本。但是script_score對(duì)性能有較大影響版保,能不用就不用呜笑。
下面簡(jiǎn)單說明幾個(gè)需求,來看看elasticsearch是如何通過function_score實(shí)現(xiàn)的彻犁。
點(diǎn)擊數(shù)影響評(píng)分
如果想要在原來搜索的基礎(chǔ)上叫胁,加入點(diǎn)擊數(shù)的影響,即將點(diǎn)擊數(shù)高的文檔放到搜索結(jié)果靠上的位置汞幢,但是搜索的評(píng)分仍然是主要的依據(jù)驼鹅。
PUT /blogposts/post/1
{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 6
}
這是一篇文章的文檔,其中保存了點(diǎn)擊數(shù)。這里可以通過field_value_factor實(shí)現(xiàn)相關(guān)需求输钩。
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes"
}
}
}
}
function_score嵌入了一個(gè)query查詢中豺型,然后內(nèi)部又設(shè)定了一個(gè)query,內(nèi)部的query查詢是主查詢买乃。在function_score內(nèi)部還有一個(gè)field_value_factor函數(shù)姻氨,這個(gè)函數(shù)會(huì)對(duì)符合每一個(gè)主查詢的文檔使用。每個(gè)文檔的最終評(píng)分都會(huì)進(jìn)行如下的計(jì)算:new_score = old_score * number_of_votes
默認(rèn)的field_value_factor計(jì)算是線性的剪验,votes
的原始值直接用來計(jì)算肴焊,這通常不會(huì)產(chǎn)生好的結(jié)果。一般情況下功戚,通過對(duì)數(shù)計(jì)算取代現(xiàn)行計(jì)算娶眷,可以取得更平滑的結(jié)果。
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p"
}
}
}
}
將field_value_factor設(shè)為對(duì)數(shù)計(jì)算啸臀,計(jì)算公式:new_score = old_score * log(1 + number_of_votes)
届宠。
field_value_factor提供了眾多參數(shù),設(shè)置相關(guān)計(jì)算的各種參數(shù)壳咕,這里不再一一列舉席揽。如果想要細(xì)致的調(diào)整搜索結(jié)果的話,參考官方文檔進(jìn)行谓厘。
對(duì)查詢結(jié)果進(jìn)行隨機(jī)評(píng)分
作為網(wǎng)站的所有者幌羞,總會(huì)希望讓廣告有更高的展現(xiàn)率。在當(dāng)前查詢下竟稳,有相同評(píng)分 _score 的文檔會(huì)每次都以相同次序出現(xiàn)属桦,為了提高展現(xiàn)率,在此引入一些隨機(jī)性可能會(huì)是個(gè)好主意他爸,這能保證有相同評(píng)分的文檔都能有均等相似的展現(xiàn)機(jī)率聂宾。
我們想讓每個(gè)用戶看到不同的隨機(jī)次序,但也同時(shí)希望如果是同一用戶翻頁瀏覽時(shí)诊笤,結(jié)果的相對(duì)次序能始終保持一致系谐。這種行為被稱為一致隨機(jī)。
random_score
函數(shù)會(huì)輸出一個(gè) 0 到 1 之間的數(shù)讨跟,當(dāng)種子 seed
值相同時(shí)纪他,生成的隨機(jī)結(jié)果是一致的。例如:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"random_score": {
"seed": "userID"
}
}
}
}
使用每個(gè)用戶的id作為seed
傳入晾匠,可以使每個(gè)用戶的隨機(jī)保持一致茶袒,同時(shí)在不同用戶之間保持不同的隨機(jī)性。
時(shí)間凉馆、空間上的“越近越好”
一般來說薪寓,搜索要求具有時(shí)間相關(guān)性亡资。也就是說,用戶只想看到時(shí)間較近的文檔向叉,而時(shí)間較遠(yuǎn)的文檔锥腻,就算相關(guān)度較高,用戶也不想看到植康】跆空間上也有相關(guān)的性質(zhì),比如以某個(gè)點(diǎn)為中心销睁,周圍一定距離以內(nèi)的文檔排在返回結(jié)果的前面供璧,而超過一定距離的文檔就算相關(guān)度較高也排在后面。elasticsearch提供了衰減函數(shù)冻记,可以對(duì)文檔的相關(guān)性按某個(gè)維度進(jìn)行衰減睡毒。
這里不能直接使用sort。因?yàn)槿绻苯邮褂胹ort冗栗,會(huì)讓相關(guān)度較低的文檔排在前面演顾,維度近了但是相關(guān)度很差,達(dá)不到相關(guān)度較高隅居、同時(shí)某個(gè)維度較 近 的需求钠至。
elasticsearch提供了三個(gè)衰減函數(shù),分別是linear胎源、exp和gauss(線性棉钧、指數(shù)和高斯函數(shù)),它們可以操作數(shù)值涕蚤、時(shí)間以及經(jīng)緯度地理坐標(biāo)點(diǎn)這樣的字段(一般衰減也沒有用字符串做衰減的)宪卿。所有三個(gè)函數(shù)都能接受以下參數(shù):
- origin:中心點(diǎn) 或字段可能的最佳值,落在原點(diǎn) origin 上的文檔評(píng)分 _score 為滿分 1.0 万栅。
- scale:衰減率佑钾,即一個(gè)文檔從原點(diǎn) origin 下落時(shí),評(píng)分 _score 改變的速度烦粒。(例如休溶,每 £10 歐元或每 100 米)。
- decay:從原點(diǎn) origin 衰減到 scale 所得的評(píng)分 _score 扰她,默認(rèn)值為 0.5 兽掰。
- offset:以原點(diǎn) origin 為中心點(diǎn),為其設(shè)置一個(gè)非零的偏移量 offset 覆蓋一個(gè)范圍义黎,而不只是單個(gè)原點(diǎn)。在范圍
-offset <= origin <= +offset
內(nèi)的所有評(píng)分 _score 都是 1.0 豁跑。
比如廉涕,想要搜索某條博客泻云,同時(shí)發(fā)表時(shí)間近的排在前面,引入基于時(shí)間的衰減函數(shù)狐蜕。
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"gauss" :{
"timestamp": {
"origin": "now timestamp",
"offset": "5d",
"scale": "10d"
}
}
}
}
}
按時(shí)間和地理空間做衰減宠纯,offset 和 scale 必須加上單位。
這里我們確定以當(dāng)前的時(shí)間為衰減函數(shù)的中心层释, 5 天以內(nèi)的文檔婆瓜,相關(guān)度不做處理;5 天到 15 天贡羔,衰減系數(shù)逐漸降低到0.5廉白;15 天之外,系數(shù)繼降低乖寒。
比如猴蹂,想將地理空間和價(jià)格的影響引入搜索,可以這樣實(shí)現(xiàn):
GET /_search
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"location": {
"origin": { "lat": 51.5, "lon": 0.12 },
"offset": "2km",
"scale": "3km"
}
}
},
{
"gauss": {
"price": {
"origin": "50",
"offset": "50",
"scale": "20"
}
},
"weight": 2
}
]
}
}
}
這里地理空間類型楣嘁,在衰減函數(shù)的參數(shù)里要加上單位磅轻,而普通的數(shù)值類型不用加單位。
通過衰減函數(shù)逐虚,可以在相關(guān)度為主的前提下聋溜,引入時(shí)間、空間叭爱、數(shù)值等其他因素撮躁,從而影響搜索的返回結(jié)果。比起簡(jiǎn)單粗暴的sort涤伐,衰減函數(shù)可以保證相關(guān)度高的依然排在靠前的位置馒胆。
終極武器script_score
elasticsearch支持用戶自己寫groovy腳本,來自定義復(fù)雜的評(píng)分影響邏輯凝果。因?yàn)槿粘V胁惶珜?shí)用祝迂,同時(shí)腳本對(duì)性能影響較大,所以這里不做介紹器净。