Elasticsearch是如何做到快速索引的

最近在參與一個(gè)基于Elasticsearch作為底層數(shù)據(jù)框架提供大數(shù)據(jù)量(億級(jí))的實(shí)時(shí)統(tǒng)計(jì)查詢的方案設(shè)計(jì)工作左冬,花了些時(shí)間學(xué)習(xí)Elasticsearch的基礎(chǔ)理論知識(shí),整理了一下遵馆,希望能對(duì)Elasticsearch感興趣/想了解的同學(xué)有所幫助。 同時(shí)也希望有發(fā)現(xiàn)內(nèi)容不正確或者有疑問(wèn)的地方,望指明古今,一起探討,學(xué)習(xí)滔以,進(jìn)步捉腥。

介紹

Elasticsearch 是一個(gè)分布式可擴(kuò)展的實(shí)時(shí)搜索和分析引擎.

Elasticsearch 是一個(gè)建立在全文搜索引擎 Apache Lucene(TM) 基礎(chǔ)上的搜索引擎. 當(dāng)然 Elasticsearch 并不僅僅是 Lucene 那么簡(jiǎn)單,它不僅包括了全文搜索功能你画,還可以進(jìn)行以下工作:

分布式實(shí)時(shí)文件存儲(chǔ)抵碟,并將每一個(gè)字段都編入索引桃漾,使其可以被搜索。

實(shí)時(shí)分析的分布式搜索引擎立磁。

可以擴(kuò)展到上百臺(tái)服務(wù)器呈队,處理PB級(jí)別的結(jié)構(gòu)化或非結(jié)構(gòu)化數(shù)據(jù)。

基本概念

先說(shuō)Elasticsearch的文件存儲(chǔ)唱歧,Elasticsearch是面向文檔型數(shù)據(jù)庫(kù)宪摧,一條數(shù)據(jù)在這里就是一個(gè)文檔,用JSON作為文檔序列化的格式颅崩,比如下面這條用戶數(shù)據(jù):

{"name":"John","sex":"Male","age":25,"birthDate":"1990/05/01","about":"I love to go rock climbing","interests":["sports","music"]}

用Mysql這樣的數(shù)據(jù)庫(kù)存儲(chǔ)就會(huì)容易想到建立一張User表几于,有balabala的字段等,在Elasticsearch里這就是一個(gè)文檔沿后,當(dāng)然這個(gè)文檔會(huì)屬于一個(gè)User的類(lèi)型沿彭,各種各樣的類(lèi)型存在于一個(gè)索引當(dāng)中。這里有一份簡(jiǎn)易的將Elasticsearch和關(guān)系型數(shù)據(jù)術(shù)語(yǔ)對(duì)照表:

關(guān)系數(shù)據(jù)庫(kù) ? 數(shù)據(jù)庫(kù) ? 表 ? 行 ? 列(Columns)

Elasticsearch ? 索引 ? 類(lèi)型 ? 文檔 ? 字段(Fields)

一個(gè) Elasticsearch 集群可以包含多個(gè)索引(數(shù)據(jù)庫(kù))尖滚,也就是說(shuō)其中包含了很多類(lèi)型(表)喉刘。這些類(lèi)型中包含了很多的文檔(行),然后每個(gè)文檔中又包含了很多的字段(列)漆弄。

Elasticsearch的交互睦裳,可以使用Java API,也可以直接使用HTTP的Restful API方式撼唾,比如我們打算插入一條記錄廉邑,可以簡(jiǎn)單發(fā)送一個(gè)HTTP的請(qǐng)求:

PUT/megacorp/employee/1{"name":"John","sex":"Male","age":25,"about":"I love to go rock climbing","interests":["sports","music"]}

更新,查詢也是類(lèi)似這樣的操作倒谷,具體操作手冊(cè)可以參見(jiàn)Elasticsearch權(quán)威指南

索引

Elasticsearch最關(guān)鍵的就是提供強(qiáng)大的索引能力了蛛蒙,其實(shí)InfoQ的這篇時(shí)間序列數(shù)據(jù)庫(kù)的秘密(2)——索引寫(xiě)的非常好,我這里也是圍繞這篇結(jié)合自己的理解進(jìn)一步梳理下渤愁,也希望可以幫助大家更好的理解這篇文章牵祟。

Elasticsearch索引的精髓:

