Elasticsearch——倒排索引與分詞

正排索引

文檔ID到文檔內(nèi)容桥胞、單詞的關(guān)聯(lián)關(guān)系。比如書的目錄頁(yè)對(duì)應(yīng)正排索引(指明章節(jié)名稱,指明頁(yè)數(shù))用于查看章節(jié)夭委。

倒排索引

單詞到文檔ID的關(guān)聯(lián)關(guān)系。比如索引頁(yè)對(duì)應(yīng)倒排索引(指明關(guān)鍵詞募强、指明頁(yè)數(shù))用于關(guān)鍵詞查找
倒排索引是搜索引擎的核心株灸,主要包含兩個(gè)部分:
單詞詞典(Term Dictionary)

  • 記錄所有文檔的單詞,一般都比較大擎值。
  • 記錄單詞到倒排列表的關(guān)聯(lián)信息慌烧。

倒排列表(Posting List)
記錄了單詞對(duì)應(yīng)的文檔集合,由倒排索引項(xiàng)組成幅恋。倒排索引項(xiàng)包含如下信息:

  • 文檔ID杏死,用于獲取原始信息。
  • 單詞頻率捆交,記錄該單詞在該文檔中的出現(xiàn)次數(shù)淑翼,用于后續(xù)相關(guān)性算分。
  • 位置品追,記錄單詞在文檔中的粉刺位置玄括,用于做詞語(yǔ)搜索。
  • 偏移肉瓦,記錄單詞在文檔的開始和結(jié)束位置遭京,用于做高亮顯示。

分詞

分詞是指將文本轉(zhuǎn)換成一系列單詞的過程泞莉,也可以叫做文本分析哪雕,在es里面成為Analysis

分詞器是Elasticsearch中專門處理分詞的組件,英文為Analyzer鲫趁,其組成如下:
Character Filters
針對(duì)原始文本進(jìn)行處理斯嚎,比如去除html特殊標(biāo)記符。

Tokenizer
將原始文本按照一定規(guī)則切分為單詞。

Token Filters
針對(duì)Tokenizer處理的單詞進(jìn)行在加工堡僻,比如轉(zhuǎn)小寫糠惫,刪除或新增等處理。

分詞器——調(diào)用順序

Analyze_api

Elasticsearch提供了一個(gè)測(cè)試分詞的api接口钉疫,方便驗(yàn)證分詞效果硼讽,endpoint是_analyze

  • 可以直接指定Analyzer進(jìn)行測(cè)試。

  • 可以直接指定索引中的字段進(jìn)行測(cè)試牲阁。

  • 可以自定義分詞器進(jìn)行測(cè)試固阁。

  • 直接指定analyze進(jìn)行測(cè)試,接口如下:


  • 直接指定索引中的字段進(jìn)行測(cè)試咨油,接口如下:


  • 自定義分詞器進(jìn)行測(cè)試您炉,接口如下:


Elasticsearch自帶分詞器

中文分詞

難點(diǎn):

  • 中文分詞指的是將一個(gè)漢字序列切分成一個(gè)一個(gè)單獨(dú)的詞,在英文中單詞之間是以空格作為自然分隔符役电,但漢語(yǔ)中則沒有形式上的分隔符。

  • 上下文不同分詞效果迥異棉胀,比如交叉歧義問題法瑟,比如下面兩種分詞都合理。

乒乓球拍/賣/完了
乒乓球/拍/買完了

常用分詞系統(tǒng)

IK

ieba

基于自然語(yǔ)言處理的分詞系統(tǒng)

HanLp

  • 由一系列模型與算法組成的Java工具包脊奋,目標(biāo)是普及自然語(yǔ)言處理在生產(chǎn)環(huán)境中的應(yīng)用
  • https://github.com/hankcs/HanLp

thulac

  • THU Lexical Analyzer for Chinese熬北,由清華大學(xué)自然語(yǔ)言處理與社會(huì)人文計(jì)算實(shí)驗(yàn)室研制推出的一套中文詞法分析工具包,具有中文分詞和詞性標(biāo)注功能
  • https://github.com/microbun/elasticsearch-thulac-plugin

