自定義查詢--關於倒排索引的研究

来源:https://www.cnblogs.com/machine-matrix/archive/2022/07/30/16534476.html
-Advertisement-
Play Games

最近學習了es的視頻,感覺這個產品對於查詢來說非常方便,但是如何應用到我們自己的 產品中來呢。因為我們的產品數據更新太快,其實不太適合用es做主力存儲。並且我們的業務還沒有到那種巨量級別,產品的伺服器容量也有限,所以我打算根據es的倒排索引的原理,自己寫一個查詢的組件。 我的理解是這樣的,有大量的文 ...


最近學習了es的視頻,感覺這個產品對於查詢來說非常方便,但是如何應用到我們自己的 產品中來呢。因為我們的產品數據更新太快,其實不太適合用es做主力存儲。並且我們的業務還沒有到那種巨量級別,產品的伺服器容量也有限,所以我打算根據es的倒排索引的原理,自己寫一個查詢的組件。

我的理解是這樣的,有大量的文字需要進行模糊查詢,在mysql中,如果使用like的話是非常合適的,目前我就是採用這種方式查詢的,因為數據量還未到千萬級別,速度也還行,不過馬上要突破了,所以要考慮優化的事情了。所以我的思路是這樣的:

1 首先將資料庫中的大段文字和標題都提取出來。

2 這些文字都對應了主鍵。

3 使用jcseg分詞將一段文字進行分詞,然後將分好的詞語主鍵保存到redis中去。

4 為了節省空間,只分重要的業務關鍵字,其他無關的分詞都不需要。

5 因為數據量巨大,在進行數據提取的時候,採用了線程池,優化了採集速度。

 

使用的代碼如下:

package com.liandyao.caop.caopdata.service.impl.ESearch;

import cn.hutool.core.util.StrUtil;
import com.liandyao.caop.caopdata.entity.CaiCaop;
import com.liandyao.caop.caopdata.mapper.CaiMapper;
import com.liandyao.caop.utils.ChineseSegment;
import com.liandyao.caop.utils.async.AsyncManager;
import com.liandyao.caop.utils.redis.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 倒排索引的研究
 * @author liandyao
 * @date 2022年6月24日
 */
@Service
public class CaiInvertedIndex {

    /**
     * 原子類型的數字
     */
    public static AtomicInteger atomic = new AtomicInteger();


    /**
     * 每頁查詢多少條
     */
    public static int SIZE = 1000 ;

    /**
     * KEY
     */
    public static String REDIS_KEY = "INVD_INDX:MYSQL_DATA:CAOPU";

    @Autowired
    CaipMapper caopMapper;

    @Autowired
    RedisUtil redisUtil ;

    /**
     * 同步數據到redis
     */
    @Transactional
    public void sysnCaopDataToRedis(int pages){
        System.out.println(pages);
        //每頁顯示1000條

        int startRows = (pages - 1) * SIZE ;
        List<CaiCaop> listCaop =caopMapper.selectListByPage(startRows,SIZE);

        System.out.println("查詢的條數:"+listCaop.size());
        listCaop.forEach(caop->{
            //加入到redis中
            redisUtil.leftPush(REDIS_KEY,caop);
            //最後一個執行的id,因為多線程的原因可能不是最後一個,這裡只是記錄一下
            redisUtil.set(REDIS_KEY+"LAST_ID",caop.getId());
        });
    }

    /**
     * 加入分詞信息
     */
    public void segCaopData(){
        long caopSize = redisUtil.lGetListSize(REDIS_KEY);
        System.out.println("正在處理,共有:"+caopSize+"條數據");
        int i = 0;
        while(i<caopSize){
            //運行一次增加1
            i++;
            AsyncManager.me().execute(new TimerTask() {
                @Override
                public void run() {
                    CaiCaop caop = (CaiCaop) redisUtil.rightPop(REDIS_KEY);
                    if(caop!=null){
                        String content = caop.getContent()+" "+caop.getAddress();
                        List<String> typeNames = StrUtil.split(caop.getTypeName(),",");

                        //先將種類作為倒序索引加入redis
                        typeNames.forEach(str->{
                            if(StrUtil.isNotBlank(str)){
                                redisUtil.zsetAdd(REDIS_KEY+":"+str,caop.getId(),caop.getUpdateDate().getTime());
                            }

                        });
                        //再進行分詞
                        List<String> list = ChineseSegment.segment(content);
                        list.forEach(segWord->{
                            redisUtil.zsetAdd(REDIS_KEY+":"+segWord,caop.getId(),caop.getUpdateDate().getTime());
                        });
                        System.out.println("處理成功:"+caop.getId());
                    }
                }
            });
        }



    }


    public static void main(String[] args) {


    }

}

 

中文分詞代碼

package com.liandyao.caop.utils;

import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.lionsoul.jcseg.ISegment;
import org.lionsoul.jcseg.IWord;
import org.lionsoul.jcseg.dic.ADictionary;
import org.lionsoul.jcseg.dic.DictionaryFactory;
import org.lionsoul.jcseg.extractor.impl.TextRankKeyphraseExtractor;
import org.lionsoul.jcseg.extractor.impl.TextRankKeywordsExtractor;
import org.lionsoul.jcseg.segmenter.SegmenterConfig;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * 中文分詞
 * @author liandyao
 * @date 2021/12/10 19:16
 */
@Slf4j
public  class  ChineseSegment {
    static  ISegment seg=null;

    /**
     * 初始化
     */
    public static synchronized void init(){
        seg=null;
        SegmenterConfig config = new SegmenterConfig(true);
        String lexicon[] = {"e://lexicon"};
        config.setLexiconPath(lexicon);
        ADictionary dic = DictionaryFactory.createSingletonDictionary(config);

        seg = ISegment.COMPLEX.factory.create(config, dic);

    }

    static{
        init();
    }

    /**
     * 取地方分詞結果
     * @param str
     * @return
     */
    public static synchronized List<String> segmentAddress(String str){

        List<String> list = new ArrayList<>();
        try {

            seg.reset(new StringReader(str));

            //System.out.println("====>"+str+"  ==> "+seg);
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {
                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    //處所詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ns")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                }
            }
        } catch (Exception e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 分析工種分詞結果
     * @param str
     * @return
     */
    public static synchronized List<String> segmentCaitype(String str){
        List<String> list = new ArrayList<>();
        try {
            seg.reset(new StringReader(str));
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {

                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    //工種分詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ncn")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                }

            }
        } catch (IOException e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 綜合分詞
     * @param str
     * @return
     */
    public static synchronized List<String> segment(String str){
        List<String> list = new ArrayList<>();
        try {
            seg.reset(new StringReader(str));
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {
                /**


                 名詞n、時間詞t、處所詞s、方位詞f、數詞m、量詞q、區別詞b、代詞r、動詞v、形容詞a、狀態詞z、副詞d、
                 介詞p、連詞c、助詞u、語氣詞y、嘆詞e、擬聲詞o、成語i、習慣用語l、簡稱j、前接成分h、後接成分k、語素g、非語素字x、標點符號w)外,
                 從語料庫應用的角度,增加了專有名詞(人名nr、地名ns、機構名稱nt、其他專有名詞nz)



                 */
                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    /*

                    //工種分詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ncn")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                    //處所詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ns")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }

                    */

                    if(word.getValue().length()>=2){
                        list.add(word.getValue());
                    }

                }

            }
        } catch (IOException e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    public static void main(String[] args) {
        //設置要分詞的內容
        String str = "今天真是陽光明媚,夕陽西下,白日依山盡!";

            int i = 0 ;

        List<String> keywords = segment(str);

        keywords.forEach(str1 -> {
            System.out.println(str1);
        });


    }

    @Test
    public void test1() throws IOException {
        String str = "四川成都市長期招工信息:招2個砌築工維修師傅,少數民族勿擾,1個力工,電話微信同步,活在成都";
     str
=""; //2, 構建TextRankKeywordsExtractor關鍵字提取器 TextRankKeywordsExtractor extractor = new TextRankKeywordsExtractor(seg); //extractor.setMaxIterateNum(100); //設置pagerank演算法最大迭代次數,非必須,使用預設即可 //extractor.setWindowSize(5); //設置textRank計算視窗大小,非必須,使用預設即可 // extractor.setKeywordsNum(10); //設置最大返回的關鍵詞個數,預設為10 List<String> keywords = extractor.getKeywords(new StringReader(str)); keywords.forEach(str2 -> System.out.println(str2)); } @Test public void test2() throws IOException { //String str = " 四川成都市長期招工信息:招2個砌築工維修師傅,少數民族勿擾,1個力工,電話微信同步, "; //2, 構建TextRankKeyphraseExtractor關鍵短語提取器 TextRankKeyphraseExtractor extractor = new TextRankKeyphraseExtractor(seg); extractor.setMaxIterateNum(100); //設置pagerank演算法最大迭代詞庫,非必須,使用預設即可 extractor.setWindowSize(5); //設置textRank視窗大小,非必須,使用預設即可 extractor.setKeywordsNum(15); //設置最大返回的關鍵詞個數,預設為10 extractor.setMaxWordsNum(4); //設置最大短語詞長,預設為5 //3, 從一個輸入reader輸入流中獲取短語 String str = "支持向量機廣泛應用於文本挖掘,例如,基於支持向量機的文本自動分類技術研究一文中很詳細的介紹支持向量機的演算法細節,文本自動分類是文本挖掘技術中的一種!"; List<String> keyphrases = extractor.getKeyphrase(new StringReader(str)); keyphrases.forEach(str2 -> System.out.println(str2)); } }

 

結語

1 本文提供了倒排索引的思路,比較淺顯,還可以深入研究

2 使用本組件將關鍵字放入redis之後,頁面上傳入的關鍵字就可以在redis中對應key,這樣的速度將非常快,從key中可以找到主鍵,再用主鍵到mysql中查詢,大大提高了查詢速度。

3 需要考慮的問題,如何做到更新就加入關鍵字到redis中去。是採用實時變更就加入,還是定時一分鐘,或者一小時加入,需要結合業務來處理。

 


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

-Advertisement-
Play Games
更多相關文章
  • 1、引例 【例1】 分析該程式,有哪些問題 int main() { swap(int p, int q); int a = 10, b = 20; printf("(1)a = %d, b = %d\n", a, b); swap(&a, &b); printf("(2)a = %d, b = % ...
  • 精華筆記: 1. 運算符: - 算術:+、-、*、/、%、++、-- - 關係:>、<、>=、<=、==、!= - 邏輯:&&、||、! - 賦值:=、+=、-=、*=、/=、%= - 字元串連接:+ - 條件/三目:boolean?數1:數2 2. 分支結構:基於條件執行的語句 - if結構:1條 ...
  • 精華筆記: 1. 變數:存數的 - 聲明: 在銀行開了個帳戶 - 初始化: 給帳戶存錢 - 使用: 使用的是帳戶裡面的錢 - 對變數的使用就是對它所存的那個數的使用 - 變數在使用之前必須聲明並初始化 - 命名: - 只能包含字母、數字、_和$符,並且不能以數字開頭 - 嚴格區分大小寫 - 不能使用 ...
  • 一、python簡介:是什麼,特點是什麼,有什麼用 1、python是什麼? python是一門結合解釋型,編譯性,互動性和麵向對象的腳本語言,具有很強的可讀性,相比其他例如Java語言,C語言更加容易入門。 2、python有哪些特點? 易於學習:python有相對較少的關鍵字,結構簡單,和一個明 ...
  • JetBrAIns GoLand 是Mac os系統上由JetBrAIns推出的一個GO語言集成開發工具環境,基於IntelliJ平臺,支持JetBrAIns插件體系,擁有針對GO語言的代碼助手、代碼編輯器、代碼調試等工具,支持前端和後端開發,並且支持IntelliJ插件,可以大大提高Go語言開發者 ...
  • 配置不同生產環境 本文適用於開發環境下需要打包項目至生產環境,避免開發環境的配置文件泄露。 設置maven 作用:1. 手動調節運行時的不同環境 2. 打包時可以不會有其它環境的文件 註:每次換環境前(打包前)記得手動clean清楚,因為idea不會在換環境後自動清除另一個環境的文件 在pom文件中 ...
  • 介紹 本篇文章主要針對於電腦二級考試的崽崽,當然想瞭解Python和學習Python的崽崽也是可以看本篇文章的;畢竟,手機和電腦都可以運行Python;本篇我文章雖然是筆記,但是也純靠手打,希望關註和點贊一下,期待我的其他隨筆和文章;文章作者由博客園狐小妖用戶撰寫,非來自於博客園且不帶轉載註明,均 ...
  • 前言:最近幾個月很忙,都沒有時間寫文章了,今天周末剛好忙完下班相對早點(20:00下班)就在家把之前想總結的知識點寫出來,於是就有了這篇文章。雖無很高深的技術,但小技巧有大用處。 有時我們經常需要將實現了某個基類或某個介面的所有Bean進行分類管理,在需要用到的時候按需獲取實現了某個基類或某個介面的 ...
一周排行
    -Advertisement-
    Play Games
  • 1.部署歷史 猿友們好,作為初來實習的我,已經遭受社會的“毒打”,所以請容許我在下麵環節適當吐槽,3Q! 傳統部署 ​ 回顧以往在伺服器部署webapi項目(非獨立發佈),dotnet環境、守護進程兩個逃都逃不掉,正常情況下還得來個nginx代理。不僅僅這仨,可能牽扯到yum或npm。node等都要 ...
  • 隨著技術的進步,跨平臺開發已經成為了標配,在此大背景下,ASP.NET Core也應運而生。本文主要基於ASP.NET Core+Element+Sql Server開發一個校園圖書管理系統為例,簡述基於MVC三層架構開發的常見知識點,前一篇文章,已經簡單介紹瞭如何搭建開發框架,和登錄功能實現,本篇... ...
  • 這道題只要會自定義cmp恰當地進行排序,其他部分沒有什麼大問題。 上代碼: 1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,s,h1,h2,cnt; 4 struct apple{ 5 int height,ns;//height為蘋 ...
  • 這篇文章主要描述RPC的路由策略,包括為什麼需要請求隔離,為什麼不在註冊中心中實現請求隔離以及不同粒度的路由策略。 ...
  • 簡介: 中介者模式,屬於行為型的設計模式。用一個中介對象來封裝一系列的對象交互。中介者是各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變他們之間的交互。 適用場景: 如果平行對象間的依賴複雜,可以使用中介者解耦。 優點: 符合迪米特法則,減少成員間的依賴。 缺點: 不適用於系統出現對 ...
  • 【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
  • 簡介: 享元模式,屬於結構型的設計模式。運用共用技術有效地支持大量細粒度的對象。 適用場景: 具有相同抽象但是細節不同的場景中。 優點: 把公共的部分分離為抽象,細節依賴於抽象,符合依賴倒轉原則。 缺點: 增加複雜性。 代碼: //用戶類 class User { private $name; fu ...
  • 這次設計一個通用的多位元組SPI介面模塊,特點如下: 可以設置為1-128位元組的SPI通信模塊 可以修改CPOL、CPHA來進行不同的通信模式 可以設置輸出的時鐘 狀態轉移圖和思路與多位元組串口發送模塊一樣,這裡就不給出了,具體可看該隨筆。 一、模塊代碼 1、需要的模塊 通用8位SPI介面模塊 `tim ...
  • AOP-03 7.AOP-切入表達式 7.1切入表達式的具體使用 1.切入表達式的作用: 通過表達式的方式定義一個或多個具體的連接點。 2.語法細節: (1)切入表達式的語法格式: execution([許可權修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]) 若目標類、介面與 ...
  • 測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...