【ElasticSearch】大數據量情況下的首碼、中綴實時搜索方案

来源:https://www.cnblogs.com/yywf/archive/2023/07/20/17541137.html
-Advertisement-
Play Games

大數據、elasticsearch、實時搜索、search_as_you_type、Completion Suggester、查詢優化、首碼匹配、中綴匹配 ...


簡述

業務開發中經常會遇到這樣一種情況,用戶在搜索框輸入時要實時展示搜索相關的結果。要實現這個場景常用的方案有Completion Suggester、search_as_you_type。那麼這兩種方式有什麼區別呢?一起來瞭解下。

環境說明:

數據量:9000w+
es版本:7.10.1
腳本執行工具:kibana

Completion Suggester和search_as_you_type的區別

1.Completion Suggester是基於首碼匹配、且數據結構存儲在記憶體中,超級快,缺點是耗記憶體
2.search_as_you_type可以是首碼、中綴匹配,可以很快,但是要選好查詢方式
3.Api調用方式不同,Completion Suggester是通過Suggest語句查詢,search_as_you_type和常規查詢方式一致

舉個慄子

如何實現首碼匹配需求

使用Completion Suggester,示例如下:

  1. 創建索引
PUT /es_demo
{
  "mappings": {
    "properties": {
      "title_comp": {
        "type": "completion",
        "analyzer": "standard"
      }
    }
  }
}
  1. 初始化數據
POST _bulk
{"index":{"_index":"es_demo","_id":"1"}}
{"title_comp": "憤怒的小鳥"}
{"index":{"_index":"es_demo","_id":"2"}}
{"title_comp": "最後一隻渡渡鳥"}
{"index":{"_index":"es_demo","_id":"3"}}
{"title_comp": "今天不加班啊"}
{"index":{"_index":"es_demo","_id":"4"}}
{"title_comp": "憤怒的青年"}
{"index":{"_index":"es_demo","_id":"5"}}
{"title_comp": "最後一隻996程式猿"}
{"index":{"_index":"es_demo","_id":"6"}}
{"title_comp": "今日無事,勾欄聽曲"}
  1. 查詢DSL
    通過首碼查詢,查找以“憤怒”開頭的字元串
GET /es_demo/_search
{
  "suggest": {
    "title_suggest": {
      "prefix": "憤怒",
      "completion": {
        "field": "title_comp"
      }
    }
  }
}
  1. 查詢代碼demo
@SpringBootTest
public class SuggestTest {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Test
    public void testComp() {
        List<Map<String, Object>> list = suggestComplete("憤怒");
        list.forEach(m -> System.out.println("[" + m.get("title_comp") + "]"));
    }

    public List<Map<String, Object>> suggestComplete(String keyword) {
        CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.completionSuggestion("title_comp");
        completionSuggestionBuilder.size(5)
                //跳過重覆的
                .skipDuplicates(true);

        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("suggest_title", completionSuggestionBuilder)
                .setGlobalText(keyword);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.suggest(suggestBuilder);

        SearchRequest searchRequest = new SearchRequest("es_demo").source(searchSourceBuilder);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            CompletionSuggestion completionSuggestion = response.getSuggest().getSuggestion("suggest_title");

            List<Map<String, Object>> suggestList = new LinkedList<>();
            for (CompletionSuggestion.Entry.Option option : completionSuggestion.getOptions()) {
                Map<String, Object> map = new HashMap<>();
                map.put("title_comp", option.getHit().getSourceAsMap().get("title_comp"));
                suggestList.add(map);
            }

            return suggestList;
        } catch (IOException e) {
            throw new RuntimeException("ES查詢出錯");
        }
    }
}

查詢結果:

[憤怒的小鳥]
[憤怒的青年]

如何實現中綴匹配需求

使用search_as_you_type,此處提供了hanlp_index和standard兩種分詞器的欄位示例。示例如下:

  1. 創建索引
