全文搜索引擎 Elasticsearch拜轨,這篇文章給講透了弥喉!

來(lái)源:微信公眾號(hào) 作者:Java面試題精選
鏈接:https://mp.weixin.qq.com/s/Q-QV86XntKniQlMohIaexQ

生活中的數(shù)據(jù)

搜索引擎是對(duì)數(shù)據(jù)的檢索,所以我們先從生活中的數(shù)據(jù)說(shuō)起例嘱。我們生活中的數(shù)據(jù)總體分為兩種:

  • 結(jié)構(gòu)化數(shù)據(jù)
  • 非結(jié)構(gòu)化數(shù)據(jù)

結(jié)構(gòu)化數(shù)據(jù): 也稱(chēng)作行數(shù)據(jù)狡逢,是由二維表結(jié)構(gòu)來(lái)邏輯表達(dá)和實(shí)現(xiàn)的數(shù)據(jù),嚴(yán)格地遵循數(shù)據(jù)格式與長(zhǎng)度規(guī)范拼卵,主要通過(guò)關(guān)系型數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ)和管理奢浑。指具有固定格式或有限長(zhǎng)度的數(shù)據(jù),如數(shù)據(jù)庫(kù)腋腮,元數(shù)據(jù)等雀彼。

非結(jié)構(gòu)化數(shù)據(jù): 又可稱(chēng)為全文數(shù)據(jù),不定長(zhǎng)或無(wú)固定格式即寡,不適于由數(shù)據(jù)庫(kù)二維表來(lái)表現(xiàn)徊哑,包括所有格式的辦公文檔、XML聪富、HTML实柠、Word 文檔,郵件善涨,各類(lèi)報(bào)表窒盐、圖片和咅頻、視頻信息等钢拧。

說(shuō)明:如果要更細(xì)致的區(qū)分的話蟹漓,XML、HTML 可劃分為半結(jié)構(gòu)化數(shù)據(jù)源内。因?yàn)樗鼈円簿哂凶约禾囟ǖ臉?biāo)簽格式葡粒,所以既可以根據(jù)需要按結(jié)構(gòu)化數(shù)據(jù)來(lái)處理份殿,也可抽取出純文本按非結(jié)構(gòu)化數(shù)據(jù)來(lái)處理。

根據(jù)兩種數(shù)據(jù)分類(lèi)嗽交,搜索也相應(yīng)的分為兩種:

  • 結(jié)構(gòu)化數(shù)據(jù)搜索
  • 非結(jié)構(gòu)化數(shù)據(jù)搜索

對(duì)于結(jié)構(gòu)化數(shù)據(jù)卿嘲,因?yàn)樗鼈兙哂刑囟ǖ慕Y(jié)構(gòu),所以我們一般都是可以通過(guò)關(guān)系型數(shù)據(jù)庫(kù)(MySQL夫壁,Oracle 等)的二維表(Table)的方式存儲(chǔ)和搜索拾枣,也可以建立索引。

對(duì)于非結(jié)構(gòu)化數(shù)據(jù)盒让,也即對(duì)全文數(shù)據(jù)的搜索主要有兩種方法:

  • 順序掃描
  • 全文檢索

順序掃描: 通過(guò)文字名稱(chēng)也可了解到它的大概搜索方式梅肤,即按照順序掃描的方式查詢特定的關(guān)鍵字。

例如給你一張報(bào)紙邑茄,讓你找到該報(bào)紙中“平安”的文字在哪些地方出現(xiàn)過(guò)姨蝴。你肯定需要從頭到尾把報(bào)紙閱讀掃描一遍然后標(biāo)記出關(guān)鍵字在哪些版塊出現(xiàn)過(guò)以及它的出現(xiàn)位置。

這種方式無(wú)疑是最耗時(shí)的最低效的肺缕,如果報(bào)紙排版字體小左医,而且版塊較多甚至有多份報(bào)紙,等你掃描完你的眼睛也差不多了同木。

全文搜索: 對(duì)非結(jié)構(gòu)化數(shù)據(jù)順序掃描很慢炒辉,我們是否可以進(jìn)行優(yōu)化?把我們的非結(jié)構(gòu)化數(shù)據(jù)想辦法弄得有一定結(jié)構(gòu)不就行了嗎泉手?

將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來(lái)黔寇,重新組織,使其變得有一定結(jié)構(gòu)斩萌,然后對(duì)此有一定結(jié)構(gòu)的數(shù)據(jù)進(jìn)行搜索缝裤,從而達(dá)到搜索相對(duì)較快的目的。

這種方式就構(gòu)成了全文檢索的基本思路颊郎。這部分從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息憋飞,我們稱(chēng)之為索引。

這種方式的主要工作量在前期索引的創(chuàng)建姆吭,但是對(duì)于后期搜索卻是快速高效的榛做。

先說(shuō)說(shuō) Lucene

通過(guò)對(duì)生活中數(shù)據(jù)的類(lèi)型作了一個(gè)簡(jiǎn)短了解之后,我們知道關(guān)系型數(shù)據(jù)庫(kù)的 SQL 檢索是處理不了這種非結(jié)構(gòu)化數(shù)據(jù)的内狸。

這種非結(jié)構(gòu)化數(shù)據(jù)的處理需要依賴全文搜索检眯,而目前市場(chǎng)上開(kāi)放源代碼的最好全文檢索引擎工具包就屬于 Apache 的 Lucene了。

但是 Lucene 只是一個(gè)工具包昆淡,它不是一個(gè)完整的全文檢索引擎锰瘸。Lucene 的目的是為軟件開(kāi)發(fā)人員提供一個(gè)簡(jiǎn)單易用的工具包,以方便的在目標(biāo)系統(tǒng)中實(shí)現(xiàn)全文檢索的功能昂灵,或者是以此為基礎(chǔ)建立起完整的全文檢索引擎避凝。

目前以 Lucene 為基礎(chǔ)建立的開(kāi)源可用全文搜索引擎主要是 Solr 和 Elasticsearch舞萄。

Solr 和 Elasticsearch 都是比較成熟的全文搜索引擎,能完成的功能和性能也基本一樣管削。

但是 ES 本身就具有分布式的特性和易安裝使用的特點(diǎn)倒脓,而 Solr 的分布式需要借助第三方來(lái)實(shí)現(xiàn),例如通過(guò)使用 ZooKeeper 來(lái)達(dá)到分布式協(xié)調(diào)管理含思。

不管是 Solr 還是 Elasticsearch 底層都是依賴于 Lucene崎弃,而 Lucene 能實(shí)現(xiàn)全文搜索主要是因?yàn)樗鼘?shí)現(xiàn)了倒排索引的查詢結(jié)構(gòu)。

如何理解倒排索引呢茸俭? 假如現(xiàn)有三份數(shù)據(jù)文檔吊履,文檔的內(nèi)容如下分別是:

  • Java is the best programming language.
  • PHP is the best programming language.
  • Javascript is the best programming language.

為了創(chuàng)建倒排索引安皱,我們通過(guò)分詞器將每個(gè)文檔的內(nèi)容域拆分成單獨(dú)的詞(我們稱(chēng)它為詞條或 Term)调鬓,創(chuàng)建一個(gè)包含所有不重復(fù)詞條的排序列表,然后列出每個(gè)詞條出現(xiàn)在哪個(gè)文檔酌伊。

結(jié)果如下所示:

Term          Doc_1    Doc_2   Doc_3  
-------------------------------------  
Java        |   X   |        |  
is          |   X   |   X    |   X  
the         |   X   |   X    |   X  
best        |   X   |   X    |   X  
programming |   x   |   X    |   X  
language    |   X   |   X    |   X  
PHP         |       |   X    |  
Javascript  |       |        |   X  
-------------------------------------  

這種結(jié)構(gòu)由文檔中所有不重復(fù)詞的列表構(gòu)成腾窝,對(duì)于其中每個(gè)詞都有一個(gè)文檔列表與之關(guān)聯(lián)。

這種由屬性值來(lái)確定記錄的位置的結(jié)構(gòu)就是倒排索引居砖。帶有倒排索引的文件我們稱(chēng)為倒排文件虹脯。

我們將上面的內(nèi)容轉(zhuǎn)換為圖的形式來(lái)說(shuō)明倒排索引的結(jié)構(gòu)信息,如下圖所示:

其中主要有如下幾個(gè)核心術(shù)語(yǔ)需要理解:

  • 詞條(Term): 索引里面最小的存儲(chǔ)和查詢單元奏候,對(duì)于英文來(lái)說(shuō)是一個(gè)單詞循集,對(duì)于中文來(lái)說(shuō)一般指分詞后的一個(gè)詞。

  • 詞典(Term Dictionary): 或字典蔗草,是詞條 Term 的集合咒彤。搜索引擎的通常索引單位是單詞,單詞詞典是由文檔集合中出現(xiàn)過(guò)的所有單詞構(gòu)成的字符串集合咒精,單詞詞典內(nèi)每條索引項(xiàng)記載單詞本身的一些信息以及指向“倒排列表”的指針镶柱。

  • 倒排表(Post list): 一個(gè)文檔通常由多個(gè)詞組成,倒排表記錄的是某個(gè)詞在哪些文檔里出現(xiàn)過(guò)以及出現(xiàn)的位置模叙。每條記錄稱(chēng)為一個(gè)倒排項(xiàng)(Posting)歇拆。倒排表記錄的不單是文檔編號(hào),還存儲(chǔ)了詞頻等信息范咨。

  • 倒排文件(Inverted File): 所有單詞的倒排列表往往順序地存儲(chǔ)在磁盤(pán)的某個(gè)文件里故觅,這個(gè)文件被稱(chēng)之為倒排文件,倒排文件是存儲(chǔ)倒排索引的物理文件渠啊。

從上圖我們可以了解到倒排索引主要由兩個(gè)部分組成:

  • 詞典
  • 倒排文件

詞典和倒排表是 Lucene 中很重要的兩種數(shù)據(jù)結(jié)構(gòu)逻卖,是實(shí)現(xiàn)快速檢索的重要基石。詞典和倒排文件是分兩部分存儲(chǔ)的昭抒,詞典在內(nèi)存中而倒排文件存儲(chǔ)在磁盤(pán)上评也。

ES 核心概念

一些基礎(chǔ)知識(shí)的鋪墊之后我們正式進(jìn)入今天的主角 Elasticsearch 的介紹炼杖。

ES 是使用 Java 編寫(xiě)的一種開(kāi)源搜索引擎,它在內(nèi)部使用 Lucene 做索引與搜索盗迟,通過(guò)對(duì) Lucene 的封裝坤邪,隱藏了 Lucene 的復(fù)雜性,取而代之的提供一套簡(jiǎn)單一致的 RESTful API罚缕。

然而艇纺,Elasticsearch 不僅僅是 Lucene,并且也不僅僅只是一個(gè)全文搜索引擎邮弹。

它可以被下面這樣準(zhǔn)確的形容:

  • 一個(gè)分布式的實(shí)時(shí)文檔存儲(chǔ)黔衡,每個(gè)字段可以被索引與搜索。
  • 一個(gè)分布式實(shí)時(shí)分析搜索引擎腌乡。
  • 能勝任上百個(gè)服務(wù)節(jié)點(diǎn)的擴(kuò)展盟劫,并支持 PB 級(jí)別的結(jié)構(gòu)化或者非結(jié)構(gòu)化數(shù)據(jù)。

官網(wǎng)對(duì) Elasticsearch 的介紹是 Elasticsearch 是一個(gè)分布式与纽、可擴(kuò)展侣签、近實(shí)時(shí)的搜索與數(shù)據(jù)分析引擎。

我們通過(guò)一些核心概念來(lái)看下 Elasticsearch 是如何做到分布式急迂,可擴(kuò)展和近實(shí)時(shí)搜索的影所。

集群(Cluster)

ES 的集群搭建很簡(jiǎn)單,不需要依賴第三方協(xié)調(diào)管理組件僚碎,自身內(nèi)部就實(shí)現(xiàn)了集群的管理功能猴娩。

ES 集群由一個(gè)或多個(gè) Elasticsearch 節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)配置相同的 cluster.name 即可加入集群勺阐,默認(rèn)值為 “elasticsearch”卷中。

確保不同的環(huán)境中使用不同的集群名稱(chēng),否則最終會(huì)導(dǎo)致節(jié)點(diǎn)加入錯(cuò)誤的集群皆看。

一個(gè) Elasticsearch 服務(wù)啟動(dòng)實(shí)例就是一個(gè)節(jié)點(diǎn)(Node)仓坞。節(jié)點(diǎn)通過(guò) node.name 來(lái)設(shè)置節(jié)點(diǎn)名稱(chēng),如果不設(shè)置則在啟動(dòng)時(shí)給節(jié)點(diǎn)分配一個(gè)隨機(jī)通用唯一標(biāo)識(shí)符作為名稱(chēng)腰吟。

①發(fā)現(xiàn)機(jī)制

那么有一個(gè)問(wèn)題无埃,ES 內(nèi)部是如何通過(guò)一個(gè)相同的設(shè)置 cluster.name 就能將不同的節(jié)點(diǎn)連接到同一個(gè)集群的?答案是 Zen Discovery毛雇。

Zen Discovery 是 Elasticsearch 的內(nèi)置默認(rèn)發(fā)現(xiàn)模塊(發(fā)現(xiàn)模塊的職責(zé)是發(fā)現(xiàn)集群中的節(jié)點(diǎn)以及選舉 Master 節(jié)點(diǎn))嫉称。

它提供單播和基于文件的發(fā)現(xiàn),并且可以擴(kuò)展為通過(guò)插件支持云環(huán)境和其他形式的發(fā)現(xiàn)灵疮。

Zen Discovery 與其他模塊集成织阅,例如,節(jié)點(diǎn)之間的所有通信都使用 Transport 模塊完成震捣。節(jié)點(diǎn)使用發(fā)現(xiàn)機(jī)制通過(guò) Ping 的方式查找其他節(jié)點(diǎn)荔棉。

Elasticsearch 默認(rèn)被配置為使用單播發(fā)現(xiàn)闹炉,以防止節(jié)點(diǎn)無(wú)意中加入集群。只有在同一臺(tái)機(jī)器上運(yùn)行的節(jié)點(diǎn)才會(huì)自動(dòng)組成集群润樱。

如果集群的節(jié)點(diǎn)運(yùn)行在不同的機(jī)器上渣触,使用單播,你可以為 Elasticsearch 提供一些它應(yīng)該去嘗試連接的節(jié)點(diǎn)列表壹若。

當(dāng)一個(gè)節(jié)點(diǎn)聯(lián)系到單播列表中的成員時(shí)嗅钻,它就會(huì)得到整個(gè)集群所有節(jié)點(diǎn)的狀態(tài),然后它會(huì)聯(lián)系 Master 節(jié)點(diǎn)店展,并加入集群养篓。

這意味著單播列表不需要包含集群中的所有節(jié)點(diǎn), 它只是需要足夠的節(jié)點(diǎn)赂蕴,當(dāng)一個(gè)新節(jié)點(diǎn)聯(lián)系上其中一個(gè)并且說(shuō)上話就可以了柳弄。

如果你使用 Master 候選節(jié)點(diǎn)作為單播列表,你只要列出三個(gè)就可以了睡腿。這個(gè)配置在 elasticsearch.yml 文件中:

discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]  

節(jié)點(diǎn)啟動(dòng)后先 Ping 语御,如果 discovery.zen.ping.unicast.hosts 有設(shè)置峻贮,則 Ping 設(shè)置中的 Host 席怪,否則嘗試 ping localhost 的幾個(gè)端口。

Elasticsearch 支持同一個(gè)主機(jī)啟動(dòng)多個(gè)節(jié)點(diǎn)纤控,Ping 的 Response 會(huì)包含該節(jié)點(diǎn)的基本信息以及該節(jié)點(diǎn)認(rèn)為的 Master 節(jié)點(diǎn)挂捻。

選舉開(kāi)始,先從各節(jié)點(diǎn)認(rèn)為的 Master 中選船万,規(guī)則很簡(jiǎn)單刻撒,按照 ID 的字典序排序,取第一個(gè)耿导。如果各節(jié)點(diǎn)都沒(méi)有認(rèn)為的 Master 声怔,則從所有節(jié)點(diǎn)中選擇,規(guī)則同上舱呻。

這里有個(gè)限制條件就是 discovery.zen.minimum_master_nodes 箱吕,如果節(jié)點(diǎn)數(shù)達(dá)不到最小值的限制,則循環(huán)上述過(guò)程茬高,直到節(jié)點(diǎn)數(shù)足夠可以開(kāi)始選舉怎栽。

最后選舉結(jié)果是肯定能選舉出一個(gè) Master 丽猬,如果只有一個(gè) Local 節(jié)點(diǎn)那就選出的是自己宿饱。

如果當(dāng)前節(jié)點(diǎn)是 Master 刑棵,則開(kāi)始等待節(jié)點(diǎn)數(shù)達(dá)到 discovery.zen.minimum_master_nodes,然后提供服務(wù)沥寥。

如果當(dāng)前節(jié)點(diǎn)不是 Master 片橡,則嘗試加入 Master 捧书。Elasticsearch 將以上服務(wù)發(fā)現(xiàn)以及選主的流程叫做 Zen Discovery 经瓷。

由于它支持任意數(shù)目的集群( 1- N ),所以不能像 Zookeeper 那樣限制節(jié)點(diǎn)必須是奇數(shù)队贱,也就無(wú)法用投票的機(jī)制來(lái)選主锋恬,而是通過(guò)一個(gè)規(guī)則与学。

只要所有的節(jié)點(diǎn)都遵循同樣的規(guī)則癣防,得到的信息都是對(duì)等的,選出來(lái)的主節(jié)點(diǎn)肯定是一致的级遭。

但分布式系統(tǒng)的問(wèn)題就出在信息不對(duì)等的情況挫鸽,這時(shí)候很容易出現(xiàn)腦裂(Split-Brain)的問(wèn)題盔沫。

大多數(shù)解決方案就是設(shè)置一個(gè) Quorum 值架诞,要求可用節(jié)點(diǎn)必須大于 Quorum(一般是超過(guò)半數(shù)節(jié)點(diǎn))谴忧,才能對(duì)外提供服務(wù)。

而 Elasticsearch 中均驶,這個(gè) Quorum 的配置就是 discovery.zen.minimum_master_nodes 辣恋。

②節(jié)點(diǎn)的角色

每個(gè)節(jié)點(diǎn)既可以是候選主節(jié)點(diǎn)也可以是數(shù)據(jù)節(jié)點(diǎn)饮潦,通過(guò)在配置文件 ../config/elasticsearch.yml 中設(shè)置即可继蜡,默認(rèn)都為 true稀并。

node.master: true  //是否候選主節(jié)點(diǎn)  
node.data: true    //是否數(shù)據(jù)節(jié)點(diǎn)   

數(shù)據(jù)節(jié)點(diǎn)負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和相關(guān)的操作忘瓦,例如對(duì)數(shù)據(jù)進(jìn)行增耕皮、刪粱年、改台诗、查和聚合等操作拉庶,所以數(shù)據(jù)節(jié)點(diǎn)(Data 節(jié)點(diǎn))對(duì)機(jī)器配置要求比較高,對(duì) CPU皆尔、內(nèi)存和 I/O 的消耗很大。

通常隨著集群的擴(kuò)大流炕,需要增加更多的數(shù)據(jù)節(jié)點(diǎn)來(lái)提高性能和可用性每辟。

候選主節(jié)點(diǎn)可以被選舉為主節(jié)點(diǎn)(Master 節(jié)點(diǎn)),集群中只有候選主節(jié)點(diǎn)才有選舉權(quán)和被選舉權(quán)挠将,其他節(jié)點(diǎn)不參與選舉的工作舔稀。

主節(jié)點(diǎn)負(fù)責(zé)創(chuàng)建索引、刪除索引贺归、跟蹤哪些節(jié)點(diǎn)是群集的一部分秋冰,并決定哪些分片分配給相關(guān)的節(jié)點(diǎn)剑勾、追蹤集群中節(jié)點(diǎn)的狀態(tài)等,穩(wěn)定的主節(jié)點(diǎn)對(duì)集群的健康是非常重要的捂刺。

一個(gè)節(jié)點(diǎn)既可以是候選主節(jié)點(diǎn)也可以是數(shù)據(jù)節(jié)點(diǎn),但是由于數(shù)據(jù)節(jié)點(diǎn)對(duì) CPU仪缸、內(nèi)存核 I/O 消耗都很大。

所以如果某個(gè)節(jié)點(diǎn)既是數(shù)據(jù)節(jié)點(diǎn)又是主節(jié)點(diǎn)拴还,那么可能會(huì)對(duì)主節(jié)點(diǎn)產(chǎn)生影響從而對(duì)整個(gè)集群的狀態(tài)產(chǎn)生影響。

因此為了提高集群的健康性,我們應(yīng)該對(duì) Elasticsearch 集群中的節(jié)點(diǎn)做好角色上的劃分和隔離晒喷。可以使用幾個(gè)配置較低的機(jī)器群作為候選主節(jié)點(diǎn)群爷抓。

主節(jié)點(diǎn)和其他節(jié)點(diǎn)之間通過(guò) Ping 的方式互檢查蓝撇,主節(jié)點(diǎn)負(fù)責(zé) Ping 所有其他節(jié)點(diǎn)虽抄,判斷是否有節(jié)點(diǎn)已經(jīng)掛掉迈窟。其他節(jié)點(diǎn)也通過(guò) Ping 的方式判斷主節(jié)點(diǎn)是否處于可用狀態(tài)车酣。

雖然對(duì)節(jié)點(diǎn)做了角色區(qū)分,但是用戶的請(qǐng)求可以發(fā)往任何一個(gè)節(jié)點(diǎn)破衔,并由該節(jié)點(diǎn)負(fù)責(zé)分發(fā)請(qǐng)求、收集結(jié)果等操作读第,而不需要主節(jié)點(diǎn)轉(zhuǎn)發(fā)。

這種節(jié)點(diǎn)可稱(chēng)之為協(xié)調(diào)節(jié)點(diǎn)吴汪,協(xié)調(diào)節(jié)點(diǎn)是不需要指定和配置的,集群中的任何節(jié)點(diǎn)都可以充當(dāng)協(xié)調(diào)節(jié)點(diǎn)的角色霜运。

③腦裂現(xiàn)象

同時(shí)如果由于網(wǎng)絡(luò)或其他原因?qū)е录褐羞x舉出多個(gè) Master 節(jié)點(diǎn)藕各,使得數(shù)據(jù)更新時(shí)出現(xiàn)不一致,這種現(xiàn)象稱(chēng)之為腦裂誉碴,即集群中不同的節(jié)點(diǎn)對(duì)于 Master 的選擇出現(xiàn)了分歧黔帕,出現(xiàn)了多個(gè) Master 競(jìng)爭(zhēng)。

“腦裂”問(wèn)題可能有以下幾個(gè)原因造成:

  • 網(wǎng)絡(luò)問(wèn)題: 集群間的網(wǎng)絡(luò)延遲導(dǎo)致一些節(jié)點(diǎn)訪問(wèn)不到 Master,認(rèn)為 Master 掛掉了從而選舉出新的 Master闻伶,并對(duì) Master 上的分片和副本標(biāo)紅,分配新的主分片畜份。
  • 節(jié)點(diǎn)負(fù)載: 主節(jié)點(diǎn)的角色既為 Master 又為 Data,訪問(wèn)量較大時(shí)可能會(huì)導(dǎo)致 ES 停止響應(yīng)(假死狀態(tài))造成大面積延遲钙态,此時(shí)其他節(jié)點(diǎn)得不到主節(jié)點(diǎn)的響應(yīng)認(rèn)為主節(jié)點(diǎn)掛掉了,會(huì)重新選取主節(jié)點(diǎn)剩失。
  • 內(nèi)存回收: 主節(jié)點(diǎn)的角色既為 Master 又為 Data,當(dāng) Data 節(jié)點(diǎn)上的 ES 進(jìn)程占用的內(nèi)存較大,引發(fā) JVM 的大規(guī)模內(nèi)存回收芒粹,造成 ES 進(jìn)程失去響應(yīng)。

為了避免腦裂現(xiàn)象的發(fā)生座云,我們可以從原因著手通過(guò)以下幾個(gè)方面來(lái)做出優(yōu)化措施:

  • 適當(dāng)調(diào)大響應(yīng)時(shí)間,減少誤判。 通過(guò)參數(shù) discovery.zen.ping_timeout 設(shè)置節(jié)點(diǎn)狀態(tài)的響應(yīng)時(shí)間睬隶,默認(rèn)為 3s,可以適當(dāng)調(diào)大窖贤。

如果 Master 在該響應(yīng)時(shí)間的范圍內(nèi)沒(méi)有做出響應(yīng)應(yīng)答,判斷該節(jié)點(diǎn)已經(jīng)掛掉了。調(diào)大參數(shù)(如 6s蹄皱,discovery.zen.ping_timeout:6)压鉴,可適當(dāng)減少誤判。

  • 選舉觸發(fā)婉宰。 我們需要在候選集群中的節(jié)點(diǎn)的配置文件中設(shè)置參數(shù) discovery.zen.munimum_master_nodes 的值。

這個(gè)參數(shù)表示在選舉主節(jié)點(diǎn)時(shí)需要參與選舉的候選主節(jié)點(diǎn)的節(jié)點(diǎn)數(shù),默認(rèn)值是 1岭佳,官方建議取值(master_eligibel_nodes2)+1,其中 master_eligibel_nodes 為候選主節(jié)點(diǎn)的個(gè)數(shù)。

這樣做既能防止腦裂現(xiàn)象的發(fā)生衩辟,也能最大限度地提升集群的高可用性,因?yàn)橹灰簧儆?discovery.zen.munimum_master_nodes 個(gè)候選節(jié)點(diǎn)存活,選舉工作就能正常進(jìn)行。

當(dāng)小于這個(gè)值的時(shí)候抖锥,無(wú)法觸發(fā)選舉行為缕探,集群無(wú)法使用耙考,不會(huì)造成分片混亂的情況斗遏。

  • 角色分離。 即是上面我們提到的候選主節(jié)點(diǎn)和數(shù)據(jù)節(jié)點(diǎn)進(jìn)行角色分離,這樣可以減輕主節(jié)點(diǎn)的負(fù)擔(dān)遵堵,防止主節(jié)點(diǎn)的假死狀態(tài)發(fā)生,減少對(duì)主節(jié)點(diǎn)“已死”的誤判壳坪。

分片(Shards)

