最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot+Elasticsearch實(shí)現(xiàn)一套完整的搜索服務(wù)系統(tǒng)

 更新時(shí)間:2026年05月29日 09:11:06   作者:fengxin_rou  
在內(nèi)容平臺(tái)場(chǎng)景中,高性能、高相關(guān)性、實(shí)時(shí)可搜是搜索模塊的核心訴求,本文基于 SpringBoot 與 Elasticsearch(ES),從零實(shí)現(xiàn)一套包含索引初始化、數(shù)據(jù)同步、增量更新、關(guān)鍵詞檢索、游標(biāo)分頁(yè)的完整搜索服務(wù),需要的朋友可以參考下

前言

在內(nèi)容平臺(tái)場(chǎng)景中,高性能、高相關(guān)性、實(shí)時(shí)可搜是搜索模塊的核心訴求。本文基于 SpringBoot 與 Elasticsearch(ES),從零實(shí)現(xiàn)一套包含索引初始化、數(shù)據(jù)同步、增量更新、關(guān)鍵詞檢索、游標(biāo)分頁(yè)的完整搜索服務(wù),解決傳統(tǒng)數(shù)據(jù)庫(kù)搜索性能差、分詞不精準(zhǔn)、實(shí)時(shí)性不足等痛點(diǎn),可直接應(yīng)用于文章、資訊、社區(qū)類內(nèi)容平臺(tái)。

一、Elasticsearch 索引設(shè)計(jì)與初始化

1.1 核心概念類比

ES 是分布式搜索引擎,核心是倒排索引,其結(jié)構(gòu)可與 MySQL 直接類比,降低理解成本:

  • Index(索引)≈ 數(shù)據(jù)庫(kù)表
  • Document(文檔)≈ 表行數(shù)據(jù)
  • Mapping(映射)≈ 表結(jié)構(gòu) Schema
  • Field(字段)≈ 表列

1.2 索引初始化實(shí)現(xiàn)

項(xiàng)目啟動(dòng)時(shí)自動(dòng)創(chuàng)建索引與 Mapping,title/body 字段啟用 IK 分詞,需提前安裝 ES 分析 - ik 插件。標(biāo)題使用 ik_max_word 分詞、ik_smart 檢索,兼顧召回率與精準(zhǔn)度。

/**
 * 搜索索引初始化:應(yīng)用啟動(dòng)時(shí)創(chuàng)建索引與映射
 */
@Service
@RequiredArgsConstructor
public class SearchIndexInitializer {
    private final ElasticsearchClient es;
    private static final String INDEX = "zhiguang_content_index";

    @PostConstruct
    public void ensureIndex() {
        try {
            // 檢查索引是否存在
            boolean exists = es.indices().exists(e -> e.index(INDEX)).value();
            if (exists) return;
            // 創(chuàng)建索引并定義映射
            es.indices().create(c -> c.index(INDEX).mappings(m -> m
                .properties("content_id", p -> p.long_(LongNumberProperty.of(b -> b)))
                .properties("title", p -> p.text(t -> t.analyzer("ik_max_word").searchAnalyzer("ik_smart")))
                .properties("body", p -> p.text(t -> t.analyzer("ik_max_word")))
                .properties("status", p -> p.keyword(KeywordProperty.of(b -> b)))
                .properties("title_suggest", p -> p.completion(CompletionProperty.of(b -> b)))
                // 其他字段省略...
            ));
        } catch (Exception ignored) {}
    }
}

1.3 字段設(shè)計(jì)要點(diǎn)

  • keyword 類型:用于標(biāo)簽、狀態(tài)、作者信息等精確匹配與過(guò)濾,不分詞。
  • text 類型:用于標(biāo)題、正文等全文檢索,綁定 IK 分詞器。
  • completion 類型:專門用于搜索建議,提升輸入 聯(lián)想體驗(yàn)。

二、搜索索引數(shù)據(jù)寫入與同步機(jī)制

2.1 全量數(shù)據(jù)回灌

應(yīng)用啟動(dòng)時(shí)若索引為空,自動(dòng)從數(shù)據(jù)庫(kù)分頁(yè)讀取歷史數(shù)據(jù),批量寫入 ES,保證索引數(shù)據(jù)完整。