自定義分詞

當(dāng)自帶的分詞無法滿足需求時(shí)诚隙,可自定義分詞
通過自定義Character Filters讶隐、Tokenizer、Token Filters實(shí)現(xiàn)

Character Filters

  • 在Tokenizer之前對(duì)原始文本進(jìn)行處理久又,比如增加巫延、刪除或替換字符等。

  • 自帶的如下:

    • HTML Strip 去除html標(biāo)簽和轉(zhuǎn)換html實(shí)體地消。
    • Mapping進(jìn)行字符替換操作炉峰。
    • Pattern Replace進(jìn)行正則匹配替換。
  • 會(huì)影響后續(xù)Tokenizer解析的postion和offset信息脉执。

Tokenizer

  • 將原始文本按照一定規(guī)則切分為單詞(term or token)
  • 自帶的如下:
    • standard 按照單詞進(jìn)行分割疼阔。
    • letter 按照非字符類進(jìn)行分割。
    • whitespace 按照空格進(jìn)行分割适瓦。
    • UAX URL Email 按照standard 分割竿开,但不會(huì)分割郵箱和url谱仪。
    • NGram和Edge NGram連詞分割。
    • Path Hierarchy 按照文件路徑進(jìn)行分割否彩。

Token Filters

  • 對(duì)于Tokenizer輸出的單詞(term)進(jìn)行增加疯攒、刪除、修改等操作列荔。
  • 自帶的如下:
    • lowercase 將所有的term轉(zhuǎn)換為小寫敬尺。
    • stop刪除stop words。
    • NGram和Edge NGram連詞分割贴浙。
    • Synonym添加近義詞的term砂吞。

自定義分詞的api

自定義分詞需要在索引的配置中設(shè)定,如下所示:

分詞會(huì)在如下兩個(gè)時(shí)機(jī)使用:

  • 創(chuàng)建或更新文檔時(shí)(Index Time)崎溃,會(huì)對(duì)相應(yīng)的文檔進(jìn)行分詞處理蜻直。
  • 查詢時(shí)(Search Time),會(huì)對(duì)查詢語(yǔ)句進(jìn)行分詞袁串。

索引時(shí)分詞是通過配置Index Mapping中每個(gè)字段的analyzer屬性實(shí)現(xiàn)的概而,如下:

  • 不指定分詞時(shí),使用默認(rèn)分詞standard


查詢時(shí)分詞的指定方式有如下幾種:

  • 查詢的時(shí)候通過analyzer指定分詞器囱修。
  • 通過index mapping設(shè)置search_analyzer實(shí)現(xiàn)


ik分詞器安裝與使用

ik分詞器下載與安裝

*2破镰、解壓餐曼,將文件復(fù)制到es安裝目錄/plugins/ik目錄下即可


  • 3、重啟elasticsearch
ik分詞器基礎(chǔ)知識(shí)

ik_max_word:會(huì)將文本做最細(xì)粒度的拆分鲜漩,比如會(huì)將"中華人民共和國(guó)人民大會(huì)堂"拆分為"中華人民共和國(guó)源譬、中華人民、中華宇整、華人瓶佳、人民、人民共和國(guó)鳞青、人民大會(huì)堂霸饲、人民大會(huì)、大會(huì)堂"臂拓,會(huì)窮盡各種可能的組合厚脉。

ik_smart:會(huì)做最粗粒度的拆分,比如會(huì)將"中華人民共和國(guó)人民大會(huì)堂"拆分為"中華人民共和國(guó)胶惰、人民大會(huì)堂"傻工。

ik分詞器的使用

存儲(chǔ)時(shí)使用ik_max_word,搜索時(shí)使用ik_smart

因?yàn)楹罄m(xù)的keyword和text設(shè)計(jì)分詞問題,這里給出分詞最佳實(shí)踐中捆。即存儲(chǔ)時(shí)時(shí)使用ik_max_word鸯匹,搜索時(shí)分詞器用ik_smart,這樣索引時(shí)最大化的將內(nèi)容分詞泄伪,搜索時(shí)更精確的搜索到想要的結(jié)果殴蓬。