一切設(shè)計(jì)都是為了提高搜索的性能

另一層意思:為了提高搜索的性能,難免會(huì)犧牲某些其他方面抖格,比如插入/更新诺苹,否則其他數(shù)據(jù)庫(kù)不用混了:)

前面看到往Elasticsearch里插入一條記錄,其實(shí)就是直接PUT一個(gè)json的對(duì)象他挎,這個(gè)對(duì)象有多個(gè)fields筝尾,比如上面例子中的name, sex, age, about, interests,那么在插入這些數(shù)據(jù)到Elasticsearch的同時(shí)办桨,Elasticsearch還默默1的為這些字段建立索引–倒排索引筹淫,因?yàn)镋lasticsearch最核心功能是搜索。

Elasticsearch是如何做到快速索引的

InfoQ那篇文章里說(shuō)Elasticsearch使用的倒排索引比關(guān)系型數(shù)據(jù)庫(kù)的B-Tree索引快,為什么呢损姜?

什么是B-Tree索引?

上大學(xué)讀書(shū)時(shí)老師教過(guò)我們饰剥,二叉樹(shù)查找效率是logN,同時(shí)插入新的節(jié)點(diǎn)不必移動(dòng)全部節(jié)點(diǎn)摧阅,所以用樹(shù)型結(jié)構(gòu)存儲(chǔ)索引汰蓉,能同時(shí)兼顧插入和查詢的性能。

因此在這個(gè)基礎(chǔ)上棒卷,再結(jié)合磁盤(pán)的讀取特性(順序讀/隨機(jī)讀)顾孽,傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)采用了B-Tree/B+Tree這樣的數(shù)據(jù)結(jié)構(gòu):

為了提高查詢的效率,減少磁盤(pán)尋道次數(shù)比规,將多個(gè)值作為一個(gè)數(shù)組通過(guò)連續(xù)區(qū)間存放若厚,一次尋道讀取多個(gè)數(shù)據(jù),同時(shí)也降低樹(shù)的高度蜒什。

什么是倒排索引?

繼續(xù)上面的例子测秸,假設(shè)有這么幾條數(shù)據(jù)(為了簡(jiǎn)單,去掉about, interests這兩個(gè)field):

IDNameAgeSex

1Kate24Female

2John24Male

3Bill29Male

ID是Elasticsearch自建的文檔id灾常,那么Elasticsearch建立的索引如下:

Name:

TermPosting List

Kate1

John2

Bill3

Age:

TermPosting List

24[1,2]

293

Sex:

TermPosting List

Female1

Male[2,3]

Posting List

Elasticsearch分別為每個(gè)field都建立了一個(gè)倒排索引霎冯,Kate, John, 24, Female這些叫term,而[1,2]就是Posting List钞瀑。Posting list就是一個(gè)int的數(shù)組沈撞,存儲(chǔ)了所有符合某個(gè)term的文檔id。

看到這里仔戈,不要認(rèn)為就結(jié)束了关串,精彩的部分才剛開(kāi)始…

通過(guò)posting list這種索引方式似乎可以很快進(jìn)行查找拧廊,比如要找age=24的同學(xué)监徘,愛(ài)回答問(wèn)題的小明馬上就舉手回答:我知道,id是1吧碾,2的同學(xué)凰盔。但是,如果這里有上千萬(wàn)的記錄呢倦春?如果是想通過(guò)name來(lái)查找呢户敬?

Term Dictionary

Elasticsearch為了能快速找到某個(gè)term,將所有的term排個(gè)序睁本,二分法查找term尿庐,logN的查找效率,就像通過(guò)字典查找一樣呢堰,這就是Term Dictionary〕現(xiàn)在再看起來(lái),似乎和傳統(tǒng)數(shù)據(jù)庫(kù)通過(guò)B-Tree的方式類(lèi)似啊枉疼,為什么說(shuō)比B-Tree的查詢快呢皮假?

Term Index

B-Tree通過(guò)減少磁盤(pán)尋道次數(shù)來(lái)提高查詢性能鞋拟,Elasticsearch也是采用同樣的思路,直接通過(guò)內(nèi)存查找term惹资,不讀磁盤(pán)贺纲,但是如果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è)位置再往后順序查找笆包。

