国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

elasticsearch 實(shí)現(xiàn)聯(lián)想輸入搜索

2019-11-09 20:17:58
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

通常,在項(xiàng)目中需要聯(lián)想輸入(即輸入關(guān)鍵字,提示相關(guān)詞條,類似百度google的搜索)的需求,可能大家都是用的數(shù)據(jù)庫(kù)的like '%關(guān)鍵字%‘來(lái)實(shí)現(xiàn)。但是這樣實(shí)現(xiàn)有幾個(gè)問(wèn)題。

第一、這樣的搜索無(wú)論是Oracle還是MySQL,都是無(wú)法使用索引的。在oracle中可能有全文檢索可以使用,但是個(gè)人感覺(jué)效果不是很好。

第二、輸入的關(guān)鍵字有l(wèi)ike的通病,就是只有保含關(guān)鍵字的詞條才會(huì)被命中。如果中間加個(gè)空格之類的,db就無(wú)能為力了。

第三、如果要想對(duì)命中結(jié)果進(jìn)行相關(guān)度排序,這個(gè)在常規(guī)數(shù)據(jù)庫(kù)是無(wú)法做到的。雖然,可以按照命中詞條的長(zhǎng)度進(jìn)行升序排序,但是加上排序,性能不是很好。

下面介紹一下使用elasticsearch實(shí)現(xiàn)聯(lián)想輸入的搜索,因?yàn)槭撬阉饕妫焐筒痪邆渖厦娴?個(gè)問(wèn)題。

在具體介紹使用方法之前,我們先找個(gè)搜索數(shù)據(jù)。我找的是ICD(就是疾病名稱的國(guó)標(biāo)),誰(shuí)讓咱一生都在跟他做斗爭(zhēng)。這個(gè)在網(wǎng)上一搜一堆。

有了數(shù)據(jù),我們先要簡(jiǎn)單描述一下我們要達(dá)到的一個(gè)目的。一般的搜索都支持漢字 和拼音兩種檢索方法。我們的這個(gè)檢索也滿足這個(gè)需求。

搜索需求描述:

1、支持漢字和簡(jiǎn)拼兩種搜索方法。

2、輸入“高血壓”時(shí),按照相關(guān)度,將帶“高血壓”名稱的疾病名稱按照相關(guān)度降序排序。

3、輸入“老年 高血壓”,時(shí),將帶“老年”和“高血壓”名稱的疾病名稱按照相關(guān)度降序排序。

4、輸入拼音'gxy‘時(shí),將拼音中帶有g(shù)xy相關(guān)的疾病按照相關(guān)度降序排序。

....

類似測(cè)試用例的需求,到此打住。

那么,我們一步一步實(shí)現(xiàn)這種需求。

首先,我們定義了一個(gè)ICD的類,算作我們的模型,其實(shí)沒(méi)有模型也可以,只要存入到es且知道各個(gè)field的名稱就行。這個(gè)里面我們只需要關(guān)注疾病名稱diseaseName及簡(jiǎn)拼pinyin字段即可,這個(gè)字段默認(rèn)是字符串,ES默認(rèn)會(huì)幫我們分詞。

java代碼

import java.io.Serializable;  

import java.math.BigDecimal;  

/**

* ICD抽象對(duì)象

* @author donlianli@126.com

*/  

public class ICD implements Serializable{  

   PRivate static final long serialVersionUID = 6934803011248581109L;  

   //疾病ID  

   private int id;  

   //疾病編碼  

   private String code;  

   //疾病名稱  

   private String diseaseName;  

   //疾病加拼音  

   private String mergeName;  

   //漢語(yǔ)拼音簡(jiǎn)拼  

   private String pinyin;  

   //是否惡心腫瘤  

   private boolean isTherioma;  

   //是否住院特殊病種  

   private boolean isSpecialDisease;  

     

   public ICD(BigDecimal id, String diseaseName, String code,  

           String pinyin, String isTherioma, String isSpecialDisease) {  

       this.id = id.intValue();  

       this.diseaseName = diseaseName;  

       this.code = code;  

       this.pinyin = pinyin;  

       if("是".equals(isTherioma)){  

           this.isTherioma = true;  

       }  

       else {  

           this.isTherioma = false;  

       }  

         

       if("是".equals(isSpecialDisease)){  

           this.isSpecialDisease = true;  

       }  

       else {  

           this.isSpecialDisease = false;  

       }  

       this.mergeName = diseaseName + "," + pinyin;  

   }  

   //set,get ......  

     

}  

第二步,將數(shù)據(jù)存儲(chǔ)到elasticsearch里面,我們?nèi)€(gè)名稱叫code,起個(gè)type名稱叫icd。ICD大概2w條數(shù)據(jù),我使用默認(rèn)的bulkIndex,存到es大概用了3秒。

我這里是把數(shù)據(jù)從oracle導(dǎo)入到elasticsearch。

Java代碼

import java.math.BigDecimal;  

import java.sql.Connection;  

import java.sql.PreparedStatement;  

import java.sql.ResultSet;  

import java.util.ArrayList;  

import java.util.List;  

 

import org.elasticsearch.action.bulk.BulkRequestBuilder;  

import org.elasticsearch.action.bulk.BulkResponse;  

import org.elasticsearch.action.index.IndexRequestBuilder;  

import org.elasticsearch.client.Client;  

 

import com.donlianli.es.ESUtils;  

import com.donlianli.es.db.DatabaseUtils;  

 

public class ICDManager {  

     

   public static void main(String[] argvs){  

       ICDManager manager = new ICDManager();  

       manager.indexDataDirect();  

   }  

   /**

    * 直接將數(shù)據(jù)初始化到ES中

    * 不創(chuàng)建mapping

    */  

   private void indexDataDirect() {  

       List<ICD> icdList = getIcdListFromDB();    

       System.out.println(" get icd from db finish,size:" + icdList.size());  

       bulkIndex(icdList);  

   }  

     

   private void bulkIndex(List<ICD> icdList) {  

       Client client = ESUtils.getCodeClient();  

       BulkRequestBuilder bulkRequest = client.prepareBulk();  

       long b = System.currentTimeMillis();  

       for(int i=0,l=icdList.size();i<l;i++){  

           //業(yè)務(wù)對(duì)象  

           ICD icd = icdList.get(i);  

           String json = ESUtils.toJson(icd);  

           IndexRequestBuilder indexRequest = client.prepareIndex("code","icd")  

           .setSource(json).setId(String.valueOf(icd.getId()));  

           //添加到builder中  

           bulkRequest.add(indexRequest);  

       }  

       BulkResponse bulkResponse = bulkRequest.execute().actionGet();  

       if (bulkResponse.hasFailures()) {  

           System.out.println(bulkResponse.buildFailureMessage());  

       }  

       long useTime = System.currentTimeMillis()-b;  

       System.out.println("useTime:" + useTime);  

   }  

   private List<ICD> getIcdListFromDB() {  

       Connection conn = DatabaseUtils.getOracleConnection();  

       String sql = "select * from icd_11";  

       PreparedStatement st = null;  

       ResultSet rs = null;  

       List<ICD> list = new ArrayList<ICD>();  

       try{  

           st = conn.prepareStatement(sql);  

           rs = st.executeQuery();  

           while(rs.next()){  

               BigDecimal id = rs.getBigDecimal("ID");  

               String diseaseName = rs.getString("DISEASE_NAME");  

               String code = rs.getString("CODE");  

               String pinyin = rs.getString("PINYIN");  

               String isTherioma = rs.getString("THERIOMA_FLAG");  

               String isSpecialDisease = rs.getString("OTHER_FLAG");  

                 

               list.add(new ICD(id,diseaseName,code,pinyin,isTherioma,isSpecialDisease));  

           }  

             

           return list;  

       }  

       catch(Exception e){  

           e.printStackTrace();  

       }  

       finally{  

           try{  

           if(rs!= null){  

               rs.close();  

           }  

           if(st!= null){  

               st.close();  

           }  

           conn.close();  

           }  

           catch(Exception e){  

               e.printStackTrace();  

           }  

       }  

       return null;  

   }  

}  

第三步,搜索接口,跑測(cè)試用例。

Java代碼

import org.elasticsearch.action.search.SearchResponse;  

import org.elasticsearch.client.Client;  

import org.elasticsearch.index.query.MultiMatchQueryBuilder;  

import org.elasticsearch.index.query.QueryBuilders;  

import org.elasticsearch.search.SearchHit;  

import org.elasticsearch.search.SearchHits;  

 

import com.donlianli.es.ESUtils;  

 

public class PinyinSearchTest {  

   public static void main(String[] args) {  

       Client client = ESUtils.getCodeClient();  

       String keyWord = "高血壓";  

//      String keyWord = "老年 高血壓";  

//      String keyWord = "gxy";  

       //多個(gè)字段匹配  

       MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyWord, "diseaseName","pinyin");  

         

       long b = System.currentTimeMillis();  

       SearchResponse response = client.prepareSearch("code").setTypes("icd")  

               .setQuery(query)  

               .setFrom(0)  

               //前20個(gè)  

               .setSize(20)  

               .execute().actionGet();  

       long useTime = System.currentTimeMillis()-b;  

       System.out.println("search use time:" + useTime + " ms");  

         

       SearchHits shs = response.getHits();  

       for (SearchHit hit : shs) {  

           System.out.println("分?jǐn)?shù):"  

                   + hit.getScore()  

                   + ",ID:"  

                   + hit.getId()  

                   + ", 疾病名稱:"  

                   + hit.getSource().get("diseaseName")  

                   + ",拼音:" + hit.getSource().get("pinyin"));  

       }  

       client.close();  

   }  

}  

3.1,關(guān)鍵字:'高血壓'

search use time:174 ms分?jǐn)?shù):2.3859928,ID:6904, 疾病名稱:高血壓病,拼音:gxyb分?jǐn)?shù):2.136423,ID:6907, 疾病名稱:高血壓I期,拼音:gxyyq分?jǐn)?shù):2.12253,ID:6908, 疾病名稱:高血壓Ⅱ期,拼音:gxyeq分?jǐn)?shù):2.12253,ID:6910, 疾病名稱:高血壓危象,拼音:gxywx分?jǐn)?shù):2.0906634,ID:6917, 疾病名稱:腎性高血壓,拼音:sxgxy分?jǐn)?shù):2.0877438,ID:6909, 疾病名稱:高血壓Ⅲ期,拼音:gxysq分?jǐn)?shù):2.0821526,ID:18767, 疾病名稱:高原性高血壓,拼音:gyxgxy分?jǐn)?shù):1.9905697,ID:6906, 疾病名稱:惡性高血壓,拼音:exgxy分?jǐn)?shù):1.9510978,ID:7260, 疾病名稱:高血壓腦出血,拼音:gxyncx分?jǐn)?shù):1.9078629,ID:6923, 疾病名稱:腎血管性高血壓,拼音:sxgxgxy分?jǐn)?shù):1.8312198,ID:6914, 疾病名稱:高血壓性腎病,拼音:gxyxsb分?jǐn)?shù):1.8193114,ID:7367, 疾病名稱:高血壓性腦病,拼音:gxyxnb分?jǐn)?shù):1.8193114,ID:13470, 疾病名稱:妊娠引起高血壓,拼音:rsyqgxy分?jǐn)?shù):1.7919972,ID:6905, 疾病名稱:臨界性高血壓,拼音:ljxgxy分?jǐn)?shù):1.7919972,ID:6912, 疾病名稱:高血壓性心臟病,拼音:gxyxxzb分?jǐn)?shù):1.7894946,ID:6928, 疾病名稱:繼發(fā)性高血壓,拼音:jfxgxy分?jǐn)?shù):1.7062025,ID:6913, 疾病名稱:高血壓性腎衰竭,拼音:gxyxssj分?jǐn)?shù):1.7062025,ID:13485, 疾病名稱:孕產(chǎn)婦高血壓,拼音:ycfgxy分?jǐn)?shù):1.7062025,ID:14534, 疾病名稱:新生兒高血壓,拼音:xsegxy分?jǐn)?shù):1.7062025,ID:16181, 疾病名稱:應(yīng)激性高血壓,拼音:yjxgxy

3.2關(guān)鍵字:'老年 高血壓'