@PostConstruct
public void ensureBackfill() {
    long cnt = es.count(c -> c.index(INDEX)).count();
    if (cnt > 0) return;
    int limit = 500;
    int offset = 0;
    while (true) {
        List<KnowPostFeedRow> rows = knowPostMapper.listFeedPublic(limit, offset);
        if (rows == null || rows.isEmpty()) break;
        for (KnowPostFeedRow r : rows) {
            upsertKnowPost(r.getId());
        }
        offset += rows.size();
    }
}

2.2 單篇文檔寫入邏輯

核心方法 upsertKnowPost 實(shí)現(xiàn)數(shù)據(jù)新增 / 更新,流程標(biāo)準(zhǔn)化:

  1. 從數(shù)據(jù)庫(kù)查詢文章詳情;
  2. 遠(yuǎn)程拉取正文,失敗則使用描述兜底,截?cái)嘀?4000 字符;
  3. 補(bǔ)充點(diǎn)贊、收藏等計(jì)數(shù)數(shù)據(jù);
  4. 寫入 ES 并設(shè)置 refresh=WaitFor,保證寫入后立即可搜

2.3 軟刪除實(shí)現(xiàn)

不物理刪除文檔,僅更新 status=deleted,搜索時(shí)過(guò)濾該狀態(tài),避免數(shù)據(jù)丟失與索引波動(dòng)。

public void softDeleteKnowPost(long id) {
    Map<String, Object> doc = new HashMap<>();
    doc.put("content_id", id);
    doc.put("status", "deleted");
    es.index(i -> i.index(INDEX).id(String.valueOf(id))
        .document(doc).refresh(Refresh.WaitFor));
}

三、基于 Kafka+Canal 的增量數(shù)據(jù)同步

3.1 同步架構(gòu)

使用 Canal 監(jiān)聽(tīng) MySQL binlog,將數(shù)據(jù)變更發(fā)送至 Kafka 的 canal-outbox 主題,搜索模塊作為消費(fèi)者,實(shí)現(xiàn)數(shù)據(jù)庫(kù)與 ES 數(shù)據(jù)準(zhǔn)實(shí)時(shí)一致

3.2 消息消費(fèi)邏輯

與用戶關(guān)系模塊共用 Topic,通過(guò)不同消費(fèi)者組隔離業(yè)務(wù),僅處理 entity=knowpost 的變更消息,保證冪等性。

/**
 * 搜索索引 Outbox 消費(fèi)者
 */
@Service
@RequiredArgsConstructor
public class CanalOutboxConsumerSearch {
    private final SearchIndexService indexService;

    @KafkaListener(topics = OutboxTopics.CANAL_OUTBOX, groupId = "search-index-consumer")
    public void onMessage(String message, Acknowledgment ack) {
        try {
            List<JsonNode> rows = OutboxMessageUtil.extractRows(objectMapper, message);
            for (JsonNode row : rows) {
                JsonNode payload = objectMapper.readTree(row.get("payload").asText());
                String entity = payload.get("entity").asText();
                String op = payload.get("op").asText();
                Long id = payload.get("id").asLong();
                if (!"knowpost".equals(entity) || id == null) continue;
                // 執(zhí)行更新或軟刪除
                if ("delete".equalsIgnoreCase(op)) {
                    indexService.softDeleteKnowPost(id);
                } else {
                    indexService.upsertKnowPost(id);
                }
            }
            ack.acknowledge();
        } catch (Exception ignored) {}
    }
}

3.3 優(yōu)勢(shì)說(shuō)明

  • 解耦:數(shù)據(jù)庫(kù)變更與搜索同步分離,互不影響;
  • 高可用:消息隊(duì)列緩沖流量,避免直接寫入 ES 導(dǎo)致雪崩;
  • 易擴(kuò)展:新增下游模塊只需新增消費(fèi)者組,無(wú)侵入改造。

四、搜索服務(wù)核心實(shí)現(xiàn):檢索、加權(quán)與分頁(yè)

4.1 完整搜索流程

前端傳入關(guān)鍵詞、標(biāo)簽、分頁(yè)參數(shù),后端構(gòu)建 ES 查詢,流程分為:參數(shù)解析→召回過(guò)濾→業(yè)務(wù)加權(quán)→排序高亮→游標(biāo)分頁(yè)→結(jié)果封裝。