PUT /index

{
    "mappings": {
        "peoperties":{
            "text":{
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            }
        }
    }
}
ik分詞器配置文件
  • ik配置文件地址/es/plugins/ik/config目錄
  • IKAnalyzer.cfg.xml:用來配置自定義詞庫(kù)。
  • main.dic:ik原生內(nèi)置的中文詞庫(kù)蟋滴,總共有27萬多條染厅,只要是這些單詞,都會(huì)被分到一起津函。
  • preposition.dic:介詞肖粮。
  • quantifier.dic:放了一些單位相關(guān)的詞,量詞尔苦。
  • suffix.dic:放了一些后綴涩馆。
  • surname.dic:中國(guó)的姓氏。
  • stopword.dic:英文停用詞允坚。

ik原生最重要的兩個(gè)配置文件:

  • main.dic:包含了原生的中文詞語(yǔ)凌净,會(huì)按照這里面的詞去進(jìn)行分詞。
  • stopword.dic:包含了英文的停用詞屋讶。

一般向停用詞會(huì)在分詞的時(shí)候,直接被干掉须教,不會(huì)建立在倒排索引中皿渗。

自定義詞庫(kù)
  • 1、自己建立詞庫(kù):每年都會(huì)涌現(xiàn)一些特殊的流行詞轻腺,網(wǎng)紅乐疆、藍(lán)瘦香菇、喊麥贬养、鬼畜等挤土,一般不會(huì)在ik的原生詞典里。
    自己補(bǔ)充自己的最新的詞語(yǔ)误算,到ik詞庫(kù)里面仰美。
    IKAnalyzer.cfg.xml文件中ext_dict標(biāo)簽中,創(chuàng)建mydict.dic儿礼。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 擴(kuò)展配置</comment>
    <!--用戶可以在這里配置自己的擴(kuò)展字典 -->
    <entry key="ext_dict">mydict.dic</entry>
     <!--用戶可以在這里配置自己的擴(kuò)展停止詞字典-->
    <entry key="ext_stopwords">mystopwords.dic</entry>
    <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
  • 2咖杂、自己建立停用詞庫(kù),比如了蚊夫、的诉字、啥、么等,可能并不想建立索引讓人家搜索壤圃。
    custom/ext_stopword.dic陵霉,已經(jīng)有了常用的中文停用詞,可以自己補(bǔ)充自己的中文停用詞伍绳,然后重啟es踊挠。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 擴(kuò)展配置</comment>
    <!--用戶可以在這里配置自己的擴(kuò)展字典 -->
    <entry key="ext_dict">mydict.dic</entry>
     <!--用戶可以在這里配置自己的擴(kuò)展停止詞字典-->
    <entry key="ext_stopwords">mystopwords.dic</entry>
    <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

使用mysql熱更新詞庫(kù)

熱更新

每次都是在es的擴(kuò)展詞典中,手動(dòng)添加新詞墨叛,很坑止毕。

  • 每次添加完,都要重啟es才能生效漠趁,非常麻煩扁凛。
  • es是分布式的可能有數(shù)百個(gè)節(jié)點(diǎn),你不能每一次都一個(gè)一個(gè)節(jié)點(diǎn)上面去修改闯传。

es不停機(jī)谨朝,我們直接在外部某個(gè)地方添加新的詞語(yǔ),es中立即熱加載到這些新詞語(yǔ)甥绿。

熱更新方案

  • 1字币、基于ik分詞器原生支持的熱更新方案,部署一個(gè)web服務(wù)器共缕,提供一個(gè)http接口洗出,通過modified和tag兩個(gè)http響應(yīng)頭,來提供詞語(yǔ)的熱更新图谷。
  • 2翩活、修改ik分詞器源碼,然后手動(dòng)支持從mysql中每隔一段時(shí)間便贵,自動(dòng)加載新的詞庫(kù)菠镇。