search use time:144 ms分?jǐn)?shù):1.1089094,ID:6904, 疾病名稱:高血壓病,拼音:gxyb分?jǐn)?shù):0.99291986,ID:6907, 疾病名稱:高血壓I期,拼音:gxyyq分?jǐn)?shù):0.9864628,ID:6908, 疾病名稱:高血壓Ⅱ期,拼音:gxyeq分?jǐn)?shù):0.9864628,ID:6910, 疾病名稱:高血壓危象,拼音:gxywx分?jǐn)?shù):0.9716526,ID:6917, 疾病名稱:腎性高血壓,拼音:sxgxy分?jǐn)?shù):0.97029567,ID:6909, 疾病名稱:高血壓Ⅲ期,拼音:gxysq分?jǐn)?shù):0.96769714,ID:18767, 疾病名稱:高原性高血壓,拼音:gyxgxy分?jǐn)?shù):0.9251333,ID:6906, 疾病名稱:惡性高血壓,拼音:exgxy分?jǐn)?shù):0.9067884,ID:7260, 疾病名稱:高血壓腦出血,拼音:gxyncx分?jǐn)?shù):0.8866946,ID:6923, 疾病名稱:腎血管性高血壓,拼音:sxgxgxy分?jǐn)?shù):0.8510741,ID:6914, 疾病名稱:高血壓性腎病,拼音:gxyxsb分?jǐn)?shù):0.8455395,ID:7367, 疾病名稱:高血壓性腦病,拼音:gxyxnb分?jǐn)?shù):0.8455395,ID:13470, 疾病名稱:妊娠引起高血壓,拼音:rsyqgxy分?jǐn)?shù):0.8328451,ID:6905, 疾病名稱:臨界性高血壓,拼音:ljxgxy分?jǐn)?shù):0.8328451,ID:6912, 疾病名稱:高血壓性心臟病,拼音:gxyxxzb分?jǐn)?shù):0.831682,ID:6928, 疾病名稱:繼發(fā)性高血壓,拼音:jfxgxy分?jǐn)?shù):0.8074301,ID:6820, 疾病名稱:老年耳聾,拼音:lnel分?jǐn)?shù):0.80348647,ID:7612, 疾病名稱:老年痣,拼音:lnz分?jǐn)?shù):0.7929714,ID:6913, 疾病名稱:高血壓性腎衰竭,拼音:gxyxssj分?jǐn)?shù):0.7929714,ID:13485, 疾病名稱:孕產(chǎn)婦高血壓,拼音:ycfgxy

高血壓和老年的相關(guān)并都出來(lái)了。只可惜老年高血壓,沒(méi)有列入ICD.

3.3拼音:'gxy'

呃?怎么沒(méi)有出來(lái)?

這個(gè)問(wèn)題折騰了我一天。一開(kāi)始我以為是被es列入了禁用詞。后來(lái),找到是因?yàn)闆](méi)有設(shè)置analyzer導(dǎo)致,在設(shè)analyzer的過(guò)程中竟然還犯了好幾個(gè)低級(jí)錯(cuò)誤,導(dǎo)致我非常懷疑設(shè)置analyzer是否管用。

這個(gè)問(wèn)題涉及到分詞,而分詞我還沒(méi)有好好研究過(guò)。總之,在創(chuàng)建索引及mapping的時(shí)候,指定一個(gè)analyzer就可以解決這個(gè)問(wèn)題。

創(chuàng)建index及mapping的代碼如下:

Java代碼

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;  

 

import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;  

import org.elasticsearch.client.Client;  

import org.elasticsearch.common.settings.ImmutableSettings;  

import org.elasticsearch.common.settings.ImmutableSettings.Builder;  

import org.elasticsearch.common.xcontent.XContentBuilder;  

 

import com.donlianli.es.ESUtils;  

/**

* 創(chuàng)建code的mapping

* @author donlianli@126.com

*/  

public class CodeMappingTest {  

   static final String INDEX_NAME="code";  

   static final String TYPE_NAME="icd";  

     

   public static void  main(String[] argv) throws Exception{  

       Client client = ESUtils.getCodeClient();  

       Builder settings = ImmutableSettings.settingsBuilder()  

               .loadFromSource(getAnalysisSettings());  

       //首先創(chuàng)建索引庫(kù)  

       CreateIndexResponse  indexresponse = client.admin().indices()  

       //這個(gè)索引庫(kù)的名稱還必須不包含大寫字母  

       .prepareCreate(INDEX_NAME).setSettings(settings)  

       //這里直接添加type的mapping  

       .addMapping(TYPE_NAME, getMapping())  

       .execute().actionGet();  

         

       System.out.println("success:"+indexresponse.isAcknowledged());  

   }  

   private static String getAnalysisSettings() throws Exception {  

       XContentBuilder mapping = jsonBuilder()    

                  .startObject()    

                  //主分片數(shù)量  

                  .field("number_of_shards",5)  

                  .field("number_of_replicas",0)  

                    .startObject("analysis")    

                       .startObject("filter")  

                           //創(chuàng)建分詞過(guò)濾器  

                           .startObject("pynGram")  

                               .field("type","nGram")  

                               //從1開(kāi)始  

                               .field("min_gram",1)  

                               .field("max_gram",15)  

                           .endObject()  

                       .endObject()      

                         

                       .startObject("analyzer")  

                               //拼音analyszer  

                               .startObject("pyAnalyzer")  

                               .field("type","custom")  

                               .field("tokenizer","standard")  

                               .field("filter", new String[]{"lowercase","pynGram"})  

                               .endObject()  

                       .endObject()      

                   .endObject()    

                 .endObject();    

       System.out.println(mapping.string());  

       return mapping.string();  

   }  

   /**

    * mapping 一旦定義,之后就不能修改。

    * @return

    * @throws Exception

    */  

   private static XContentBuilder getMapping() throws Exception{  

       XContentBuilder mapping = jsonBuilder()    

                  .startObject()    

                    .startObject("icd")    

                    //指定分詞器  

                    .field("index_analyzer","pyAnalyzer")  

                    .startObject("properties")          

                      .startObject("id")  

                           .field("type", "long")  

                           .field("store", "yes")  

                       .endObject()      

                         

                      .startObject("code")  

                           .field("type", "string")  

                           .field("store", "yes")  

                           .field("index", "analyzed")  

                       .endObject()    

                         

                        .startObject("diseaseName")  

                           .field("type", "string")  

                           .field("store", "yes")  

                           .field("index", "analyzed")  

                       .endObject()    

                         

                        .startObject("mergeName")  

                           .field("type", "string")  

                           .field("store", "yes")  

                           .field("index", "analyzed")  

                       .endObject()  

                         

                       .startObject("pinyin")  

                           .field("type", "string")  

                           .field("store", "yes")  

                           .field("index", "analyzed")  

                       .endObject()    

                         

                      .startObject("isTherioma")  

                           .field("type", "boolean")  

                           .field("store", "yes")  

                      .endObject()    

                       

                       .startObject("isSpecialDisease")  

                           .field("type", "boolean")  

                           .field("store", "yes")  

                      .endObject()    

                       

                    .endObject()    

                   .endObject()    

                 .endObject();    

       return mapping;  

   }  

(PS:其實(shí)還有一種簡(jiǎn)單的方法,不用創(chuàng)建analyzer,在搜索的時(shí)候,使用'*gxy*'進(jìn)行搜索也可以)

最后,我還把這個(gè)檢索跟oracle的like進(jìn)行了比較。結(jié)果發(fā)現(xiàn)oracle只用20ms就能算出結(jié)果,而es卻用了將近100ms。可見(jiàn)這種吹捧的nosql,性能不見(jiàn)得比oracle強(qiáng)大啊,但是毋庸置疑的是,功能確實(shí)強(qiáng)大了。


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 富源县| 阳原县| 壶关县| 锦屏县| 新沂市| 九江市| 潼南县| 绵竹市| 项城市| 荔浦县| 蕲春县| 东安县| 霍山县| 榆树市| 安顺市| 龙泉市| 门源| 保康县| 桐庐县| 巫山县| 托里县| 磐石市| 南丰县| 阳信县| 株洲市| 柯坪县| 泊头市| 万全县| 呼伦贝尔市| 吐鲁番市| 仲巴县| 民乐县| 图木舒克市| 屏边| 阿拉尔市| 惠来县| 沙河市| 玛多县| 长沙县| 绥江县| 天气|