Solr 實戰完全筆記:從基礎語法到效能調校與評分機制

基本參數介紹

  • q:查詢的關鍵字,此參數最為重要,例如 q=id:1,默認為 q=*:*
  • fl:指定返回哪些字段,用逗號或空格分隔,注意字段區分大小寫,例如 fl=id,title,sort
  • start:返回結果的第幾條記錄開始,一般分頁用,默認 0 開始
  • rows:指定返回結果最多有多少條記錄,默認值為 10,配合 start 實現分頁
  • sort:排序方式,例如 id desc 表示按照 id 降序,多個字段:score desc, price asc
  • wt:(writer type) 指定輸出格式,有 xml, json, php 等
  • fq:(filter query) 過濾查詢,提供一個可選的篩選器查詢,例如:q=id:1&fq=sort:[1 TO 5]
  • df:默認的查詢字段,一般默認指定
  • qt:(query type) 指定哪個類型來處理查詢請求,一般不用指定,默認是 standard
  • indent:返回的結果是否縮進,默認關閉,用 indent=true 開啟

查詢語法

  • : 指定字段查指定值,如返回所有值 *:*
  • ? 表示單個任意字符的通配
  • * 表示多個任意字符的通配(不能在檢索的項開始使用)
  • ~ 表示模糊檢索,如 roam~ 將找到 foam 和 roams;roam~0.8 檢索返回相似度在 0.8 以上的記錄
  • AND|| 布爾操作符
  • OR&& 布爾操作符
  • NOT!- 排除操作符
  • + 存在操作符,要求符號後的項必須在文檔中存在
  • ( ) 用於構成子查詢
  • [] 包含範圍檢索,如 date:[201507 TO 201510] 包含頭尾
  • {} 不包含範圍檢索,如 date:{201507 TO 201510} 不包含頭尾

Solr 本質

Solr 本質上還是搜尋引擎,因此優先還是 index 其後才是 store。
Partial update 也是先把資料拉回來重新 index 後 store。
順序:index 先,然後 store


實戰案例:2024618 大戰

具體問題

  • 大總管 job 機 64 台:一秒鐘處理一條 queue
  • 大總管 job 機 128 台:兩秒鐘處理一條 queue
  • 機器開兩倍,速度完全沒起來!

問題截圖1

問題截圖2

具體發現

  1. 機器量大起來時:查詢、更新都會有影響,尤其更新影響更大。查詢其實沒有太大影響,是更新後的 commit 造成的。
  2. Solr 為了資料絕不遺漏:在 shard 沒有全部完全起來前,不可查、不可更新、不可刪除。But 可以新增,新增的資料也可刪除,等到其他機器復活後會自動 Sync。
  3. 當 shard 尚未完全準備就緒時(例如:應該有 3 個副本,但僅有 1 個處於 active 狀態):
    • ✓ 可以插入新的文件 (doc)
    • ✗ 不可以進行查詢 (query)
    • ✗ 不可以刪除 (delete)
    • ✗ 不可以更新現有的文件 (update)
  4. Zookeeper 內有 Solr 相關設定,可從 Zookeeper get Solr 的設定進而查詢到 Solr 資料。

Commit vs Soft Commit

Commit(硬提交)

  • 功能:將所有緩存中的更改(如新增、更新或刪除的文檔)寫入到索引的持久存儲中
  • 性能:較低,因為涉及將數據寫入磁碟,過程相對較慢
  • 可見性:所有的更改在 commit 後立即對查詢可見
  • 索引更新:真正更新索引,需要進行這一操作

Soft Commit(軟提交)

  • 功能:僅將文檔的變更從緩存刷新到內存索引,而不會將它們寫入磁碟
  • 性能:較高,執行速度比普通的 commit 快
  • 可見性:變更會立即對查詢可見,但實際的持久化仍需執行普通的 commit
  • 資料安全性:若系統故障,未進行正常 commit 的變更將會丟失