ES 支持 PB 級(jí)全文搜索岂昭,當(dāng)索引上的數(shù)據(jù)量太大的時(shí)候惧磺,ES 通過(guò)水平拆分的方式將一個(gè)索引上的數(shù)據(jù)拆分出來(lái)分配到不同的數(shù)據(jù)塊上,拆分出來(lái)的數(shù)據(jù)庫(kù)塊稱(chēng)之為一個(gè)分片磨隘。

這類(lèi)似于 MySQL 的分庫(kù)分表缤底,只不過(guò) MySQL 分庫(kù)分表需要借助第三方組件而 ES 內(nèi)部自身實(shí)現(xiàn)了此功能顾患。

在一個(gè)多分片的索引中寫(xiě)入數(shù)據(jù)時(shí),通過(guò)路由來(lái)確定具體寫(xiě)入哪一個(gè)分片中个唧,所以在創(chuàng)建索引的時(shí)候需要指定分片的數(shù)量,并且分片的數(shù)量一旦確定就不能修改徙歼。

分片的數(shù)量和下面介紹的副本數(shù)量都是可以通過(guò)創(chuàng)建索引時(shí)的 Settings 來(lái)配置犁河,ES 默認(rèn)為一個(gè)索引創(chuàng)建 5 個(gè)主分片, 并分別為每個(gè)分片創(chuàng)建一個(gè)副本。

PUT /myIndex  
{  
   "settings" : {  
      "number_of_shards" : 5,  
      "number_of_replicas" : 1  
   }  
} 

ES 通過(guò)分片的功能使得索引在規(guī)模上和性能上都得到提升魄梯,每個(gè)分片都是 Lucene 中的一個(gè)索引文件桨螺,每個(gè)分片必須有一個(gè)主分片和零到多個(gè)副本。

副本(Replicas)

副本就是對(duì)分片的 Copy酿秸,每個(gè)主分片都有一個(gè)或多個(gè)副本分片灭翔,當(dāng)主分片異常時(shí),副本可以提供數(shù)據(jù)的查詢等操作辣苏。

主分片和對(duì)應(yīng)的副本分片是不會(huì)在同一個(gè)節(jié)點(diǎn)上的肝箱,所以副本分片數(shù)的最大值是 N-1(其中 N 為節(jié)點(diǎn)數(shù))。

對(duì)文檔的新建稀蟋、索引和刪除請(qǐng)求都是寫(xiě)操作煌张,必須在主分片上面完成之后才能被復(fù)制到相關(guān)的副本分片。

ES 為了提高寫(xiě)入的能力這個(gè)過(guò)程是并發(fā)寫(xiě)的糊治,同時(shí)為了解決并發(fā)寫(xiě)的過(guò)程中數(shù)據(jù)沖突的問(wèn)題唱矛,ES 通過(guò)樂(lè)觀鎖的方式控制,每個(gè)文檔都有一個(gè)_version (版本)號(hào)井辜,當(dāng)文檔被修改時(shí)版本號(hào)遞增绎谦。

一旦所有的副本分片都報(bào)告寫(xiě)成功才會(huì)向協(xié)調(diào)節(jié)點(diǎn)報(bào)告成功,協(xié)調(diào)節(jié)點(diǎn)向客戶端報(bào)告成功粥脚。

從上圖可以看出為了達(dá)到高可用窃肠,Master 節(jié)點(diǎn)會(huì)避免將主分片和副本分片放在同一個(gè)節(jié)點(diǎn)上。

假設(shè)這時(shí)節(jié)點(diǎn) Node1 服務(wù)宕機(jī)了或者網(wǎng)絡(luò)不可用了刷允,那么主節(jié)點(diǎn)上主分片 S0 也就不可用了冤留。

幸運(yùn)的是還存在另外兩個(gè)節(jié)點(diǎn)能正常工作,這時(shí) ES 會(huì)重新選舉新的主節(jié)點(diǎn)树灶,而且這兩個(gè)節(jié)點(diǎn)上存在我們所需要的 S0 的所有數(shù)據(jù)纤怒。

我們會(huì)將 S0 的副本分片提升為主分片,這個(gè)提升主分片的過(guò)程是瞬間發(fā)生的天通。此時(shí)集群的狀態(tài)將會(huì)為 Yellow泊窘。

為什么我們集群狀態(tài)是 Yellow 而不是 Green 呢?雖然我們擁有所有的 2 個(gè)主分片,但是同時(shí)設(shè)置了每個(gè)主分片需要對(duì)應(yīng)兩份副本分片烘豹,而此時(shí)只存在一份副本分片瓜贾。所以集群不能為 Green 的狀態(tài)。

如果我們同樣關(guān)閉了 Node2 携悯,我們的程序依然可以保持在不丟失任何數(shù)據(jù)的情況下運(yùn)行祭芦,因?yàn)?Node3 為每一個(gè)分片都保留著一份副本。

如果我們重新啟動(dòng) Node1 憔鬼,集群可以將缺失的副本分片再次進(jìn)行分配龟劲,那么集群的狀態(tài)又將恢復(fù)到原來(lái)的正常狀態(tài)。

如果 Node1 依然擁有著之前的分片逊彭,它將嘗試去重用它們咸灿,只不過(guò)這時(shí) Node1 節(jié)點(diǎn)上的分片不再是主分片而是副本分片了,如果期間有更改的數(shù)據(jù)只需要從主分片上復(fù)制修改的數(shù)據(jù)文件即可侮叮。

小結(jié):

  • 將數(shù)據(jù)分片是為了提高可處理數(shù)據(jù)的容量和易于進(jìn)行水平擴(kuò)展,為分片做副本是為了提高集群的穩(wěn)定性和提高并發(fā)量悼瘾。

  • 副本是乘法囊榜,越多消耗越大,但也越保險(xiǎn)亥宿。分片是除法卸勺,分片越多,單分片數(shù)據(jù)就越少也越分散烫扼。

  • 副本越多曙求,集群的可用性就越高,但是由于每個(gè)分片都相當(dāng)于一個(gè) Lucene 的索引文件映企,會(huì)占用一定的文件句柄悟狱、內(nèi)存及 CPU。并且分片間的數(shù)據(jù)同步也會(huì)占用一定的網(wǎng)絡(luò)帶寬堰氓,所以索引的分片數(shù)和副本數(shù)也不是越多越好挤渐。

映射(Mapping)

映射是用于定義 ES 對(duì)索引中字段的存儲(chǔ)類(lèi)型、分詞方式和是否存儲(chǔ)等信息双絮,就像數(shù)據(jù)庫(kù)中的 Schema 浴麻,描述了文檔可能具有的字段或?qū)傩浴⒚總€(gè)字段的數(shù)據(jù)類(lèi)型囤攀。

只不過(guò)關(guān)系型數(shù)據(jù)庫(kù)建表時(shí)必須指定字段類(lèi)型软免,而 ES 對(duì)于字段類(lèi)型可以不指定然后動(dòng)態(tài)對(duì)字段類(lèi)型猜測(cè),也可以在創(chuàng)建索引時(shí)具體指定字段的類(lèi)型焚挠。

對(duì)字段類(lèi)型根據(jù)數(shù)據(jù)格式自動(dòng)識(shí)別的映射稱(chēng)之為動(dòng)態(tài)映射(Dynamic Mapping)膏萧,我們創(chuàng)建索引時(shí)具體定義字段類(lèi)型的映射稱(chēng)之為靜態(tài)映射或顯示映射(Explicit Mapping)。

在講解動(dòng)態(tài)映射和靜態(tài)映射的使用前,我們先來(lái)了解下 ES 中的數(shù)據(jù)有哪些字段類(lèi)型向抢?之后我們?cè)僦v解為什么我們創(chuàng)建索引時(shí)需要建立靜態(tài)映射而不使用動(dòng)態(tài)映射认境。

ES(v6.8)中字段數(shù)據(jù)類(lèi)型主要有以下幾類(lèi):

Text 用于索引全文值的字段,例如電子郵件正文或產(chǎn)品說(shuō)明挟鸠。這些字段是被分詞的叉信,它們通過(guò)分詞器傳遞 ,以在被索引之前將字符串轉(zhuǎn)換為單個(gè)術(shù)語(yǔ)的列表艘希。

分析過(guò)程允許 Elasticsearch 搜索單個(gè)單詞中每個(gè)完整的文本字段硼身。文本字段不用于排序,很少用于聚合覆享。

Keyword 用于索引結(jié)構(gòu)化內(nèi)容的字段佳遂,例如電子郵件地址,主機(jī)名撒顿,狀態(tài)代碼丑罪,郵政編碼或標(biāo)簽。它們通常用于過(guò)濾凤壁,排序吩屹,和聚合。Keyword 字段只能按其確切值進(jìn)行搜索拧抖。

通過(guò)對(duì)字段類(lèi)型的了解我們知道有些字段需要明確定義的煤搜,例如某個(gè)字段是 Text 類(lèi)型還是 Keyword 類(lèi)型差別是很大的,時(shí)間字段也許我們需要指定它的時(shí)間格式唧席,還有一些字段我們需要指定特定的分詞器等等擦盾。

如果采用動(dòng)態(tài)映射是不能精確做到這些的,自動(dòng)識(shí)別常常會(huì)與我們期望的有些差異淌哟。

所以創(chuàng)建索引的時(shí)候一個(gè)完整的格式應(yīng)該是指定分片和副本數(shù)以及 Mapping 的定義迹卢,如下:

PUT my_index   
{  
   "settings" : {  
      "number_of_shards" : 5,  
      "number_of_replicas" : 1  
   }  
  "mappings": {  
    "_doc": {   
      "properties": {   
        "title":    { "type": "text"  },   
        "name":     { "type": "text"  },   
        "age":      { "type": "integer" },    
        "created":  {  
          "type":   "date",   
          "format": "strict_date_optional_time||epoch_millis"  
        }  
      }  
    }  
  }  
} 

ES 的基本使用

在決定使用 Elasticsearch 的時(shí)候首先要考慮的是版本問(wèn)題,Elasticsearch (排除 0.x 和 1.x)目前有如下常用的穩(wěn)定的主版本:2.x绞绒,5.x婶希,6.x,7.x(current)蓬衡。

你可能會(huì)發(fā)現(xiàn)沒(méi)有 3.x 和 4.x喻杈,ES 從 2.4.6 直接跳到了 5.0.0。其實(shí)是為了 ELK(ElasticSearch狰晚,Logstash筒饰,Kibana)技術(shù)棧的版本統(tǒng)一,免的給用戶帶來(lái)混亂壁晒。

在 Elasticsearch 是 2.x (2.x 的最后一版 2.4.6 的發(fā)布時(shí)間是 July 25, 2017) 的情況下瓷们,Kibana 已經(jīng)是 4.x(Kibana 4.6.5 的發(fā)布時(shí)間是 July 25, 2017)。

那么在 Kibana 的下一主版本肯定是 5.x 了,所以 Elasticsearch 直接將自己的主版本發(fā)布為 5.0.0 了谬晕。

統(tǒng)一之后碘裕,我們選版本就不會(huì)猶豫困惑了,我們選定 Elasticsearch 的版本后再選擇相同版本的 Kibana 就行了攒钳,不用擔(dān)憂版本不兼容的問(wèn)題帮孔。

Elasticsearch 是使用 Java 構(gòu)建,所以除了注意 ELK 技術(shù)的版本統(tǒng)一不撑,我們?cè)谶x擇 Elasticsearch 的版本的時(shí)候還需要注意 JDK 的版本文兢。

因?yàn)槊總€(gè)大版本所依賴的 JDK 版本也不同,目前 7.2 版本已經(jīng)可以支持 JDK11焕檬。

安裝使用

①下載和解壓 Elasticsearch姆坚,無(wú)需安裝解壓后即可用,解壓后目錄如上圖:

  • bin:二進(jìn)制系統(tǒng)指令目錄实愚,包含啟動(dòng)命令和安裝插件命令等兼呵。
  • config:配置文件目錄。
  • data:數(shù)據(jù)存儲(chǔ)目錄爆侣。
  • lib:依賴包目錄萍程。
  • logs:日志文件目錄。
  • modules:模塊庫(kù)兔仰,例如 x-pack 的模塊。
  • plugins:插件目錄乎赴。

②安裝目錄下運(yùn)行 bin/elasticsearch 來(lái)啟動(dòng) ES。

③默認(rèn)在 9200 端口運(yùn)行潮尝,請(qǐng)求 curl http://localhost:9200/ 或者瀏覽器輸入 http://localhost:9200榕吼,得到一個(gè) JSON 對(duì)象,其中包含當(dāng)前節(jié)點(diǎn)勉失、集群羹蚣、版本等信息。

{  
  "name" : "U7fp3O9",  
  "cluster_name" : "elasticsearch",  
  "cluster_uuid" : "-Rj8jGQvRIelGd9ckicUOA",  
  "version" : {  
    "number" : "6.8.1",  
    "build_flavor" : "default",  
    "build_type" : "zip",  
    "build_hash" : "1fad4e1",  
    "build_date" : "2019-06-18T13:16:52.517138Z",  
    "build_snapshot" : false,  
    "lucene_version" : "7.7.0",  
    "minimum_wire_compatibility_version" : "5.6.0",  
    "minimum_index_compatibility_version" : "5.0.0"  
  },  
  "tagline" : "You Know, for Search"  
}  

集群健康狀態(tài)

要檢查群集運(yùn)行狀況乱凿,我們可以在 Kibana 控制臺(tái)中運(yùn)行以下命令 GET /_cluster/health顽素,得到如下信息:

{  
  "cluster_name" : "wujiajian",  
  "status" : "yellow",  
  "timed_out" : false,  
  "number_of_nodes" : 1,  
  "number_of_data_nodes" : 1,  
  "active_primary_shards" : 9,  
  "active_shards" : 9,  
  "relocating_shards" : 0,  
  "initializing_shards" : 0,  
  "unassigned_shards" : 5,  
  "delayed_unassigned_shards" : 0,  
  "number_of_pending_tasks" : 0,  
  "number_of_in_flight_fetch" : 0,  
  "task_max_waiting_in_queue_millis" : 0,  
  "active_shards_percent_as_number" : 64.28571428571429  
}  

集群狀態(tài)通過(guò) 綠,黃徒蟆,紅 來(lái)標(biāo)識(shí):

  • 綠色:集群健康完好胁出,一切功能齊全正常,所有分片和副本都可以正常工作段审。

  • 黃色:預(yù)警狀態(tài)全蝶,所有主分片功能正常,但至少有一個(gè)副本是不能正常工作的。此時(shí)集群是可以正常工作的抑淫,但是高可用性在某種程度上會(huì)受影響绷落。

  • 紅色:集群不可正常使用。某個(gè)或某些分片及其副本異常不可用始苇,這時(shí)集群的查詢操作還能執(zhí)行砌烁,但是返回的結(jié)果會(huì)不準(zhǔn)確。對(duì)于分配到這個(gè)分片的寫(xiě)入請(qǐng)求將會(huì)報(bào)錯(cuò)埂蕊,最終會(huì)導(dǎo)致數(shù)據(jù)的丟失往弓。

當(dāng)集群狀態(tài)為紅色時(shí),它將會(huì)繼續(xù)從可用的分片提供搜索請(qǐng)求服務(wù)蓄氧,但是你需要盡快修復(fù)那些未分配的分片函似。

ES 機(jī)制原理

ES 的基本概念和基本操作介紹完了之后,我們可能還有很多疑惑:

  • 它們內(nèi)部是如何運(yùn)行的喉童?

  • 主分片和副本分片是如何同步的撇寞?

  • 創(chuàng)建索引的流程是什么樣的?

  • ES 如何將索引數(shù)據(jù)分配到不同的分片上的堂氯?以及這些索引數(shù)據(jù)是如何存儲(chǔ)的蔑担?

  • 為什么說(shuō) ES 是近實(shí)時(shí)搜索引擎而文檔的 CRUD (創(chuàng)建-讀取-更新-刪除) 操作是實(shí)時(shí)的?

  • 以及 Elasticsearch 是怎樣保證更新被持久化在斷電時(shí)也不丟失數(shù)據(jù)咽白?

  • 還有為什么刪除文檔不會(huì)立刻釋放空間啤握?

帶著這些疑問(wèn)我們進(jìn)入接下來(lái)的內(nèi)容。

寫(xiě)索引原理

下圖描述了 3 個(gè)節(jié)點(diǎn)的集群晶框,共擁有 12 個(gè)分片排抬,其中有 4 個(gè)主分片(S0、S1授段、S2蹲蒲、S3)和 8 個(gè)副本分片(R0、R1侵贵、R2届搁、R3),每個(gè)主分片對(duì)應(yīng)兩個(gè)副本分片窍育,節(jié)點(diǎn) 1 是主節(jié)點(diǎn)(Master 節(jié)點(diǎn))負(fù)責(zé)整個(gè)集群的狀態(tài)卡睦。

寫(xiě)索引是只能寫(xiě)在主分片上,然后同步到副本分片蔫骂。這里有四個(gè)主分片么翰,一條數(shù)據(jù) ES 是根據(jù)什么規(guī)則寫(xiě)到特定分片上的呢?

這條索引數(shù)據(jù)為什么被寫(xiě)到 S0 上而不寫(xiě)到 S1 或 S2 上辽旋?那條數(shù)據(jù)為什么又被寫(xiě)到 S3 上而不寫(xiě)到 S0 上了浩嫌?

首先這肯定不會(huì)是隨機(jī)的檐迟,否則將來(lái)要獲取文檔的時(shí)候我們就不知道從何處尋找了。

實(shí)際上码耐,這個(gè)過(guò)程是根據(jù)下面這個(gè)公式?jīng)Q定的:

shard = hash(routing) % number_of_primary_shards  

Routing 是一個(gè)可變值追迟,默認(rèn)是文檔的_id ,也可以設(shè)置成一個(gè)自定義的值骚腥。