所以term index不需要存下所有的term环揽,而僅僅是他們的一些前綴與Term Dictionary的block之間的映射關(guān)系,再結(jié)合FST(Finite State Transducers)的壓縮技術(shù)庵佣,可以使term index緩存到內(nèi)存中歉胶。從term index查到對(duì)應(yīng)的term dictionary的block位置之后,再去磁盤(pán)上找term巴粪,大大減少了磁盤(pán)隨機(jī)讀的次數(shù)通今。

這時(shí)候愛(ài)提問(wèn)的小明又舉手了:”那個(gè)FST是神馬東東啊?”

一看就知道小明是一個(gè)上大學(xué)讀書(shū)的時(shí)候跟我一樣不認(rèn)真聽(tīng)課的孩子,數(shù)據(jù)結(jié)構(gòu)老師一定講過(guò)什么是FST肛根。但沒(méi)辦法辫塌,我也忘了,這里再補(bǔ)下課:

FSTs are finite-state machines thatmapaterm (byte sequence)to an arbitraryoutput.

假設(shè)我們現(xiàn)在要將mop, moth, pop, star, stop and top(term index里的term前綴)映射到序號(hào):0派哲,1臼氨,2,3芭届,4储矩,5(term dictionary的block位置)。最簡(jiǎn)單的做法就是定義個(gè)Map褂乍,大家找到自己的位置對(duì)應(yīng)入座就好了持隧,但從內(nèi)存占用少的角度想想,有沒(méi)有更優(yōu)的辦法呢逃片?答案就是:FST(理論依據(jù)在此屡拨,但我相信99%的人不會(huì)認(rèn)真看完的)

??表示一種狀態(tài)

–>表示狀態(tài)的變化過(guò)程,上面的字母/數(shù)字表示狀態(tài)變化和權(quán)重

將單詞分成單個(gè)字母通過(guò)??和–>表示出來(lái)赡勘,0權(quán)重不顯示策吠。如果??后面出現(xiàn)分支,就標(biāo)記權(quán)重孵班,最后整條路徑上的權(quán)重加起來(lái)就是這個(gè)單詞對(duì)應(yīng)的序號(hào)赠潦。

FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output.

FST以字節(jié)的方式存儲(chǔ)所有的term叫胖,這種壓縮方式可以有效的縮減存儲(chǔ)空間,使得term index足以放進(jìn)內(nèi)存她奥,但這種方式也會(huì)導(dǎo)致查找時(shí)需要更多的CPU資源瓮增。

后面的更精彩,看累了的同學(xué)可以喝杯咖啡……

壓縮技巧

Elasticsearch里除了上面說(shuō)到用FST壓縮term index外哩俭,對(duì)posting list也有壓縮技巧绷跑。 小明喝完咖啡又舉手了:”posting list不是已經(jīng)只存儲(chǔ)文檔id了嗎?還需要壓縮凡资?”

嗯砸捏,我們?cè)倏椿刈铋_(kāi)始的例子,如果Elasticsearch需要對(duì)同學(xué)的性別進(jìn)行索引(這時(shí)傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)已經(jīng)哭暈在廁所……)隙赁,會(huì)怎樣垦藏?如果有上千萬(wàn)個(gè)同學(xué),而世界上只有男/女這樣兩個(gè)性別伞访,每個(gè)posting list都會(huì)有至少百萬(wàn)個(gè)文檔id掂骏。 Elasticsearch是如何有效的對(duì)這些文檔id壓縮的呢?

Frame Of Reference

增量編碼壓縮厚掷,將大數(shù)變小數(shù)弟灼,按字節(jié)存儲(chǔ)

首先,Elasticsearch要求posting list是有序的(為了提高搜索的性能冒黑,再任性的要求也得滿足)田绑,這樣做的一個(gè)好處是方便壓縮,看下面這個(gè)圖例:

如果數(shù)學(xué)不是體育老師教的話薛闪,還是比較容易看出來(lái)這種壓縮技巧的辛馆。

原理就是通過(guò)增量俺陋,將原來(lái)的大數(shù)變成小數(shù)僅存儲(chǔ)增量值豁延,再精打細(xì)算按bit排好隊(duì),最后通過(guò)字節(jié)存儲(chǔ)腊状,而不是大大咧咧的盡管是2也是用int(4個(gè)字節(jié))來(lái)存儲(chǔ)诱咏。

Roaring bitmaps