實務建議

  • 以訂單的 case 來說,新增需要及時的 commit,但是更新可以使用 soft commit 來爭取速度跟時間
  • 測試案例顯示:開 64 台跟 96 台有沒有及時 commit,回應時間是有明顯差異的,但是查詢差異不大

⚠️ Soft Commit 的坑

soft commit 的啟用會讓頂層快取失效(filter queryResult)跟 document 不一致,造成 query 跟 get 的方法查同一個 ID 資料不一致


Solr 評分機制 (Scoring)

使用評分的條件

  1. 需要用 q,不能用 fq
  2. defType: edismax
  3. 欄位要用切詞性質(IK 等等),如果是 qf
  4. 設定欄位計算方式(qf、mm、bf 等等)
  5. 排序按照 score
  6. 欄位的部分要看 score:fl=*,score

Java 範例

SolrQuery solrQuery = new SolrQuery("東南西北");
solrQuery.set("defType", "edismax");
solrQuery.set("qf", "total_amount_ik^2 payment_ik^3");
// 設置要返回的字段,包括 score
solrQuery.setFields("*,score");
solrQuery.setRows(10);
solrQuery.setSort("score", SolrQuery.ORDER.desc);
var datas = _orderDao.query(solrQuery);
for (var da : datas) {
    System.out.println(da.getOrderId());
}

評分結果

qf 算法

boost * idf * tf
idf = log(1 + (N - n + 0.5) / (n + 0.5))
tf = freq / (freq + k1 * (1 - b + b * dl / avgdl))

qf vs bf 總結

  • qf:會經過 TF-IDF 計算權重,取最高的一個分數來計
  • bf:直接增加權重分數

注意:Solr 有時候 index 會卡住影響 idf 跟 tf 的分數。如果只有一個欄位進行 qf 沒有問題,但是如果有兩個取高者,卡住的就會有相對影響。

整體順序

q >> fq 資料進行 qf 然後加上 bf,最後 sort 並且 fl out


自訂 Request Handler

步驟

  1. 自己包一個 jar 檔
  2. 放到 Solr 的 lib 裡面
  3. 到對應的 collection 上設定路徑跟對應的 class

Gradle 設定檔

plugins {
    id 'java'
    id 'com.github.johnrengelman.shadow' version '7.1.2'
}

group 'com.yourpackage'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.solr:solr-core:8.11.1'
    implementation 'org.apache.solr:solr-solrj:8.11.1'
    annotationProcessor 'org.apache.solr:solr-core:8.11.1'
    annotationProcessor 'org.apache.solr:solr-solrj:8.11.1'
}

shadowJar {
    archiveClassifier.set('all')
    manifest {
        attributes(
            'Main-Class': 'com.solrtest.MyCustomRequestHandler',
            'Implementation-Title': project.name,
            'Implementation-Version': project.version
        )
    }
}

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

Java Handler 範例

package com.solrtest;

import org.apache.solr.handler.component.SearchHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;

public class MyCustomRequestHandler extends SearchHandler {

    @Override
    public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
        String param = req.getParams().get("paramName");
        
        ModifiableSolrParams params = new ModifiableSolrParams(req.getParams());
        params.set("defType", "edismax");
        params.set("qf", "data_ik^2");
        
        super.handleRequestBody(req, rsp);
        rsp.add("response", "handleRequestBody: " + param);
    }

    @Override
    public String getDescription() {
        return "Custom Search Handler";
    }

    @Override
    public String getName() {
        return "MyCustomRequestHandler";
    }
}

Solr 設定 (solrconfig.xml)

<requestHandler name="/customSearch" class="com.solrtest.MyCustomRequestHandler">
    <lst name="defaults">
        <str name="echoParams">explicit</str>
        <int name="rows">10</int>
    </lst>
    <arr name="components">
        <str>query</str>
        <str>facet</str>
    </arr>
</requestHandler>

測試查詢

http://localhost:8080/solr/new_core/customSearch?indent=true¶mName=123&q.op=OR&q=*:*

參考資料

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *