開(kāi)發(fā)Web應(yīng)用時(shí),你經(jīng)常要加上搜索功能。甚至還不知道要搜什么,就在草圖上畫(huà)了一個(gè)放大鏡。

說(shuō)到目前計(jì)算機(jī)的文字搜索在應(yīng)用上的實(shí)現(xiàn),象形文字天生就比拼音字母劣勢(shì)的多,分詞、詞性判斷、拼音文字轉(zhuǎn)換啥的,容易讓人香菇。

首先我們來(lái)了解下什么是Inverted index,翻譯過(guò)來(lái)的名字有很多,比如反轉(zhuǎn)索引、倒排索引什么的,讓人不明所以,可以理解為:一個(gè)未經(jīng)處理的數(shù)據(jù)庫(kù)中,一般是以文檔ID作為索引,以文檔內(nèi)容作為記錄。而Inverted index 指的是將單詞或記錄作為索引,將文檔ID作為記錄,這樣便可以方便地通過(guò)單詞或記錄查找到其所在的文檔。并不是什么高深概念。

oracle里常用的位圖索引(Bitmap index)也可認(rèn)為是Inverted index。位圖索引對(duì)于相異基數(shù)低的數(shù)據(jù)最為合適,即記錄多,但取值較少。比如一個(gè)100W行的表有一個(gè)字段會(huì)頻繁地被當(dāng)做查詢(xún)條件,我們會(huì)想到在這一列上面建立一個(gè)索引,但是這一列只可能取3個(gè)值。那么如果建立一個(gè)B*樹(shù)索引(普通索引)是不合適的,因?yàn)闊o(wú)論查找哪一個(gè)值,都可能會(huì)查出很多數(shù)據(jù),這時(shí)就可以考慮使用位圖索引。位圖索引相對(duì)于傳統(tǒng)的B*樹(shù)索引,在葉子節(jié)點(diǎn)上采用了完全不同的結(jié)構(gòu)組織方式。傳統(tǒng)B*樹(shù)索引將每一行記錄保存為一個(gè)葉子節(jié)點(diǎn),上面記錄對(duì)應(yīng)的索引列取值和行rowid信息。而位圖索引將每個(gè)可能的索引取值組織為一個(gè)葉子節(jié)點(diǎn)。每個(gè)位圖索引的葉子節(jié)點(diǎn)上,記錄著該索引鍵值的起始截止rowid和一個(gè)位圖向量串。如果不考慮起止rowid,那么就是取值有幾個(gè),就有幾個(gè)索引,比如上例,雖說(shuō)有100W條記錄,但是針對(duì)只有3個(gè)可取值的字段來(lái)說(shuō),索引節(jié)點(diǎn)只有3個(gè),類(lèi)似于下圖:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

需要注意的是,由于所有索引字段同值行共享一個(gè)索引節(jié)點(diǎn),位圖索引不適用于頻繁增刪改的字段,否則可能會(huì)導(dǎo)致針對(duì)該字段(其它行)的增刪改阻塞(對(duì)其它非索引字段的操作無(wú)影響),是一種索引段級(jí)鎖。具體請(qǐng)參看 深入解析B-Tree索引與Bitmap位圖索引的鎖代價(jià)。

下面說(shuō)說(shuō)筆者知道的一些全文搜索的工具。

文中綠色文字表示筆者并不確定描述是否正確,紅色表示筆者疑問(wèn),若有知道的同學(xué)請(qǐng)不吝賜教,多謝!


ICTCLAS分詞系統(tǒng)

本來(lái)想借著ICTCLAS簡(jiǎn)單介紹下中文分詞的一些原理和算法,不過(guò)網(wǎng)上已有比較好的文章了,可參看 ICTCLAS分詞系統(tǒng)研究。中文分詞基本上是基于詞典,[可能]涉及到的知識(shí) —— HMM(隱馬爾科夫鏈)、動(dòng)態(tài)規(guī)劃、TF-IDF、凸優(yōu)化,更基礎(chǔ)的就是信息論、概率論、矩陣等等,我們?cè)谧x書(shū)的時(shí)候可能并不知道所學(xué)何用,想較快重溫的同學(xué)可閱讀吳軍博士的《數(shù)學(xué)之美》。這些概念我會(huì)擇要在后續(xù)博文中介紹。下面我們就來(lái)看看分詞系統(tǒng)在數(shù)據(jù)庫(kù)中的具體應(yīng)用。


Postgresql的中文分詞

在PostgreSQL中,GIN索引就是Inverted index,GIN索引存儲(chǔ)一系列(key, posting list)對(duì), 這里的posting list是一組出現(xiàn)鍵的行ID。 每一個(gè)被索引的項(xiàng)目都可能包含多個(gè)鍵,因此同一個(gè)行ID可能會(huì)出現(xiàn)在多個(gè)posting list中。 每個(gè)鍵值只被存儲(chǔ)一次,因此在相同的鍵出現(xiàn)在很多項(xiàng)目的情況下,GIN索引是非常緊湊的(來(lái)自PostgreSQL 9.4.4 中文手冊(cè))。顯然,將之應(yīng)用到數(shù)組類(lèi)型的字段上是非常合適的。全文檢索類(lèi)型(tsvector)同樣支持GIN索引,可以加速查詢(xún)。聽(tīng)說(shuō)9.6版本出了一個(gè)什么RUM索引,對(duì)比GIN,檢索效率得到了很大的提升,可參看 PostgreSQL 全文檢索加速 快到?jīng)]有朋友 - RUM索引接口(潘多拉魔盒)。

幸運(yùn)的是,阿里云RDS PgSQL已支持zhparser(基于SCWS)中文分詞插件。

連接要分詞的數(shù)據(jù)庫(kù),執(zhí)行以下語(yǔ)句:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

-- 安裝擴(kuò)展create extension zhparser;-- 查看該數(shù)據(jù)庫(kù)的所有擴(kuò)展select * from pg_ts_parser; 
-- 支持的token類(lèi)型,即詞性,比如形容詞名詞啥的select ts_token_type('zhparser'); 
-- 創(chuàng)建使用zhparser作為解析器的全文搜索的配置 
CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser); 
-- 往全文搜索配置中增加token映射,上面的token映射只映射了名詞(n),動(dòng)詞(v),形容詞(a),成語(yǔ)(i),嘆詞(e)和習(xí)慣用語(yǔ)(l)6種,這6種以外的token全部被屏蔽。-- 詞典使用的是內(nèi)置的simple詞典,即僅做小寫(xiě)轉(zhuǎn)換。ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple; 
set zhparser.punctuation_ignore = t; -- 忽略標(biāo)點(diǎn)符號(hào)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

現(xiàn)在我們就可以方便的進(jìn)行中文分詞了,比如“select to_tsvector('testzhcfg','南京市長(zhǎng)江大橋');”,會(huì)拆分為“'南京市':1 '長(zhǎng)江大橋':2”。如果要分的更細(xì)粒度,那么可以設(shè)置復(fù)合分詞,復(fù)合分詞的級(jí)別:1~15,按位異或的 1|2|4|8 依次表示 短詞|二元|主要字|全部字,缺省不復(fù)合分詞,這是SCWS的配置選項(xiàng),對(duì)應(yīng)的zhparser選項(xiàng)為zhparser.multi_short、zhparser.multi_duality、zhparser.multi_zmain、zhparser.multi_zall。比如我們要設(shè)置短詞復(fù)合分詞,那么就set zhparser.multi_short=on;那么“select to_tsvector('testzhcfg','南京市長(zhǎng)江大橋');”得到的分詞結(jié)果將是“'南京':2 '南京市':1 '大橋':5 '長(zhǎng)江':4 '長(zhǎng)江大橋':3”,這樣就可以匹配到更多的關(guān)鍵詞,當(dāng)然檢索效率會(huì)變慢。

短詞復(fù)合分詞是根據(jù)詞典來(lái)的,比如詞典中有'一次性'、'一次性使用'、’'一次性使用吸痰管'、'使用'、'吸痰管'5個(gè)詞語(yǔ),當(dāng)multi_short=off時(shí),select to_tsvector('testzhcfg','"一次性使用吸痰管"');返回最大匹配的"一次性使用吸痰管",而為on時(shí),返回的是"'一次性':2 '一次性使用吸痰管':1 '使用':3 '吸痰管':4",讓人困惑的是,結(jié)果里沒(méi)有提取出'一次性使用'這個(gè)詞,不知怎么回事。

在產(chǎn)品表上建一列tsv存儲(chǔ)產(chǎn)品名稱(chēng)的tsvector值,并對(duì)該列建GIN索引。

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