說(shuō)到Roaring bitmaps,就必須先從bitmap說(shuō)起缴挖。Bitmap是一種數(shù)據(jù)結(jié)構(gòu)袋狞,假設(shè)有某個(gè)posting list:

[1,3,4,7,10]

對(duì)應(yīng)的bitmap就是:

[1,0,1,1,0,0,1,0,0,1]

非常直觀,用0/1表示某個(gè)值是否存在,比如10這個(gè)值就對(duì)應(yīng)第10位苟鸯,對(duì)應(yīng)的bit值是1同蜻,這樣用一個(gè)字節(jié)就可以代表8個(gè)文檔id,舊版本(5.0之前)的Lucene就是用這樣的方式來(lái)壓縮的早处,但這樣的壓縮方式仍然不夠高效湾蔓,如果有1億個(gè)文檔,那么需要12.5MB的存儲(chǔ)空間砌梆,這僅僅是對(duì)應(yīng)一個(gè)索引字段(我們往往會(huì)有很多個(gè)索引字段)默责。于是有人想出了Roaring bitmaps這樣更高效的數(shù)據(jù)結(jié)構(gòu)。

Bitmap的缺點(diǎn)是存儲(chǔ)空間隨著文檔個(gè)數(shù)線性增長(zhǎng)咸包,Roaring bitmaps需要打破這個(gè)魔咒就一定要用到某些指數(shù)特性:

將posting list按照65535為界限分塊桃序,比如第一塊所包含的文檔id范圍在0~65535之間,第二塊的id范圍是65536~131071烂瘫,以此類(lèi)推媒熊。再用<商,余數(shù)>的組合表示每一組id坟比,這樣每組里的id范圍都在0~65535內(nèi)了泛释,剩下的就好辦了,既然每組id不會(huì)變得無(wú)限大温算,那么我們就可以通過(guò)最有效的方式對(duì)這里的id存儲(chǔ)怜校。

細(xì)心的小明這時(shí)候又舉手了:”為什么是以65535為界限?”

程序員的世界里除了1024外,65535也是一個(gè)經(jīng)典值注竿,因?yàn)樗?2^16-1茄茁,正好是用2個(gè)字節(jié)能表示的最大數(shù),一個(gè)short的存儲(chǔ)單位巩割,注意到上圖里的最后一行“If a block has more than 4096 values, encode as a bit set, and otherwise as a simple array using 2 bytes per value”裙顽,如果是大塊,用節(jié)省點(diǎn)用bitset存宣谈,小塊就豪爽點(diǎn)愈犹,2個(gè)字節(jié)我也不計(jì)較了,用一個(gè)short[]存著方便闻丑。

那為什么用4096來(lái)區(qū)分采用數(shù)組還是bitmap的閥值呢漩怎?

這個(gè)是從內(nèi)存大小考慮的,當(dāng)block塊里元素超過(guò)4096后嗦嗡,用bitmap更恃福空間: 采用bitmap需要的空間是恒定的: 65536/8 = 8192bytes 而如果采用short[],所需的空間是: 2*N(N為數(shù)組元素個(gè)數(shù)) 小明手指一掐N=4096剛好是邊界:

聯(lián)合索引

上面說(shuō)了半天都是單field索引侥祭,如果多個(gè)field索引的聯(lián)合查詢叁执,倒排索引如何滿足快速查詢的要求呢茄厘?

利用跳表(Skip list)的數(shù)據(jù)結(jié)構(gòu)快速做“與”運(yùn)算,或者

利用上面提到的bitset按位“與”

先看看跳表的數(shù)據(jù)結(jié)構(gòu):

將一個(gè)有序鏈表level0谈宛,挑出其中幾個(gè)元素到level1及l(fā)evel2次哈,每個(gè)level越往上,選出來(lái)的指針元素越少吆录,查找時(shí)依次從高level往低查找亿乳,比如55,先找到level2的31径筏,再找到level1的47葛假,最后找到55,一共3次查找滋恬,查找效率和2叉樹(shù)的效率相當(dāng)聊训,但也是用了一定的空間冗余來(lái)?yè)Q取的。

假設(shè)有下面三個(gè)posting list需要聯(lián)合索引:

如果使用跳表恢氯,對(duì)最短的posting list中的每個(gè)id带斑,逐個(gè)在另外兩個(gè)posting list中查找看是否存在,最后得到交集的結(jié)果勋拟。