用第二種方案,第一種方案ik官方和社區(qū)都不建議采用承璃,覺得不太穩(wěn)定利耍。

1、基于ik分詞器原生支持的熱更新方案
  • 1)盔粹、ik官方文檔說明
    目前該插件支持熱更新 IK 分詞隘梨,通過上文在 IK 配置文件中提到的如下配置
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
<entry key="remote_ext_dict">location</entry>
<!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
<entry key="remote_ext_stopwords">location</entry>

其中 location 是指一個(gè) url,比如 http://yoursite.com/getCustomDict玻佩,該請(qǐng)求只需滿足以下兩點(diǎn)即可完成分詞熱更新出嘹。

  • A、該 http 請(qǐng)求需要返回兩個(gè)頭部(header)咬崔,一個(gè)是 Last-Modified税稼,一個(gè)是 ETag烦秩,這兩者都是字符串類型,只要有一個(gè)發(fā)生變化郎仆,該插件就會(huì)去抓取新的分詞進(jìn)而更新詞庫(kù)只祠。
  • B、該 http 請(qǐng)求返回的內(nèi)容格式是一行一個(gè)分詞扰肌,換行符用 \n 即可抛寝。

滿足上面兩點(diǎn)要求就可以實(shí)現(xiàn)熱更新分詞了,不需要重啟 ES 實(shí)例曙旭。

可以將需自動(dòng)更新的熱詞放在一個(gè) UTF-8 編碼的xxx.txt文件里盗舰,放在 nginx 或其他簡(jiǎn)易 http server下,當(dāng)xxx.txt文件修改時(shí)桂躏,http server 會(huì)在客戶端請(qǐng)求該文件時(shí)自動(dòng)返回相應(yīng)的 Last-Modified 和 ETag钻趋。可以另外做一個(gè)工具來從業(yè)務(wù)系統(tǒng)提取相關(guān)詞匯剂习,并更新這個(gè)xxx.txt文件蛮位。

個(gè)人體會(huì):nginx方式比較簡(jiǎn)單容易實(shí)現(xiàn),建議使用鳞绕;

  • 2)失仁、在服務(wù)中實(shí)現(xiàn)http請(qǐng)求,并連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)熱詞管理實(shí)例:
  • A们何、編寫http請(qǐng)求服務(wù)接口demo
@RestController
@RequestMapping("/keyWord")
@Slf4j
public class KeyWordDict {

    private String lastModified = new Date().toString();
    private String etag = String.valueOf(System.currentTimeMillis());

    @RequestMapping(value = "/hot", method = {RequestMethod.GET,RequestMethod.HEAD}, produces="text/html;charset=UTF-8")
    public String getHotWordByOracle(HttpServletResponse response,Integer type){
        response.setHeader("Last-Modified",lastModified);
        response.setHeader("ETag",etag);

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        String sql = "";
        final ArrayList<String> list = new ArrayList<String>();
        StringBuilder words = new StringBuilder();
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            conn = DriverManager.getConnection(
                    "jdbc:oracle:thin:@192.168.114.13:1521:xe",
                    "test",
                    "test"
            );
            if(ObjectUtils.isEmpty(type)){
                type = 99;
            }
            switch (type){
                case 0:
                    sql = "select word from IK_HOT_WORD where type=0 and status=0";
                    break;
                case 1:
                    sql = "select word from IK_HOT_WORD where type=1 and status=0";
                    break;
                default:
                    sql = "select word from IK_HOT_WORD where type=99";
                    break;
            }
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);

            while(rs.next()) {
                String theWord = rs.getString("word");
                System.out.println("hot word from mysql: " + theWord);
                words.append(theWord);
                words.append("\n");
            }
            return words.toString();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    log.error("資源關(guān)閉異常:",e);
                }
            }
            if(stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error("資源關(guān)閉異常:",e);
                }
            }
            if(conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error("資源關(guān)閉異常:",e);
                }
            }
        }
        return null;
    }

    @RequestMapping(value = "/update", method = RequestMethod.GET)
    public void updateModified(){
        lastModified = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
        etag = String.valueOf(System.currentTimeMillis());
    }

}