CREATE OR REPLACE FUNCTION func_get_relatedkeywords(keyword text)  RETURNS SETOF text[] AS$BODY$begin    if (char_length(keyword)>0) then 
        RETURN QUERY select string_to_array(tsv::text,' ') from "Merchandises" where tsv @@ plainto_tsquery('testzhcfg',keyword);    end if;    
end$BODY$
  LANGUAGE plpgsql VOLATILE

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

注意plainto_tsquery和to_tsquery稍微有點(diǎn)區(qū)別,比如前者不認(rèn)識(shí)':*',而后者遇到空格會(huì)報(bào)錯(cuò)。

這會(huì)返回所有包含傳入關(guān)鍵詞的tsvector格式的字符串,所以我們要在業(yè)務(wù)層分解去重再傳遞給前端。

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

 1 public async Task<ActionResult> GetRelatedKeywords(string keyword) 2 { 3     var keywords = await MerchandiseContext.GetRelatedKeywords(keyword); 4     if(keywords != null && keywords.Count>0) 5     { 6         //將所有產(chǎn)品的關(guān)鍵詞匯總?cè)ブ?nbsp;7         var relatedKeywords = new List<string>(); 8         foreach(var k in keywords) 9         {10             for(int i=0;i<k.Count();i++) //pg返回的是帶冒號(hào)的tsvector格式11             {12                 k[i] = k[i].Split(':')[0].Trim('\'');13             }14             relatedKeywords.AddRange(k);//k可以作為整體,比如多個(gè)詞語(yǔ)作為一個(gè)組合加入返回結(jié)果,更科學(xué)(這里是拆分后獨(dú)立加入返回結(jié)果)15         }16         //根據(jù)出現(xiàn)重復(fù)次數(shù)排序(基于重復(fù)次數(shù)多,說(shuō)明關(guān)聯(lián)性高的預(yù)設(shè))17         relatedKeywords = relatedKeywords.GroupBy(rk => rk).OrderByDescending(g => g.Count()).Select(g => g.Key).Distinct().ToList();18         relatedKeywords.RemoveAll(rk=>keyword.Contains(rk));19         return this.Json(new OPResult<IEnumerable<string>> { IsSucceed = true, Data = relatedKeywords.Take(10) }, JsonRequestBehavior.AllowGet);20     }21     return this.Json(new OPResult { IsSucceed = true }, JsonRequestBehavior.AllowGet);22 }

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

now,我們就初步實(shí)現(xiàn)了類(lèi)似各大電商的搜索欄關(guān)鍵詞聯(lián)想功能:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

然而,尚有一些值得考慮的細(xì)節(jié)。當(dāng)數(shù)據(jù)庫(kù)中產(chǎn)品表越來(lái)越大,毫無(wú)疑問(wèn)查詢(xún)時(shí)間會(huì)變長(zhǎng),雖然我們只需要前面10個(gè)關(guān)聯(lián)詞,但可能有重復(fù)詞,所以并不能簡(jiǎn)單的在sql語(yǔ)句后面加limit 10。暫時(shí)縮小不了查詢(xún)范圍,可以減少相同關(guān)鍵詞的數(shù)據(jù)庫(kù)查詢(xún)頻率,即在上層加入緩存。key是關(guān)鍵詞或關(guān)鍵詞組合,value是關(guān)聯(lián)關(guān)鍵詞,關(guān)鍵詞多的話(huà),加上各種組合那么數(shù)據(jù)量肯定很大,所以我們緩存時(shí)間要根據(jù)數(shù)據(jù)量和用戶(hù)搜索量定個(gè)合適時(shí)間。以redis為例:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

 1 public static async Task SetRelatedKeywords(string keyword, IEnumerable<string> relatedKeywords) 2 { 3     var key = string.Format(RedisKeyTemplates.MERCHANDISERELATEDKEYWORDS, keyword); 4     IDatabase db = RedisGlobal.MANAGER.GetDatabase(); 5     var count = await db.SetAddAsync(key, relatedKeywords.Select<string, RedisValue>(kw => kw).ToArray()); 6     if (count > 0) 7         db.KeyExpire(key, TimeSpan.FromHours(14), CommandFlags.FireAndForget); //緩存 8 } 9 
10 public static async Task<List<string>> GetRelatedKeywords(string keyword)11 {12     IDatabase db = RedisGlobal.MANAGER.GetDatabase();13     var keywords = await db.SetMembersAsync(string.Format(RedisKeyTemplates.MERCHANDISERELATEDKEYWORDS, keyword));14     return keywords.Select(kw => kw.ToString()).ToList();15 }

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

當(dāng)用戶(hù)在搜索欄里輸入的并非完整的關(guān)鍵詞——輸入的文字并未精確匹配到數(shù)據(jù)庫(kù)里的任一tsvector——比如就輸入一個(gè)“交”或者“鎖型”之類(lèi),并沒(méi)有提供用戶(hù)預(yù)期的自動(dòng)補(bǔ)完功能(雖然自動(dòng)補(bǔ)完和關(guān)鍵詞聯(lián)想本質(zhì)上是兩個(gè)不同的功能,不過(guò)用戶(hù)可能并不這么想)。我們知道,在關(guān)鍵詞后加':*',比如“交:*”,那么是可以匹配到的,如:select '交鎖型:2 交鎖型股骨重建釘主釘:1 股骨:3 重建:4'::tsvector @@ to_tsquery('交:*'),返回的就是true。然而我們總不能讓用戶(hù)輸入的時(shí)候帶上:*,在代碼里給自動(dòng)附加:*是一種解決方法(select to_tsquery('testzhcfg','股骨重建:*'),結(jié)果是"'股骨':* & '重建':*"),然而會(huì)帶來(lái)可能的效率問(wèn)題,比如select to_tsquery('testzhcfg','一次性使用吸痰管:*'),它會(huì)拆分為"'一次性使用吸痰管':* & '一次性':* & '使用':* & '吸痰管':*",并且出于空格的考慮,我們用的是plainto_tsquery,而它是不認(rèn)識(shí):*的。

當(dāng)用戶(hù)輸入一些字符的時(shí)候,如何判斷是已完成的關(guān)鍵詞(進(jìn)行關(guān)鍵詞聯(lián)想)還是未輸完的關(guān)鍵詞(自動(dòng)補(bǔ)完),這是個(gè)問(wèn)題。我們可以將用戶(hù)常搜的一些關(guān)鍵詞緩存起來(lái)(或者定期從tsv字段獲取),當(dāng)用戶(hù)輸入匹配到多個(gè)(>1)緩存關(guān)鍵詞時(shí),說(shuō)明關(guān)鍵詞還未輸完整,返回關(guān)鍵詞列表供用戶(hù)選擇,否則(匹配數(shù)量<=1)時(shí),則去查詢(xún)關(guān)聯(lián)關(guān)鍵詞。同樣用redis(很幸運(yùn),redis2.8版本后支持set集合的值正則匹配):

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

/// <summary>/// 獲取關(guān)鍵詞(模糊匹配)/// </summary>public static List<string> GetKeywords(string keyword, int takeSize = 10)
{
    IDatabase db = RedisGlobal.MANAGER.GetDatabase();    //這里的pageSize表示單次遍歷數(shù)量,而不是說(shuō)最終返回?cái)?shù)量    var result = db.SetScan(RedisKeyTemplates.SearchKeyword, keyword + "*", pageSize: Int32.MaxValue);    return result.Take(takeSize).Select<RedisValue, string>(r => r).ToList();
}

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

當(dāng)然,也有可能用戶(hù)輸入已經(jīng)匹配到一個(gè)完整關(guān)鍵詞,但同時(shí)該關(guān)鍵詞是另外一些關(guān)鍵詞的一部分。我們可以先去緩存里面取關(guān)鍵詞,若數(shù)量少于10個(gè)(頁(yè)面上提示至多10個(gè)),那么就再去看是否有關(guān)聯(lián)關(guān)鍵詞補(bǔ)充。

大部分網(wǎng)站搜索還支持拼音搜索,即按全拼或拼音首字母搜索。

對(duì)關(guān)鍵詞[組合]賦予權(quán)重,權(quán)重計(jì)算可以依據(jù)搜索量、搜索結(jié)果等,每次返回給用戶(hù)最有效的前幾條。這以后再說(shuō)吧。

總的來(lái)說(shuō),數(shù)據(jù)庫(kù)自帶的全文檢索還是建立在字段檢索的基礎(chǔ)上,適合傳統(tǒng)SQL查詢(xún)場(chǎng)景,而且圍繞分詞系統(tǒng)的查詢(xún)方案和邏輯大部分需要自己處理,涉及到稍復(fù)雜的應(yīng)用就力不從心,或者效率低下了(比如上述的自動(dòng)補(bǔ)完功能),另外分布部署的時(shí)候也要在上層另做集群架構(gòu)。


Elasticsearch

基于5.4版本

節(jié)點(diǎn):一個(gè)運(yùn)行中的 Elasticsearch 實(shí)例稱(chēng)為一個(gè) 節(jié)點(diǎn)。

集群是由一個(gè)或者多個(gè)擁有相同 cluster.name 配置的節(jié)點(diǎn)組成, 它們共同承擔(dān)數(shù)據(jù)和負(fù)載的壓力。當(dāng)有節(jié)點(diǎn)加入集群中或者從集群中移除節(jié)點(diǎn)時(shí),集群將會(huì)重新平均分布所有的數(shù)據(jù)。一個(gè)集群只能有一個(gè)主節(jié)點(diǎn)。

索引:作為名詞時(shí),類(lèi)似于傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)中的一個(gè)數(shù)據(jù)庫(kù)。索引實(shí)際上是指向一個(gè)或者多個(gè)物理 分片 的 邏輯命名空間 。一個(gè)索引應(yīng)該是(非強(qiáng)制)因共同的特性被分組到一起的文檔集合, 例如,你可能存儲(chǔ)所有的產(chǎn)品在索引 products 中,而存儲(chǔ)所有銷(xiāo)售的交易到索引 sales 中。

分片:一個(gè)分片是一個(gè) Lucene 的實(shí)例(亦即一個(gè) Lucene 索引 ),它僅保存了全部數(shù)據(jù)中的一部分。索引內(nèi)任意一個(gè)文檔都?xì)w屬于一個(gè)主分片,所以主分片的數(shù)目決定著索引能夠保存的最大數(shù)據(jù)量;副本分片作為硬件故障時(shí)保護(hù)數(shù)據(jù)不丟失的冗余備份,并為搜索和返回文檔等讀操作提供服務(wù)。

類(lèi)型:由類(lèi)型名和mapping組成,mapping類(lèi)似于數(shù)據(jù)表的schema,或者說(shuō)類(lèi)[以及字段的具體]定義。

技術(shù)上講,多個(gè)類(lèi)型可以在相同的索引中存在,只要它們的字段不沖突,即同名字段類(lèi)型必須相同。但是,如果兩個(gè)類(lèi)型的字段集是互不相同的,這就意味著索引中將有一半的數(shù)據(jù)是空的(字段將是 稀疏的 ),最終將導(dǎo)致性能問(wèn)題?!獙?dǎo)致這一限制的根本原因,是Lucene沒(méi)有文檔類(lèi)型的概念,一個(gè)Lucene索引(ES里的分片)以扁平的模式定義其中所有字段,即假如該分片里有兩個(gè)類(lèi)型A\B,A中定義了a\c兩個(gè)字符串類(lèi)型的字段,B定義了b\c兩個(gè)字符串類(lèi)型的字段,那么Lucene創(chuàng)建的映射包括的是a\b\c三個(gè)字符串類(lèi)型的字段,如果A\B中c字段類(lèi)型不一樣,那么配置這個(gè)映射時(shí),將會(huì)出現(xiàn)異常。由此亦知,一個(gè)分片可包含不同類(lèi)型的文檔。

文檔:一個(gè)對(duì)象被序列化成為 JSON,它被稱(chēng)為一個(gè) JSON 文檔,指定了唯一 ID 。

假如文檔中新增了一個(gè)未事先定義的字段,或者給字段傳遞了非定義類(lèi)型的值,那么就涉及到動(dòng)態(tài)映射的概念了。另外,盡管可以增加新的類(lèi)型到索引中,或者增加新的字段到類(lèi)型中,但是不能添加新的分析器或者對(duì)現(xiàn)有的字段做改動(dòng),遇到這種情況,我們可能需要針對(duì)此類(lèi)文檔重建索引。

在 Elasticsearch 中, 每個(gè)字段的所有數(shù)據(jù) 都是 默認(rèn)被索引的 。 即每個(gè)字段都有為了快速檢索設(shè)置的專(zhuān)用倒排索引。

樂(lè)觀并發(fā)控制,Elasticsearch 使用 version 版本號(hào)控制、處理沖突。

Lucene中的[倒排]索引(在Lucene索引中表現(xiàn)為 段 的概念,Lucene索引除表示所有  的集合外,還有一個(gè) 提交點(diǎn) 的概念 ),[一旦創(chuàng)建]是不可變的,這有諸多好處:

  • 不需要鎖;

  • 重用索引緩存[,而非每次去磁盤(pán)獲取索引](即緩存不會(huì)失效,因?yàn)樗饕蛔儯M(jìn)一步可以重用相同查詢(xún)[構(gòu)建過(guò)程和返回的數(shù)據(jù)],而不需要每次都重新查詢(xún);

  • 允許[索引被]壓縮;

但是 數(shù)據(jù)/文檔 變化后,畢竟還是得更新 索引/段 的,那么怎么更新呢?—— 新的文檔和段會(huì)被創(chuàng)建,而舊的文檔和段被標(biāo)記為刪除狀態(tài),查詢(xún)時(shí),后者會(huì)被拋棄。

安裝Elasticsearch前需要安裝JRE(Java運(yùn)行時(shí),注意和JDK的區(qū)別),然后去到https://www.elastic.co/start里,根據(jù)提示步驟安裝運(yùn)行即可。(筆者為windows環(huán)境)

安裝完之后我們就可以在通過(guò)http://localhost:5601打開(kāi)kibana的工作臺(tái)。為了讓遠(yuǎn)程機(jī)子可以訪問(wèn),在啟動(dòng)kibana之前要先設(shè)置kibana.yml中的server.host,改為安裝了kibana的機(jī)器的IP地址,即server.host: "192.168.0.119",注意中間冒號(hào)和引號(hào)之間要有空格,否則無(wú)效,筆者被此處坑成狗,也是醉了。同理,要elasticsearch遠(yuǎn)程可訪問(wèn),需要設(shè)置elasticsearch.yml中的network.host。

單機(jī)上啟動(dòng)多個(gè)節(jié)點(diǎn),文檔中說(shuō) “你可以在同一個(gè)目錄內(nèi),完全依照啟動(dòng)第一個(gè)節(jié)點(diǎn)的方式來(lái)啟動(dòng)一個(gè)新節(jié)點(diǎn)。多個(gè)節(jié)點(diǎn)可以共享同一個(gè)目錄?!?沒(méi)搞懂什么意思,試了下再開(kāi)個(gè)控制臺(tái)進(jìn)入es目錄執(zhí)行命令行,會(huì)拋異常。所以還是老老實(shí)實(shí)按照網(wǎng)上其它資料提到的,拷貝一份es目錄先,要幾個(gè)節(jié)點(diǎn)就拷貝幾份。。

ES官方給.Net平臺(tái)提供了兩個(gè)工具—— Elasticsearch.Net 和 NEST,前者較底層,后者基于前者基礎(chǔ)上進(jìn)行了更高級(jí)的封裝以方便開(kāi)發(fā)調(diào)用。

NEST有個(gè)Connection pools,這跟我們平常認(rèn)為的連接池不是同一個(gè)概念,而是一種策略——以什么方式連接到ES——有四種策略:

  • SingleNodeConnectionPool:每次連接指向到同一個(gè)節(jié)點(diǎn)(一般設(shè)置為主節(jié)點(diǎn),專(zhuān)門(mén)負(fù)責(zé)路由)

  • StaticConnectionPool:如果知道一些節(jié)點(diǎn)Uri的話(huà),那么每次就[隨機(jī)]連接到這些節(jié)點(diǎn)[中的一個(gè)]

  • SniffingConnectionPool:derived from StaticConnectionPool,a sniffing connection pool allows itself to be reseeded at run time。然而暫時(shí)并不知道具體用處。。。

  • StickyConnectionPool:選擇第一個(gè)節(jié)點(diǎn)作為請(qǐng)求主節(jié)點(diǎn)。同樣不知用這個(gè)有什么好處。。。

下面我們使用ES實(shí)現(xiàn)自動(dòng)補(bǔ)完的功能,順帶介紹涉及到的知識(shí)點(diǎn)。

服務(wù)器根據(jù)用戶(hù)當(dāng)前輸入返回可能的[用戶(hù)真正想輸?shù)腯字符串——"Suggest As You Type"。ES提供了四個(gè)Suggester API(可參看 Elasticsearch Suggester詳解,這篇文章沒(méi)有介紹第四個(gè)Context Suggester,我會(huì)在本節(jié)后面稍作描述),本文舉例的自動(dòng)補(bǔ)

 

轉(zhuǎn)載請(qǐng)注明出處:http://www.cnblogs.com/newton/p/6873508.html

http://www.cnblogs.com/newton/p/6873508.html