4.2 多字段匹配與權(quán)重加權(quán)

使用 multi_match 實(shí)現(xiàn)多字段檢索,標(biāo)題權(quán)重設(shè)為 3,正文權(quán)重為 1,提升標(biāo)題匹配優(yōu)先級(jí)。通過(guò) function_score 對(duì)點(diǎn)贊、瀏覽量做對(duì)數(shù)加權(quán),讓優(yōu)質(zhì)內(nèi)容排名更靠前。

// 構(gòu)建查詢核心邏輯
.query(qb -> qb.functionScore(fs -> fs
    .query(qb2 -> qb2.bool(bq -> {
        // 多字段匹配,標(biāo)題權(quán)重3倍
        bq.must(m -> m.multiMatch(mm -> mm.query(q).fields("title^3", "body")));
        // 過(guò)濾已發(fā)布內(nèi)容
        bq.filter(f -> f.term(t -> t.field("status").value("published")));
        // 標(biāo)簽過(guò)濾
        if (!tags.isEmpty()) {
            bq.filter(f -> f.terms(t -> t.field("tags").terms(tv -> tv.value(tags))));
        }
        return bq;
    }))
    // 點(diǎn)贊數(shù)加權(quán):log(1+like)×2
    .functions(fn -> fn.fieldValueFactor(f -> f.field("like_count").modifier(Log1p)).weight(2.0))
    // 瀏覽數(shù)加權(quán):log(1+view)×1
    .functions(fn -> fn.fieldValueFactor(f -> f.field("view_count").modifier(Log1p)).weight(1.0))
    .boostMode(Sum)
))

4.3 游標(biāo)分頁(yè)實(shí)現(xiàn)

替代傳統(tǒng) offset+limit,使用 search_after 實(shí)現(xiàn)深分頁(yè)高性能,將最后一條數(shù)據(jù)的排序值(評(píng)分、時(shí)間、點(diǎn)贊、ID)Base64 編碼為游標(biāo),下一頁(yè)從該位置繼續(xù)查詢。

4.4 高亮與摘要生成

對(duì)標(biāo)題、正文關(guān)鍵詞添加 <em> 高亮標(biāo)簽,合并為搜索摘要(Snippet),提升用戶閱讀體驗(yàn)。

五、搜索建議功能實(shí)現(xiàn)

基于 ES completion 類型實(shí)現(xiàn)輸入 聯(lián)想,用戶輸入前綴時(shí)快速返回標(biāo)題候選,響應(yīng)時(shí)間毫秒級(jí)。

public SuggestResponse suggest(String prefix, int size) {
    var resp = es.search(s -> s.index(INDEX)
        .suggest(sug -> sug.suggesters("title_suggest",
            sc -> sc.prefix(prefix).completion(c -> c.field("title_suggest").size(size))))
        , Map.class);
    // 解析建議結(jié)果并返回
    List<String> items = new ArrayList<>();
    resp.suggest().get("title_suggest").forEach(s -> {
        s.completion().options().forEach(opt -> items.add(opt.text()));
    });
    return new SuggestResponse(items);
}

結(jié)語(yǔ)

本文完整實(shí)現(xiàn)了 SpringBoot 整合 Elasticsearch 的企業(yè)級(jí)內(nèi)容搜索系統(tǒng),覆蓋索引設(shè)計(jì)、數(shù)據(jù)全量 / 增量同步、關(guān)鍵詞檢索、游標(biāo)分頁(yè)、搜索建議全流程。方案具備實(shí)時(shí)性高、檢索精準(zhǔn)、擴(kuò)展性強(qiáng)、性能穩(wěn)定等特點(diǎn),適配文章、社區(qū)、電商等內(nèi)容搜索場(chǎng)景。

實(shí)際落地需注意:IK 分詞器自定義詞庫(kù)優(yōu)化、ES 集群分片規(guī)劃、異步同步重試機(jī)制、查詢性能監(jiān)控。后續(xù)可擴(kuò)展語(yǔ)義搜索、個(gè)性化排序、搜索熱詞統(tǒng)計(jì)等能力,進(jìn)一步提升搜索體驗(yàn)。