注:
updateModified方法為單獨(dú)更新lastModified與etag萄焦,用于判斷ik是否需要重新加載遠(yuǎn)程詞庫(kù),具體關(guān)聯(lián)數(shù)據(jù)庫(kù)操作代碼時(shí)自行擴(kuò)展

  • B冤竹、ik配置文件修改
    • 文件目錄:/data/elasticsearch-7.3.0/plugins/ik/config/IKAnalyzer.cfg.xml
    • 遠(yuǎn)程調(diào)用方法填寫在“用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典”下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 擴(kuò)展配置</comment>
        <!--用戶可以在這里配置自己的擴(kuò)展字典 -->
        <entry key="ext_dict"></entry>
         <!--用戶可以在這里配置自己的擴(kuò)展停止詞字典-->
        <entry key="ext_stopwords"></entry>
        <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展字典 -->
        <entry key="remote_ext_dict">http://192.168.xx.xx:8080/keyWord/hot?type=0</entry>
        <!--用戶可以在這里配置遠(yuǎn)程擴(kuò)展停止詞字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
重寫ik源碼連接mysql/oracle更新詞庫(kù)
  • 1楷扬、下載ik源碼(下載對(duì)應(yīng)版本)
    https://github.com/medcl/elasticsearch-analysis-ik/releases
    ik分詞器是一個(gè)標(biāo)準(zhǔn)的java maven工程,直接導(dǎo)入idea就可看到源碼贴见。

  • 2、修改ik插件源碼(以mysql為例)

  • 1)躲株、添加jdbc配置文件
    在項(xiàng)目根目錄下的config目錄中添加config\jdbc-reload.properties配置文件:

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/es?serverTimezone=UTC
jdbc.user=root
jdbc.password=yibo
jdbc.reload.sql=select word from hot_words
jdbc.reload.stopword.sql=select stopword as word from hot_stopwords
jdbc.reload.interval=5000
  • 2)片部、在Dictionary類的同級(jí)目錄下新建HotDictReloadThread類
/**
 * @Description: 加載字典線程
 */
public class HotDictReloadThread implements Runnable {

    private static final Logger log = ESPluginLoggerFactory.getLogger(HotDictReloadThread.class.getName());

    @Override
    public void run() {
        log.info("[--------]reload hot dict from mysql");
        Dictionary.getSingleton().reLoadMainDict();
    }
}
  • 3)、在Dictionary類initial方法中新增代碼
public static synchronized void initial(Configuration cfg) {
    if (singleton == null) {
        synchronized (Dictionary.class) {
            if (singleton == null) {

                singleton = new Dictionary(cfg);
                singleton.loadMainDict();
                singleton.loadSurnameDict();
                singleton.loadQuantifierDict();
                singleton.loadSuffixDict();
                singleton.loadPrepDict();
                singleton.loadStopWordDict();

                //!!!!!!!!mysql監(jiān)控線程  新增代碼
                new Thread(new HotDictReloadThread()).start();

                if(cfg.isEnableRemoteDict()){
                    // 建立監(jiān)控線程
                    for (String location : singleton.getRemoteExtDictionarys()) {
                        // 10 秒是初始延遲可以修改的 60是間隔時(shí)間 單位秒
                        pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
                    }
                    for (String location : singleton.getRemoteExtStopWordDictionarys()) {
                        pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
                    }
                }

            }
        }
    }
}
  • 4)霜定、在Dictionary類loadMainDict方法中新增代碼
/**
 * 加載主詞典及擴(kuò)展詞典
 */
private void loadMainDict() {
    // 建立一個(gè)主詞典實(shí)例
    _MainDict = new DictSegment((char) 0);

    // 讀取主詞典文件
    Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_MAIN);
    loadDictFile(_MainDict, file, false, "Main Dict");
    // 加載擴(kuò)展詞典
    this.loadExtDict();
    // 加載遠(yuǎn)程自定義詞庫(kù)
    this.loadRemoteExtDict();
    //從mysql中加載熱更新詞典 新增代碼
    this.loadMySQLExtDict();
}
  • 5)档悠、在Dictionary類loadStopWordDict方法中新增代碼