如果使用bitset勋磕,就很直觀了,直接按位與敢靡,得到的結(jié)果就是最后的交集挂滓。

總結(jié)和思考

Elasticsearch的索引思路:

將磁盤(pán)里的東西盡量搬進(jìn)內(nèi)存,減少磁盤(pán)隨機(jī)讀取次數(shù)(同時(shí)也利用磁盤(pán)順序讀特性)啸胧,結(jié)合各種奇技淫巧的壓縮算法赶站,用及其苛刻的態(tài)度使用內(nèi)存。

所以纺念,對(duì)于使用Elasticsearch進(jìn)行索引時(shí)需要注意:

不需要索引的字段贝椿,一定要明確定義出來(lái),因?yàn)槟J(rèn)是自動(dòng)建索引的

同樣的道理陷谱,對(duì)于String類(lèi)型的字段烙博,不需要analysis的也需要明確定義出來(lái),因?yàn)槟J(rèn)也是會(huì)analysis的

選擇有規(guī)律的ID很重要烟逊,隨機(jī)性太大的ID(比如java的UUID)不利于查詢

關(guān)于最后一點(diǎn)渣窜,個(gè)人認(rèn)為有多個(gè)因素:

其中一個(gè)(也許不是最重要的)因素: 上面看到的壓縮算法,都是對(duì)Posting list里的大量ID進(jìn)行壓縮的焙格,那如果ID是順序的图毕,或者是有公共前綴等具有一定規(guī)律性的ID,壓縮比會(huì)比較高眷唉;

另外一個(gè)因素: 可能是最影響查詢性能的予颤,應(yīng)該是最后通過(guò)Posting list里的ID到磁盤(pán)中查找Document信息的那步,因?yàn)镋lasticsearch是分Segment存儲(chǔ)的冬阳,根據(jù)ID這個(gè)大范圍的Term定位到Segment的效率直接影響了最后查詢的性能蛤虐,如果ID是有規(guī)律的,可以快速跳過(guò)不包含該ID的Segment肝陪,從而減少不必要的磁盤(pán)讀次數(shù)驳庭,具體可以參考這篇如何選擇一個(gè)高效的全局ID方案(評(píng)論也很精彩)

后續(xù)再結(jié)合實(shí)際開(kāi)發(fā)及調(diào)優(yōu)工作分享更多內(nèi)容,敬請(qǐng)期待氯窍!

Reference

Elasticsearch權(quán)威指南

時(shí)間序列數(shù)據(jù)庫(kù)的秘密(2)——索引

Elasticsearch默認(rèn)會(huì)為每個(gè)字段根據(jù)value的類(lèi)型分別建立索引饲常,如果不想為某些字段建立索引或者不做分詞分析的話,需要通過(guò)FieldMapping注明?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狼讨,一起剝皮案震驚了整個(gè)濱河市贝淤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌政供,老刑警劉巖播聪,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異布隔,居然都是意外死亡离陶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)衅檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)招刨,“玉大人,你說(shuō)我怎么就攤上這事哀军〖萍茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵排苍,是天一觀的道長(zhǎng)沦寂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)淘衙,這世上最難降的妖魔是什么传藏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮彤守,結(jié)果婚禮上毯侦,老公的妹妹穿的比我還像新娘。我一直安慰自己具垫,他們只是感情好侈离,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著筝蚕,像睡著了一般卦碾。 火紅的嫁衣襯著肌膚如雪铺坞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天洲胖,我揣著相機(jī)與錄音济榨,去河邊找鬼。 笑死绿映,一個(gè)胖子當(dāng)著我的面吹牛擒滑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叉弦,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丐一,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了淹冰?” 一聲冷哼從身側(cè)響起库车,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榄棵,沒(méi)想到半個(gè)月后凝颇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疹鳄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年拧略,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘪弓。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垫蛆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腺怯,到底是詐尸還是另有隱情袱饭,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布呛占,位于F島的核電站虑乖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晾虑。R本人自食惡果不足惜疹味,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帜篇。 院中可真熱鬧糙捺,春花似錦、人聲如沸笙隙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竟痰。三九已至签钩,卻和暖如春掏呼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背边臼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工哄尔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留假消,地道東北人柠并。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像富拗,于是被迫代替她去往敵國(guó)和親臼予。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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