Routing 通過(guò) Hash 函數(shù)生成一個(gè)數(shù)字敦间,然后這個(gè)數(shù)字再除以 number_of_primary_shards (主分片的數(shù)量)后得到余數(shù)。

這個(gè)在 0 到 number_of_primary_shards-1 之間的余數(shù)束铭,就是我們所尋求的文檔所在分片的位置廓块。

這就解釋了為什么我們要在創(chuàng)建索引的時(shí)候就確定好主分片的數(shù)量并且永遠(yuǎn)不會(huì)改變這個(gè)數(shù)量:因?yàn)槿绻麛?shù)量變化了,那么所有之前路由的值都會(huì)無(wú)效契沫,文檔也再也找不到了带猴。

由于在 ES 集群中每個(gè)節(jié)點(diǎn)通過(guò)上面的計(jì)算公式都知道集群中的文檔的存放位置,所以每個(gè)節(jié)點(diǎn)都有處理讀寫(xiě)請(qǐng)求的能力懈万。

在一個(gè)寫(xiě)請(qǐng)求被發(fā)送到某個(gè)節(jié)點(diǎn)后拴清,該節(jié)點(diǎn)即為前面說(shuō)過(guò)的協(xié)調(diào)節(jié)點(diǎn),協(xié)調(diào)節(jié)點(diǎn)會(huì)根據(jù)路由公式計(jì)算出需要寫(xiě)到哪個(gè)分片上会通,再將請(qǐng)求轉(zhuǎn)發(fā)到該分片的主分片節(jié)點(diǎn)上口予。

假如此時(shí)數(shù)據(jù)通過(guò)路由計(jì)算公式取余后得到的值是 shard=hash(routing)%4=0

則具體流程如下:

  • 客戶端向 ES1 節(jié)點(diǎn)(協(xié)調(diào)節(jié)點(diǎn))發(fā)送寫(xiě)請(qǐng)求涕侈,通過(guò)路由計(jì)算公式得到值為 0沪停,則當(dāng)前數(shù)據(jù)應(yīng)被寫(xiě)到主分片 S0 上。

  • ES1 節(jié)點(diǎn)將請(qǐng)求轉(zhuǎn)發(fā)到 S0 主分片所在的節(jié)點(diǎn) ES3裳涛,ES3 接受請(qǐng)求并寫(xiě)入到磁盤(pán)牙甫。

  • 并發(fā)將數(shù)據(jù)復(fù)制到兩個(gè)副本分片 R0 上,其中通過(guò)樂(lè)觀并發(fā)控制數(shù)據(jù)的沖突调违。一旦所有的副本分片都報(bào)告成功,則節(jié)點(diǎn) ES3 將向協(xié)調(diào)節(jié)點(diǎn)報(bào)告成功泻轰,協(xié)調(diào)節(jié)點(diǎn)向客戶端報(bào)告成功技肩。

存儲(chǔ)原理

上面介紹了在 ES 內(nèi)部索引的寫(xiě)處理流程,這個(gè)流程是在 ES 的內(nèi)存中執(zhí)行的浮声,數(shù)據(jù)被分配到特定的分片和副本上之后虚婿,最終是存儲(chǔ)到磁盤(pán)上的,這樣在斷電的時(shí)候就不會(huì)丟失數(shù)據(jù)泳挥。

具體的存儲(chǔ)路徑可在配置文件 ../config/elasticsearch.yml中進(jìn)行設(shè)置然痊,默認(rèn)存儲(chǔ)在安裝目錄的 Data 文件夾下。

建議不要使用默認(rèn)值屉符,因?yàn)槿?ES 進(jìn)行了升級(jí)剧浸,則有可能導(dǎo)致數(shù)據(jù)全部丟失:

path.data: /path/to/data  //索引數(shù)據(jù)  
path.logs: /path/to/logs  //日志記錄  

①分段存儲(chǔ)

索引文檔以段的形式存儲(chǔ)在磁盤(pán)上锹引,何為段?索引文件被拆分為多個(gè)子文件唆香,則每個(gè)子文件叫作段嫌变,每一個(gè)段本身都是一個(gè)倒排索引,并且段具有不變性躬它,一旦索引的數(shù)據(jù)被寫(xiě)入硬盤(pán)腾啥,就不可再修改。

在底層采用了分段的存儲(chǔ)模式冯吓,使它在讀寫(xiě)時(shí)幾乎完全避免了鎖的出現(xiàn)倘待,大大提升了讀寫(xiě)性能。

段被寫(xiě)入到磁盤(pán)后會(huì)生成一個(gè)提交點(diǎn)组贺,提交點(diǎn)是一個(gè)用來(lái)記錄所有提交后段信息的文件凸舵。

一個(gè)段一旦擁有了提交點(diǎn),就說(shuō)明這個(gè)段只有讀的權(quán)限锣披,失去了寫(xiě)的權(quán)限贞间。相反,當(dāng)段在內(nèi)存中時(shí)雹仿,就只有寫(xiě)的權(quán)限增热,而不具備讀數(shù)據(jù)的權(quán)限,意味著不能被檢索胧辽。

段的概念提出主要是因?yàn)椋涸谠缙谌臋z索中為整個(gè)文檔集合建立了一個(gè)很大的倒排索引峻仇,并將其寫(xiě)入磁盤(pán)中。

如果索引有更新邑商,就需要重新全量創(chuàng)建一個(gè)索引來(lái)替換原來(lái)的索引摄咆。這種方式在數(shù)據(jù)量很大時(shí)效率很低,并且由于創(chuàng)建一次索引的成本很高人断,所以對(duì)數(shù)據(jù)的更新不能過(guò)于頻繁吭从,也就不能保證時(shí)效性。

索引文件分段存儲(chǔ)并且不可修改恶迈,那么新增涩金、更新和刪除如何處理呢?

  • 新增暇仲,新增很好處理步做,由于數(shù)據(jù)是新的,所以只需要對(duì)當(dāng)前文檔新增一個(gè)段就可以了奈附。

  • 刪除全度,由于不可修改,所以對(duì)于刪除操作斥滤,不會(huì)把文檔從舊的段中移除而是通過(guò)新增一個(gè) .del 文件将鸵,文件中會(huì)列出這些被刪除文檔的段信息勉盅。這個(gè)被標(biāo)記刪除的文檔仍然可以被查詢匹配到, 但它會(huì)在最終結(jié)果被返回前從結(jié)果集中移除咨堤。

  • 更新菇篡,不能修改舊的段來(lái)進(jìn)行反映文檔的更新,其實(shí)更新相當(dāng)于是刪除和新增這兩個(gè)動(dòng)作組成一喘。會(huì)將舊的文檔在 .del 文件中標(biāo)記刪除驱还,然后文檔的新版本被索引到一個(gè)新的段中⊥箍耍可能兩個(gè)版本的文檔都會(huì)被一個(gè)查詢匹配到议蟆,但被刪除的那個(gè)舊版本文檔在結(jié)果集返回前就會(huì)被移除。

段被設(shè)定為不可修改具有一定的優(yōu)勢(shì)也有一定的缺點(diǎn)萎战,優(yōu)勢(shì)主要表現(xiàn)在:

  • 不需要鎖咐容。如果你從來(lái)不更新索引,你就不需要擔(dān)心多進(jìn)程同時(shí)修改數(shù)據(jù)的問(wèn)題蚂维。

  • 一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存戳粒,便會(huì)留在哪里,由于其不變性虫啥。只要文件系統(tǒng)緩存中還有足夠的空間蔚约,那么大部分讀請(qǐng)求會(huì)直接請(qǐng)求內(nèi)存,而不會(huì)命中磁盤(pán)涂籽。這提供了很大的性能提升苹祟。

  • 其它緩存(像 Filter 緩存),在索引的生命周期內(nèi)始終有效评雌。它們不需要在每次數(shù)據(jù)改變時(shí)被重建树枫,因?yàn)閿?shù)據(jù)不會(huì)變化。

  • 寫(xiě)入單個(gè)大的倒排索引允許數(shù)據(jù)被壓縮景东,減少磁盤(pán) I/O 和需要被緩存到內(nèi)存的索引的使用量砂轻。

段的不變性的缺點(diǎn)如下:

  • 當(dāng)對(duì)舊數(shù)據(jù)進(jìn)行刪除時(shí),舊數(shù)據(jù)不會(huì)馬上被刪除斤吐,而是在 .del 文件中被標(biāo)記為刪除舔清。而舊數(shù)據(jù)只能等到段更新時(shí)才能被移除,這樣會(huì)造成大量的空間浪費(fèi)曲初。

  • 若有一條數(shù)據(jù)頻繁的更新,每次更新都是新增新的標(biāo)記舊的杯聚,則會(huì)有大量的空間浪費(fèi)臼婆。

  • 每次新增數(shù)據(jù)時(shí)都需要新增一個(gè)段來(lái)存儲(chǔ)數(shù)據(jù)。當(dāng)段的數(shù)量太多時(shí)幌绍,對(duì)服務(wù)器的資源例如文件句柄的消耗會(huì)非常大颁褂。

  • 在查詢的結(jié)果中包含所有的結(jié)果集故响,需要排除被標(biāo)記刪除的舊數(shù)據(jù),這增加了查詢的負(fù)擔(dān)颁独。

②延遲寫(xiě)策略

介紹完了存儲(chǔ)的形式彩届,那么索引寫(xiě)入到磁盤(pán)的過(guò)程是怎樣的?是否是直接調(diào) Fsync 物理性地寫(xiě)入磁盤(pán)誓酒?

答案是顯而易見(jiàn)的樟蠕,如果是直接寫(xiě)入到磁盤(pán)上,磁盤(pán)的 I/O 消耗上會(huì)嚴(yán)重影響性能靠柑。

那么當(dāng)寫(xiě)數(shù)據(jù)量大的時(shí)候會(huì)造成 ES 停頓卡死寨辩,查詢也無(wú)法做到快速響應(yīng)。如果真是這樣 ES 也就不會(huì)稱(chēng)之為近實(shí)時(shí)全文搜索引擎了歼冰。

為了提升寫(xiě)的性能靡狞,ES 并沒(méi)有每新增一條數(shù)據(jù)就增加一個(gè)段到磁盤(pán)上,而是采用延遲寫(xiě)的策略隔嫡。

每當(dāng)有新增的數(shù)據(jù)時(shí)甸怕,就將其先寫(xiě)入到內(nèi)存中,在內(nèi)存和磁盤(pán)之間是文件系統(tǒng)緩存腮恩。

當(dāng)達(dá)到默認(rèn)的時(shí)間(1 秒鐘)或者內(nèi)存的數(shù)據(jù)達(dá)到一定量時(shí)梢杭,會(huì)觸發(fā)一次刷新(Refresh),將內(nèi)存中的數(shù)據(jù)生成到一個(gè)新的段上并緩存到文件緩存系統(tǒng) 上庆揪,稍后再被刷新到磁盤(pán)中并生成提交點(diǎn)式曲。

這里的內(nèi)存使用的是 ES 的 JVM 內(nèi)存,而文件緩存系統(tǒng)使用的是操作系統(tǒng)的內(nèi)存缸榛。

新的數(shù)據(jù)會(huì)繼續(xù)的被寫(xiě)入內(nèi)存吝羞,但內(nèi)存中的數(shù)據(jù)并不是以段的形式存儲(chǔ)的,因此不能提供檢索功能内颗。

由內(nèi)存刷新到文件緩存系統(tǒng)的時(shí)候會(huì)生成新的段钧排,并將段打開(kāi)以供搜索使用,而不需要等到被刷新到磁盤(pán)均澳。

在 Elasticsearch 中恨溜,寫(xiě)入和打開(kāi)一個(gè)新段的輕量的過(guò)程叫做 Refresh (即內(nèi)存刷新到文件緩存系統(tǒng))。

默認(rèn)情況下每個(gè)分片會(huì)每秒自動(dòng)刷新一次找前。這就是為什么我們說(shuō) Elasticsearch 是近實(shí)時(shí)搜索糟袁,因?yàn)槲臋n的變化并不是立即對(duì)搜索可見(jiàn),但會(huì)在一秒之內(nèi)變?yōu)榭梢?jiàn)躺盛。

我們也可以手動(dòng)觸發(fā) Refresh项戴,POST /_refresh 刷新所有索引,POST /nba/_refresh 刷新指定的索引槽惫。

Tips:盡管刷新是比提交輕量很多的操作周叮,它還是會(huì)有性能開(kāi)銷(xiāo)辩撑。當(dāng)寫(xiě)測(cè)試的時(shí)候, 手動(dòng)刷新很有用仿耽,但是不要在生產(chǎn)>環(huán)境下每次索引一個(gè)文檔都去手動(dòng)刷新合冀。而且并不是所有的情況都需要每秒刷新。

可能你正在使用 Elasticsearch 索引大量的日志文件项贺, 你可能想優(yōu)化索引速度而不是>近實(shí)時(shí)搜索君躺。

這時(shí)可以在創(chuàng)建索引時(shí)在 Settings 中通過(guò)調(diào)大 refresh_interval = "30s"的值 , 降低每個(gè)索引的刷新頻率敬扛,設(shè)值時(shí)需要注意后面帶上時(shí)間單位晰洒,否則默認(rèn)是毫秒。當(dāng) refresh_interval=-1 時(shí)表示關(guān)閉索引的自動(dòng)刷新啥箭。

雖然通過(guò)延時(shí)寫(xiě)的策略可以減少數(shù)據(jù)往磁盤(pán)上寫(xiě)的次數(shù)提升了整體的寫(xiě)入能力谍珊,但是我們知道文件緩存系統(tǒng)也是內(nèi)存空間,屬于操作系統(tǒng)的內(nèi)存急侥,只要是內(nèi)存都存在斷電或異常情況下丟失數(shù)據(jù)的危險(xiǎn)砌滞。

為了避免丟失數(shù)據(jù),Elasticsearch 添加了事務(wù)日志(Translog)坏怪,事務(wù)日志記錄了所有還沒(méi)有持久化到磁盤(pán)的數(shù)據(jù)贝润。

添加了事務(wù)日志后整個(gè)寫(xiě)索引的流程如上圖所示:

  • 一個(gè)新文檔被索引之后,先被寫(xiě)入到內(nèi)存中铝宵,但是為了防止數(shù)據(jù)的丟失打掘,會(huì)追加一份數(shù)據(jù)到事務(wù)日志中。

    不斷有新的文檔被寫(xiě)入到內(nèi)存鹏秋,同時(shí)也都會(huì)記錄到事務(wù)日志中尊蚁。這時(shí)新數(shù)據(jù)還不能被檢索和查詢。

  • 當(dāng)達(dá)到默認(rèn)的刷新時(shí)間或內(nèi)存中的數(shù)據(jù)達(dá)到一定量后侣夷,會(huì)觸發(fā)一次 Refresh横朋,將內(nèi)存中的數(shù)據(jù)以一個(gè)新段形式刷新到文件緩存系統(tǒng)中并清空內(nèi)存。這時(shí)雖然新段未被提交到磁盤(pán)百拓,但是可以提供文檔的檢索功能且不能被修改琴锭。

  • 隨著新文檔索引不斷被寫(xiě)入,當(dāng)日志數(shù)據(jù)大小超過(guò) 512M 或者時(shí)間超過(guò) 30 分鐘時(shí)衙传,會(huì)觸發(fā)一次 Flush决帖。

    內(nèi)存中的數(shù)據(jù)被寫(xiě)入到一個(gè)新段同時(shí)被寫(xiě)入到文件緩存系統(tǒng),文件系統(tǒng)緩存中數(shù)據(jù)通過(guò) Fsync 刷新到磁盤(pán)中蓖捶,生成提交點(diǎn)古瓤,日志文件被刪除,創(chuàng)建一個(gè)空的新日志。

通過(guò)這種方式當(dāng)斷電或需要重啟時(shí)落君,ES 不僅要根據(jù)提交點(diǎn)去加載已經(jīng)持久化過(guò)的段,還需要工具 Translog 里的記錄亭引,把未持久化的數(shù)據(jù)重新持久化到磁盤(pán)上绎速,避免了數(shù)據(jù)丟失的可能。

③段合并

由于自動(dòng)刷新流程每秒會(huì)創(chuàng)建一個(gè)新的段 焙蚓,這樣會(huì)導(dǎo)致短時(shí)間內(nèi)的段數(shù)量暴增纹冤。而段數(shù)目太多會(huì)帶來(lái)較大的麻煩。

每一個(gè)段都會(huì)消耗文件句柄购公、內(nèi)存和 CPU 運(yùn)行周期萌京。更重要的是,每個(gè)搜索請(qǐng)求都必須輪流檢查每個(gè)段然后合并查詢結(jié)果宏浩,所以段越多知残,搜索也就越慢。

Elasticsearch 通過(guò)在后臺(tái)定期進(jìn)行段合并來(lái)解決這個(gè)問(wèn)題比庄。小的段被合并到大的段求妹,然后這些大的段再被合并到更大的段。

段合并的時(shí)候會(huì)將那些舊的已刪除文檔從文件系統(tǒng)中清除佳窑。被刪除的文檔不會(huì)被拷貝到新的大段中制恍。合并的過(guò)程中不會(huì)中斷索引和搜索。

段合并在進(jìn)行索引和搜索時(shí)會(huì)自動(dòng)進(jìn)行神凑,合并進(jìn)程選擇一小部分大小相似的段净神,并且在后臺(tái)將它們合并到更大的段中,這些段既可以是未提交的也可以是已提交的溉委。

合并結(jié)束后老的段會(huì)被刪除鹃唯,新的段被 Flush 到磁盤(pán),同時(shí)寫(xiě)入一個(gè)包含新段且排除舊的和較小的段的新提交點(diǎn)薛躬,新的段被打開(kāi)可以用來(lái)搜索俯渤。