以上就是基于SpringBoot+Elasticsearch實(shí)現(xiàn)一套完整的搜索服務(wù)系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Elasticsearch搜索服務(wù)系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • mybatis判斷l(xiāng)ist不為空/大小的問(wèn)題

    mybatis判斷l(xiāng)ist不為空/大小的問(wèn)題

    這篇文章主要介紹了mybatis判斷l(xiāng)ist不為空/大小的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java日志軟件Log4j的基本使用教程

    Java日志軟件Log4j的基本使用教程

    這篇文章主要介紹了Java日志軟件Log4j的基本使用教程,包括回滾和發(fā)送日志郵件等基本功能使用的講解,需要的朋友可以參考下
    2015-12-12
  • SpringSecurity安全框架的使用

    SpringSecurity安全框架的使用

    SpringSecurity是一個(gè)用于企業(yè)應(yīng)用系統(tǒng)的安全框架,可以控制用戶登錄權(quán)限,實(shí)現(xiàn)不同權(quán)限用戶訪問(wèn)不同內(nèi)容,文章介紹了SpringSecurity的簡(jiǎn)單配置和使用,包括環(huán)境搭建、測(cè)試用例和自定義登錄頁(yè)面的配置
    2025-02-02
  • git stash 和unstash的使用操作,git unstash failed

    git stash 和unstash的使用操作,git unstash failed

    這篇文章主要介紹了git stash 和unstash的使用操作,git unstash failed,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02
  • Java無(wú)需Office環(huán)境實(shí)現(xiàn)Word轉(zhuǎn)HTML功能

    Java無(wú)需Office環(huán)境實(shí)現(xiàn)Word轉(zhuǎn)HTML功能

    在企業(yè)級(jí)開(kāi)發(fā)、文檔管理系統(tǒng)、在線預(yù)覽等場(chǎng)景中,Word?文檔轉(zhuǎn)換為?HTML?是高頻需求,?本文將從環(huán)境配置、基礎(chǔ)轉(zhuǎn)換、高級(jí)定制等維度,全面講解基于該庫(kù)的?Word?轉(zhuǎn)?HTML?技術(shù)實(shí)現(xiàn),希望對(duì)大家有所幫助
    2026-05-05
  • Java正則判斷日期格式是否正確的方法示例

    Java正則判斷日期格式是否正確的方法示例

    這篇文章主要介紹了Java正則判斷日期格式是否正確的方法,結(jié)合實(shí)例形式分析了Java針對(duì)日期字符串正則判斷的相關(guān)操作技巧,需要的朋友可以參考下
    2017-03-03
  • Spring異常實(shí)現(xiàn)統(tǒng)一處理的方法

    Spring異常實(shí)現(xiàn)統(tǒng)一處理的方法

    這篇文章主要介紹了Spring異常實(shí)現(xiàn)統(tǒng)一處理的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-12-12
  • Spring之ORM模塊代碼詳解

    Spring之ORM模塊代碼詳解

    這篇文章主要介紹了Spring之ORM模塊代碼詳解,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-12-12
  • Java方法參數(shù)裝配順序詳解

    Java方法參數(shù)裝配順序詳解

    這篇文章主要介紹了Java方法參數(shù)裝配順序詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)

    Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)

    在SpringBoot中配置自定義線程池時(shí),參數(shù)的設(shè)置需要根據(jù)具體的應(yīng)用場(chǎng)景、系統(tǒng)資源和業(yè)務(wù)需求來(lái)調(diào)整,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下
    2025-10-10

最新評(píng)論

广水市| 多伦县| 安徽省| 绥阳县| 额济纳旗| 高台县| 营口市| 翼城县| 四子王旗| 罗平县| 中阳县| 宣恩县| 柳州市| 武乡县| 巴南区| 延庆县| 惠安县| 白水县| 鄂伦春自治旗| 宁夏| 九龙城区| 太和县| 汝州市| 牙克石市| 高清| 成武县| 乌兰浩特市| 莒南县| 江达县| 册亨县| 江北区| 江阴市| 冀州市| 牟定县| 井冈山市| 蓬莱市| 枣强县| 翁源县| 固阳县| 来凤县| 临洮县|