/**
 * 加載用戶擴(kuò)展的停止詞詞典
 */
private void loadStopWordDict() {
    // 建立主詞典實(shí)例
    _StopWords = new DictSegment((char) 0);

    // 讀取主詞典文件
    Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_STOP);
    loadDictFile(_StopWords, file, false, "Main Stopwords");

    // 加載擴(kuò)展停止詞典
    List<String> extStopWordDictFiles = getExtStopWordDictionarys();
    if (extStopWordDictFiles != null) {
        for (String extStopWordDictName : extStopWordDictFiles) {
            logger.info("[Dict Loading] " + extStopWordDictName);

            // 讀取擴(kuò)展詞典文件
            file = PathUtils.get(extStopWordDictName);
            loadDictFile(_StopWords, file, false, "Extra Stopwords");
        }
    }

    // 加載遠(yuǎn)程停用詞典
    List<String> remoteExtStopWordDictFiles = getRemoteExtStopWordDictionarys();
    for (String location : remoteExtStopWordDictFiles) {
        logger.info("[Dict Loading] " + location);
        List<String> lists = getRemoteWords(location);
        // 如果找不到擴(kuò)展的字典,則忽略
        if (lists == null) {
            logger.error("[Dict Loading] " + location + "加載失敗");
            continue;
        }
        for (String theWord : lists) {
            if (theWord != null && !"".equals(theWord.trim())) {
                // 加載遠(yuǎn)程詞典數(shù)據(jù)到主內(nèi)存中
                logger.info(theWord);
                _StopWords.fillSegment(theWord.trim().toLowerCase().toCharArray());
            }
        }
    }

    //!!!!!!!!從mysql中加載停用詞 新增代碼
    this.loadMySQLStopWordDict();
}
  • 6)望浩、在Dictionary類新增靜態(tài)代碼塊辖所,加載db驅(qū)動(dòng)
private static Properties prop = new Properties();

static {
    try {
        Class.forName("com.mysql.cj.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        logger.error("error",e);
    }
}
  • 7)、在Dictionary類新增loadMySQLExtDict方法磨德,從mysql中加載熱更新詞典
/**
 * 從mysql中加載熱更新詞典
 */