PUT /es_search_as_you_type
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "han": {
            "type": "search_as_you_type",
            "analyzer": "hanlp_index"
          },
          "stan": {
            "type": "search_as_you_type",
            "analyzer": "standard"
          }
        }
      }
    }
  }
}
  1. 初始化數據
POST _bulk
{"index":{"_index":"es_search_as_you_type","_id":"1"}}
{"title": "憤怒的小鳥"}
{"index":{"_index":"es_search_as_you_type","_id":"2"}}
{"title": "最後一隻渡渡鳥"}
{"index":{"_index":"es_search_as_you_type","_id":"3"}}
{"title": "今天不加班啊"}
{"index":{"_index":"es_search_as_you_type","_id":"4"}}
{"title": "憤怒的青年"}
{"index":{"_index":"es_search_as_you_type","_id":"5"}}
{"title": "最後一隻996程式猿"}
{"index":{"_index":"es_search_as_you_type","_id":"6"}}
{"title": "今日無事,勾欄聽曲"}
  1. 查詢DSL
GET /es_search_as_you_type/_search
{
  "query": {
    "match": {
      "title.stan": {
        "query": "的小",
        "operator": "and"
      }
    }
  }
}
  1. 查詢代碼demo
@SpringBootTest
public class SuggestTest {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Test
    public void testSearchAsYouType() {
        List<Map<String, Object>> list = suggestSearchAsYouType("的小");
        list.forEach(m -> System.out.println("[" + m.get("title") + "]"));
    }

    public List<Map<String, Object>> suggestSearchAsYouType(String keyword) {
        //這裡使用了search_as_you_type的2gram欄位,可以根據自己需求調整配置
        MatchQueryBuilder matchQueryBuilder = matchQuery("title.stan._2gram", keyword).operator(Operator.AND);

        //需要返回的欄位
        String[] includeFields = new String[]{"title"};
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(matchQueryBuilder).size(5)
                .fetchSource(includeFields, null)
                .trackTotalHits(false)
                .trackScores(true)
                .sort(SortBuilders.scoreSort());

        SearchRequest searchRequest = new SearchRequest("es_search_as_you_type").source(searchSourceBuilder);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            org.elasticsearch.search.SearchHits hits = response.getHits();

            List<Map<String, Object>> suggestList = new LinkedList<>();
            for (org.elasticsearch.search.SearchHit hit : hits) {
                Map<String, Object> map = new HashMap<>();
                map.put("title", hit.getSourceAsMap().get("title").toString());
                suggestList.add(map);
            }
            return suggestList;
        } catch (IOException e) {
            throw new RuntimeException("ES查詢出錯");
        }
    }
}

查詢結果:

[憤怒的小鳥]

分詞器說明

查看分詞結果的方式

第一種

指定分詞器

GET _analyze
{
  "analyzer": "standard",
  "text": [
    "憤怒的小鳥"
  ]
}

第二種

指定使用某個欄位的分詞器

POST es_search_as_you_type/_analyze
{
  "field": "title.stan",
  "text": [
    "憤怒的青年"
  ]
}

hanlp_index和standard分詞器的區別

standard分詞器

  • 預設會過濾掉符號
  • 中文以單個字為最小單位,英文則會以空格符或其他符號或中文分隔作為一個單詞

例:

GET _analyze
{
  "analyzer": "standard",
  "text": [
    "憤怒的小鳥"
  ]
}

分詞結果:

{
  "tokens" : [
    {
      "token" : "憤",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "怒",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "的",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "小",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "鳥",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    }
  ]
}

hanlp_index分詞器

  • 預設不會過濾符號
  • 通過語義等對字元串進行分詞,會分出詞語

例:

GET _analyze
{
  "analyzer": "hanlp_index",
  "text": [
    "憤怒的小鳥"
  ]
}

分詞結果:

{
  "tokens" : [
    {
      "token" : "憤怒",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "a",
      "position" : 0
    },
    {
      "token" : "的",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "ude1",
      "position" : 1
    },
    {
      "token" : "小鳥",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "n",
      "position" : 2
    }
  ]
}

生產實踐中的查詢情況

基本都是幾百毫秒就解決。ps:如果一條數據欄位很多,最好只返回幾個需要的欄位即可,否則數據傳輸就要占用較多時間。
image

總結

當然,無論是Completion Suggester還是search_as_you_type的查詢配置方式都還有很多,例如Completion Suggester的Context Suggester,search_as_you_type的2gram、3gram,還有查詢類型match_bool_prefix、match_phrase、match_phrase_prefix等等。各種組合起來都會產生不同的效果,筆者這裡只是列舉出一種還算可以的方式。關於其他的查詢類型和配置如何使用以及分別是怎麼工作的,下次有空再聊聊。

官方文檔鏈接

https://www.elastic.co/guide/en/elasticsearch/reference/7.10/search-as-you-type.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # Spring 的依賴註入 @[toc] ## 每博一文案 ```tex "在千千萬萬個選擇里",我永遠選擇去做哪些我認為值得的事,我可能幹得很漂亮,也可能搞得一塌糊塗。 但沒關係,重要的是我為之努力過。”我們很難做好每件事,讓人生不留下任何遺憾,儘力而為就好“享受 生活的過程,接受結果。”人生是 ...
  • * Cobar(已經被淘汰沒使用了) * TDDL * 淘寶根據自己的業務特點開發了 TDDL (Taobao Distributed Data Layer) * 基於JDBC規範,沒有server,以client-jar的形式存在,引入項目即可使用 * 開源功能比較少,阿裡內部使用為主 * Myc ...
  • 在本頁中,我們將瞭解 Java 對象和類。在面向對象的編程技術中,我們使用對象和類來設計程式。 Java中的對象既是物理實體又是邏輯實體,而Java中的類只是邏輯實體。 # 什麼是Java中的對象 具有狀態和行為的實體稱為對象,例如椅子、自行車、記號筆、筆、桌子、汽車等。它可以是物理的或邏輯的(有形 ...
  • 一、Django入門 Django 是一個功能強大且高效的Web應用程式框架,它採用了Python語言,幫助開發人員快速構建可擴展和可維護的Web應用程式。本文將深入探討Django框架的核心概念和優勢。 1. Django簡介 Django 是一個開源的Web應用程式框架,由 Adrian Hol ...
  • ## 教程簡介 CherryPy 是一個使用 Python 編程語言的面向對象的 Web 應用程式框架。它旨在通過包裝 HTTP 協議來快速開發 Web 應用程式,但保持在較低級別,並且提供的內容遠不及 RFC 7231 中定義的內容。 [CherryPy入門教程](https://www.itba ...
  • ## 教程簡介 CodeIgniter 是一套給 PHP 網站開發者使用的應用程式開發框架和工具包。它提供單的介面和邏輯結構,其目的是使開發人員更快速地進行項目開發。使用 CodeIgniter可以減少代碼的編寫量,並將你的精力投入到項目的創造性開發上。 [CodeIgniter入門教程](http ...
  • redis是一個非常快速‎‎的非關係資料庫‎‎解決方案。其簡單的鍵值數據模型使 Redis 能夠處理大型數據集,同時保持令人印象深刻的讀寫速度和可用性。‎redis提供了五種數據類型,分別是是:1、string(字元串);2、hash(哈希);3、list(列表);4、set(集合);5、sort ... ...
  • 一、概念 **1、public和private** 兩個都是訪問許可權修飾符,用於控制外界對類內部成員的訪問。 * public:表明對象成員是完全共有的,外界可以隨意訪問。用public修飾的數據成員、成員函數是對所有用戶開放的,所有用戶都可以直接進行調用。 * private:表明對象成員是完全私 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...