段合并的計(jì)算量龐大, 而且還要吃掉大量磁盤(pán) I/O型宝,段合并會(huì)拖累寫(xiě)入速率八匠,如果任其發(fā)展會(huì)影響搜索性能。

Elasticsearch 在默認(rèn)情況下會(huì)對(duì)合并流程進(jìn)行資源限制趴酣,所以搜索仍然有足夠的資源很好地執(zhí)行梨树。

性能優(yōu)化

存儲(chǔ)設(shè)備

磁盤(pán)在現(xiàn)代服務(wù)器上通常都是瓶頸。Elasticsearch 重度使用磁盤(pán)岖寞,你的磁盤(pán)能處理的吞吐量越大抡四,你的節(jié)點(diǎn)就越穩(wěn)定。

這里有一些優(yōu)化磁盤(pán) I/O 的技巧:

  • 使用 SSD。就像其他地方提過(guò)的指巡, 他們比機(jī)械磁盤(pán)優(yōu)秀多了淑履。

  • 使用 RAID 0。條帶化 RAID 會(huì)提高磁盤(pán) I/O藻雪,代價(jià)顯然就是當(dāng)一塊硬盤(pán)故障時(shí)整個(gè)就故障了秘噪。不要使用鏡像或者奇偶校驗(yàn) RAID 因?yàn)楦北疽呀?jīng)提供了這個(gè)功能。

  • 另外勉耀,使用多塊硬盤(pán)指煎,并允許 Elasticsearch 通過(guò)多個(gè) path.data 目錄配置把數(shù)據(jù)條帶化分配到它們上面。

  • 不要使用遠(yuǎn)程掛載的存儲(chǔ)便斥,比如 NFS 或者 SMB/CIFS至壤。這個(gè)引入的延遲對(duì)性能來(lái)說(shuō)完全是背道而馳的。

  • 如果你用的是 EC2枢纠,當(dāng)心 EBS像街。即便是基于 SSD 的 EBS,通常也比本地實(shí)例的存儲(chǔ)要慢京郑。

內(nèi)部索引優(yōu)化

Elasticsearch 為了能快速找到某個(gè) Term宅广,先將所有的 Term 排個(gè)序,然后根據(jù)二分法查找 Term些举,時(shí)間復(fù)雜度為 logN跟狱,就像通過(guò)字典查找一樣,這就是 Term Dictionary户魏。

現(xiàn)在再看起來(lái)驶臊,似乎和傳統(tǒng)數(shù)據(jù)庫(kù)通過(guò) B-Tree 的方式類(lèi)似。但是如果 Term 太多叼丑,Term Dictionary 也會(huì)很大关翎,放內(nèi)存不現(xiàn)實(shí),于是有了 Term Index鸠信。

就像字典里的索引頁(yè)一樣纵寝,A 開(kāi)頭的有哪些 Term,分別在哪頁(yè)星立,可以理解 Term Index是一棵樹(shù)爽茴。

這棵樹(shù)不會(huì)包含所有的 Term,它包含的是 Term 的一些前綴绰垂。通過(guò) Term Index 可以快速地定位到 Term Dictionary 的某個(gè) Offset室奏,然后從這個(gè)位置再往后順序查找。

在內(nèi)存中用 FST 方式壓縮 Term Index劲装,F(xiàn)ST 以字節(jié)的方式存儲(chǔ)所有的 Term胧沫,這種壓縮方式可以有效的縮減存儲(chǔ)空間昌简,使得 Term Index 足以放進(jìn)內(nèi)存跨细,但這種方式也會(huì)導(dǎo)致查找時(shí)需要更多的 CPU 資源蒲牧。

對(duì)于存儲(chǔ)在磁盤(pán)上的倒排表同樣也采用了壓縮技術(shù)減少存儲(chǔ)所占用的空間。

調(diào)整配置參數(shù)

調(diào)整配置參數(shù)建議如下:

  • 給每個(gè)文檔指定有序的具有壓縮良好的序列模式 ID励稳,避免隨機(jī)的 UUID-4 這樣的 ID南蹂,這樣的 ID 壓縮比很低址否,會(huì)明顯拖慢 Lucene。

  • 對(duì)于那些不需要聚合和排序的索引字段禁用 Doc values碎紊。Doc Values 是有序的基于 document=>field value 的映射列表。

  • 不需要做模糊檢索的字段使用 Keyword 類(lèi)型代替 Text 類(lèi)型樊诺,這樣可以避免在建立索引前對(duì)這些文本進(jìn)行分詞仗考。

  • 如果你的搜索結(jié)果不需要近實(shí)時(shí)的準(zhǔn)確度,考慮把每個(gè)索引的 index.refresh_interval 改到 30s 词爬。

    如果你是在做大批量導(dǎo)入秃嗜,導(dǎo)入期間你可以通過(guò)設(shè)置這個(gè)值為 -1 關(guān)掉刷新,還可以通過(guò)設(shè)置 index.number_of_replicas: 0 關(guān)閉副本顿膨。別忘記在完工的時(shí)候重新開(kāi)啟它锅锨。

  • 避免深度分頁(yè)查詢建議使用 Scroll 進(jìn)行分頁(yè)查詢。普通分頁(yè)查詢時(shí)恋沃,會(huì)創(chuàng)建一個(gè) from+size 的空優(yōu)先隊(duì)列必搞,每個(gè)分片會(huì)返回 from+size 條數(shù)據(jù),默認(rèn)只包含文檔 ID 和得分 Score 給協(xié)調(diào)節(jié)點(diǎn)囊咏。

    如果有 N 個(gè)分片恕洲,則協(xié)調(diào)節(jié)點(diǎn)再對(duì)(from+size)×n 條數(shù)據(jù)進(jìn)行二次排序,然后選擇需要被取回的文檔梅割。當(dāng) from 很大時(shí)霜第,排序過(guò)程會(huì)變得很沉重,占用 CPU 資源嚴(yán)重户辞。

  • 減少映射字段泌类,只提供需要檢索,聚合或排序的字段底燎。其他字段可存在其他存儲(chǔ)設(shè)備上刃榨,例如 Hbase,在 ES 中得到結(jié)果后再去 Hbase 查詢這些字段书蚪。

  • 創(chuàng)建索引和查詢時(shí)指定路由 Routing 值喇澡,這樣可以精確到具體的分片查詢,提升查詢效率殊校。路由的選擇需要注意數(shù)據(jù)的分布均衡晴玖。

JVM 調(diào)優(yōu)

JVM 調(diào)優(yōu)建議如下:

  • 確保堆內(nèi)存最小值( Xms )與最大值( Xmx )的大小是相同的,防止程序在運(yùn)行時(shí)改變堆內(nèi)存大小。Elasticsearch 默認(rèn)安裝后設(shè)置的堆內(nèi)存是 1GB呕屎∪貌荆可通過(guò)../config/jvm.option 文件進(jìn)行配置,但是最好不要超過(guò)物理內(nèi)存的50%和超過(guò) 32GB秀睛。

  • GC 默認(rèn)采用 CMS 的方式尔当,并發(fā)但是有 STW 的問(wèn)題,可以考慮使用 G1 收集器蹂安。

  • ES 非常依賴文件系統(tǒng)緩存(Filesystem Cache)椭迎,快速搜索。一般來(lái)說(shuō)田盈,應(yīng)該至少確保物理上有一半的可用內(nèi)存分配到文件系統(tǒng)緩存畜号。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市允瞧,隨后出現(xiàn)的幾起案子简软,更是在濱河造成了極大的恐慌,老刑警劉巖述暂,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痹升,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡畦韭,警方通過(guò)查閱死者的電腦和手機(jī)疼蛾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)廊驼,“玉大人据过,你說(shuō)我怎么就攤上這事《士妫” “怎么了绳锅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)酝掩。 經(jīng)常有香客問(wèn)我鳞芙,道長(zhǎng),這世上最難降的妖魔是什么期虾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任原朝,我火速辦了婚禮,結(jié)果婚禮上镶苞,老公的妹妹穿的比我還像新娘喳坠。我一直安慰自己,他們只是感情好茂蚓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布壕鹉。 她就那樣靜靜地躺著剃幌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晾浴。 梳的紋絲不亂的頭發(fā)上负乡,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音脊凰,去河邊找鬼抖棘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狸涌,可吹牛的內(nèi)容都是我干的切省。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼帕胆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼数尿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起惶楼,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诊杆,沒(méi)想到半個(gè)月后歼捐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晨汹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年豹储,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘这。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剥扣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铝穷,到底是詐尸還是另有隱情钠怯,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布曙聂,位于F島的核電站晦炊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宁脊。R本人自食惡果不足惜断国,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榆苞。 院中可真熱鬧稳衬,春花似錦、人聲如沸坐漏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至输涕,卻和暖如春音婶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莱坎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工衣式, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檐什。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓碴卧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親乃正。 傳聞我的和親對(duì)象是個(gè)殘疾皇子住册,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容