private void loadMySQLExtDict(){
    Connection conn = null;
    Statement state = null;
    ResultSet rs = null;
    try{
        Path file = PathUtils.get(getDictRoot(),"jdbc-reload.properties");
        prop.load(new FileInputStream(file.toFile()));
        for (Object key : prop.keySet()) {
            logger.info("[--------]" + key +"=" + prop.getProperty(String.valueOf(key)));
        }
        logger.info("[--------]query hot dict from mysql," + prop.getProperty("jdbc.reload.sql") + "......");

        // 創(chuàng)建數(shù)據(jù)連接
        conn = DriverManager.getConnection(
                prop.getProperty("jdbc.url"),
                prop.getProperty("jdbc.user"),
                prop.getProperty("jdbc.password")
        );

        state = conn.createStatement();
        rs = state.executeQuery(prop.getProperty("jdbc.reload.sql"));

        while(rs.next()){
            String theWord = rs.getString("word");
            logger.info("[--------]hot word from mysql: " + theWord);
            _MainDict.fillSegment(theWord.trim().toCharArray());
        }

        Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval"))));
    }catch (Exception e){
        logger.error("error",e);
    }finally {
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
        if(state != null){
            try {
                state.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
    }
}
  • 8)缘回、在Dictionary類新增loadMySQLStopWordDict方法吆视,從mysql中加載停用詞
/**
 * 從mysql中加載停用詞
 */
private void loadMySQLStopWordDict(){
    Connection conn = null;
    Statement state = null;
    ResultSet rs = null;
    try{
        Path file = PathUtils.get(getDictRoot(),"jdbc-reload.properties");
        prop.load(new FileInputStream(file.toFile()));
        for (Object key : prop.keySet()) {
            logger.info("[--------]" + key +"=" + prop.getProperty(String.valueOf(key)));
        }
        logger.info("[--------]query hot stopword from mysql," + prop.getProperty("jdbc.reload.stopword.sql") + "......");

        // 創(chuàng)建數(shù)據(jù)連接
        conn = DriverManager.getConnection(
                prop.getProperty("jdbc.url"),
                prop.getProperty("jdbc.user"),
                prop.getProperty("jdbc.password")
        );

        state = conn.createStatement();
        rs = state.executeQuery(prop.getProperty("jdbc.reload.stopword.sql"));

        while(rs.next()){
            String theWord = rs.getString("word");
            logger.info("[--------]hot stopword from mysql: " + theWord);
            _MainDict.fillSegment(theWord.trim().toCharArray());
        }

        Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval"))));
    }catch (Exception e){
        logger.error("error",e);
    }finally {
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
        if(state != null){
            try {
                state.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                logger.error("error",e);
            }
        }
    }
}
  • 3、mvn package打包代碼

  • 4酥宴、解壓ik壓縮包
    將mysql驅(qū)動(dòng)jar啦吧,放入ik目錄下。

  • 5拙寡、修改jdbc相關(guān)配置

  • 6授滓、重啟es
    觀察日志,日志中會(huì)顯示打印那些東西肆糕,比如加載了什么配置般堆,加載了什么詞語(yǔ),加載了什么停用詞诚啃。

  • 7淮摔、在MySQL中添加詞庫(kù)與停用詞

  • 8、分詞驗(yàn)證绍申,驗(yàn)證熱更新

總結(jié):

  • 一般不需要特別指定查詢時(shí)分詞器噩咪,直接使用索引時(shí)分詞器即可,否則會(huì)出現(xiàn)無法匹配的情況极阅。
  • 明確字段是否需要分詞胃碾,不需要分詞的字段就將type設(shè)置為keyword,可以節(jié)省空間和提高寫性能筋搏。
  • 善用_analyze API仆百,查看文檔的具體分詞結(jié)果。

參考:
https://github.com/medcl/elasticsearch-analysis-ik

https://blog.csdn.net/qq_40592041/article/details/107856588

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奔脐,一起剝皮案震驚了整個(gè)濱河市俄周,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌髓迎,老刑警劉巖峦朗,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異排龄,居然都是意外死亡波势,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門橄维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尺铣,“玉大人,你說我怎么就攤上這事争舞×莘蓿” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵竞川,是天一觀的道長(zhǎng)店溢。 經(jīng)常有香客問我叁熔,道長(zhǎng),這世上最難降的妖魔是什么逞怨? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任者疤,我火速辦了婚禮,結(jié)果婚禮上叠赦,老公的妹妹穿的比我還像新娘驹马。我一直安慰自己,他們只是感情好除秀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布糯累。 她就那樣靜靜地躺著,像睡著了一般册踩。 火紅的嫁衣襯著肌膚如雪泳姐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天暂吉,我揣著相機(jī)與錄音胖秒,去河邊找鬼。 笑死慕的,一個(gè)胖子當(dāng)著我的面吹牛阎肝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肮街,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼风题,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嫉父?” 一聲冷哼從身側(cè)響起沛硅,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绕辖,沒想到半個(gè)月后摇肌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仪际,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年朦蕴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弟头。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涉茧,靈堂內(nèi)的尸體忽然破棺而出赴恨,到底是詐尸還是另有隱情,我是刑警寧澤伴栓,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布伦连,位于F島的核電站雨饺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惑淳。R本人自食惡果不足惜额港,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歧焦。 院中可真熱鬧移斩,春花似錦、人聲如沸绢馍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舰涌。三九已至猖任,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓷耙,已是汗流浹背朱躺。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搁痛,地道東北人长搀。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像落追,于是被迫代替她去往敵國(guó)和親盈滴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348