Elasticsearch 5.4.0 用戶指南

博客原文一
博客原文二

翻譯作品,水平有限笔宿,如有錯誤,煩請留言指正棱诱。原文請見 官網英文文檔

Elasticsearch

起步

Elasticsearch 是一個大規(guī)模開源的全文搜索和分析引擎泼橘,你可以用它來快速(接近實時)存儲、搜索和分析大量的數(shù)據迈勋。通常被用作基礎的引擎炬灭,或者有復雜的搜索功能和要求的應用的技術支撐。

這兒是一些Elasticsearch簡單的使用場景:

  • 在線網站的數(shù)據存儲靡菇,允許你的客戶對你售賣的產品進行搜索的場景重归。這時米愿,你可以使用Elasticsearch存儲你的全部產品信息分類和庫存,然后提供搜索和自動填充建議提前。

  • 你想做日志和事務數(shù)據的收集吗货,然后對它們進行分析和挖掘,期待找到某種趨勢狈网,或者進行統(tǒng)計宙搬、歸納和異常分析。在這種情況下拓哺,你可以使用logstash( Elasticsearch/Logstash/Kibana 棧的一部分)收集勇垛、匯總和解析數(shù)據,然后logstash將這些數(shù)據傳送到Elasticsearch中士鸥,一旦數(shù)據進入Elasticsearch中闲孤,你就可以使用搜索和聚合來挖掘任何你想要的信息。

  • 價格預警平臺烤礁,允許精打細算的客戶指定特殊的規(guī)則讼积,比如我對某個特定的電子產品比較感興趣,在下個月任意一個供應商的價格下降到指定價格脚仔,然后提醒我勤众。在這種情況下,你可以把供貨商的價格推送到Elasticsearch中鲤脏,利用它的反向搜索(過濾)能力匹配用戶查詢的價格走勢们颜,最后將已經發(fā)現(xiàn)的匹配項發(fā)送給客戶。

  • 你有分析或者商業(yè)智能的需求猎醇,在大量數(shù)據(數(shù)百萬或者數(shù)十億的記錄)中想做個快速的調查窥突、分析、可視化和詢問特殊問題硫嘶。在這種情況下阻问,你可以使用Elasticsearch存儲數(shù)據,使用kibana構建自定義的儀表盤來展示你關心的數(shù)據沦疾。此外则拷,你還可以使用Elasticsearch的聚合功能來做復雜的商業(yè)智能分析與數(shù)據查詢。

接下來的教程曹鸠,我將會引導你完成Elasticsearch的獲取煌茬、安裝和運行,然后稍微深入一點彻桃,介紹一些基本操作坛善,比如建立索引、搜索和修改你的數(shù)據。在教程的最后你應該對Elasticsearch有一個很好的認識眠屎,它是什么剔交,它是怎么工作的。無論是構建復雜的搜索程序還是從你的數(shù)據中挖掘情報改衩,在如何使用Elasticsearch方面岖常,希望你能受到啟發(fā)。

1 基本概念

這里有一些Elasticsearch的主要核心概念葫督,在開始的時候就理解了這些概念竭鞍,對于你的學習過程有非常大的幫助。

準實時

Elasticsearch是一個準實時的搜索平臺橄镜。意思是從你創(chuàng)建一個文檔的索引到這個索引可以被搜索只有很小延遲(通常是1秒)偎快。

集群

一個集群是一個或者多個節(jié)點(服務器)的集合,它們共同擁有所有的數(shù)據洽胶,并且提供跨節(jié)點的索引和搜索能力晒夹。一個集群由一個唯一的名字確定,默認情況下是“elasticsearch”姊氓,這個名字是很重要的丐怯,因為一個節(jié)點僅可能是一個集群的一部分,節(jié)點加入到哪個集群是根據這個名字來判斷的翔横。

一定不要在不同的環(huán)境使用相同的集群名稱读跷,否則你可能將節(jié)點加入到錯誤的集群中,例如你可以分別在開發(fā)環(huán)境棕孙、預備環(huán)境和生產環(huán)境使用logging-dev, logging-stagelogging-prod

注意些膨,在一個集群中只有一個節(jié)點也是有效的和非常好的蟀俊。此外,你可以有多個獨立的集群订雾,如果它們的有各自唯一的集群名字肢预。

節(jié)點

一個節(jié)點是一臺服務器,也是一個集群的一部分洼哎,存儲你的數(shù)據烫映,參與提供集群的索引和搜索能力。就像集群一樣噩峦,節(jié)點也是由一個名字唯一確定的锭沟,這個名字默認是一個隨機的UUID,它是在節(jié)點啟動的時候分配的识补。如果你不想要默認的名字族淮,你可以隨意定義你的節(jié)點名稱。這個名字在集群的管理上顯得很重要,他可以讓你確定地知道在elasticsearch集群中的節(jié)點對應網絡中的哪一臺服務器祝辣。

一個節(jié)點可以通過配置集群名稱加入到任意的集群中贴妻,默認情況下,每個節(jié)點都被設置加入到一個名叫elasticsearch的集群中蝙斜,這意味著名惩,如果你在一個網絡中(服務器之間能夠彼此互相發(fā)現(xiàn))啟動大量這樣的節(jié)點,它們將自動的形成一個集群孕荠,名叫elasticsearch娩鹉。

在一個集群中你可以有任意多的節(jié)點。此外岛琼,如果在當前節(jié)點運行的網絡中沒有其他的 Elasticsearch 的節(jié)點底循,只啟動一個節(jié)點,它將會形成一個新的單節(jié)點集群槐瑞,名叫elasticsearch熙涤。

索引

索引是具有相似特性的文檔的集合,例如困檩,你可以為客戶數(shù)據建立一個索引祠挫,你也可以為產品類數(shù)據建立索引,還有訂單數(shù)據也可以建立索引悼沿。一個索引都有一個特定的名字(必需為小寫)標識等舔,在對索引的文檔執(zhí)行索引、搜索糟趾、更新和刪除操作時慌植,該名稱用于指定索引。

在一個集群中义郑,你可以定義任意多的索引蝶柿。

類型

在一個索引中,你可以定義一個或者多個類型非驮,類型是索引的邏輯類/分區(qū)交汤,它的語義完全由你決定。一般來說劫笙,將一組具有公共字段的文檔定義為一種類型芙扎。例如,假設你在運行一個博客平臺填大,你的所有數(shù)據都存儲在一個索引中戒洼,在這個索引中你可能為用戶數(shù)據定義一個類型,博客數(shù)據定義另一個類型允华,注釋數(shù)據為另一個類型施逾。

文檔

文檔是可以被索引的信息的基本單位敷矫。例如,你可以為一個單獨的客戶數(shù)據建立一個文檔汉额,一個單獨的產品也可以建立一個文檔曹仗,還有一個訂單文檔。這些文檔是用JSON格式的數(shù)據來表示的蠕搜,這是一種在互聯(lián)網上無處不在的數(shù)據交互格式怎茫。

在一個索引/類型中,你可以存儲盡可能多的文檔妓灌。注意轨蛤,盡管一個文檔實際是存儲在索引中的,但是實際上你必須將文檔分配到一個索引中的一個類型虫埂。

分片&備份

一個索引可以存儲大量的數(shù)據祥山,這些數(shù)據可能超過單個節(jié)點硬盤限制,例如掉伏,一個存儲了十億文檔的索引占了1TB的磁盤空間缝呕,可能不太適合存儲在一個節(jié)點的硬盤上,或者說單個節(jié)點服務于搜索請求太慢了斧散。

為了解決這個問題供常,Elasticsearch提供了將索引細分為多塊的能力,這些塊被稱為“分片”鸡捐。在你創(chuàng)建索引的時候栈暇,可以簡單地定義所需數(shù)目的分片,每一個分片本身是一個功能全面的箍镜、獨立的“索引”源祈,它可以被托管在集群中的任意節(jié)點。

索引分片有兩個原因是非常重要的:

  • 你可以水平拆分/縮放存儲數(shù)據卷色迂。
  • 你可以分布式或者并行地執(zhí)行跨分片(可能存儲在多個節(jié)點上)的操作香缺,因此可以提高性能和吞吐量。

分片的分布機制和分片的文檔是如何聚集在一起實現(xiàn)一次用戶的搜索請求脚草,這些完全都是由Elasticsearch管理的赫悄,對用戶來說是透明的原献。

在網絡/云環(huán)境中隨時都有可能失敗馏慨,在一個分片/節(jié)點不知原因地掉線或者消失的情況下,故障轉移機制還是非常有用的姑隅,強烈推薦写隶。為此,Elasticsearch允許你為索引的分片創(chuàng)建一個或多個副本讲仰,這就是所謂的副本分片慕趴,或者簡稱為備份。

備份有兩個原因是非常重要的:

  • 在分片或者節(jié)點故障的情況下,它提供了高可用性冕房。正因為這一點躏啰,注意一個副本分片從來不會被分配和主分片(copy的來源節(jié)點)在同一個節(jié)點上,這一點是很重要的耙册。
  • 它能提高你的搜索數(shù)據量和吞吐量给僵,因為搜索可以并行地在多個副本上執(zhí)行。

總結一下详拙,一個索引能分成多個分片帝际,一個索引能被復制零(無備份)或多次。一旦復制了之后饶辙,每個索引都有主分片(拷貝的來源分片)和副分片(主分片的拷貝)蹲诀。一個索引的分片和副本的數(shù)量都可以在索引創(chuàng)建的時候定義,在索引創(chuàng)建之后弃揽,你可以隨時動態(tài)的改變副本的數(shù)量脯爪,但是你不能事后修改分片的數(shù)量。

默認情況下蹋宦,在Elasticsearch中的每個索引被分為5個主分片和1個副本披粟,這就意味著在你的集群中至少有兩個節(jié)點,你的索引將有5個主分片和另外5個副分片(一個完整的副本)冷冗,每個索引總共10個分片守屉。

注意,每一個Elasticsearch分片都是一個Lucene索引蒿辙,在一個單獨的Lucene索引中擁有的文檔數(shù)量是有最大值的拇泛,根據LUCENE-5843中介紹,這個限制是2,147,483,519 (= Integer.MAX_VALUE - 128)個文檔思灌。你可以使用api _cat/shards 來監(jiān)控分片的大小俺叭。

既然如此,我們就開始有趣的下一part吧泰偿。

2 安裝

Elasticsearch要求java 8 以上熄守,在寫該文檔時屑咳,建議你使用oracle的JDK版本1.8.0_131膀钠,關于java在各個平臺的安裝過程這里就不詳細介紹了,在oracle的官網上能夠找到oracle推薦的安裝文檔在刺,我只想說调塌,在你安裝Elasticsearch之前晋南,請檢查你的當前運行的java版本(根據需要安裝或升級java):

java -version
echo $JAVA_HOME

如果你已經安裝了java,我們就可以開始下載和運行Elasticsearch了羔砾。在 www.elastic.co/downloads
這個網址上可以下載到可用的二進制包负间,還有過去發(fā)行的其它版本的包偶妖。對于每一個發(fā)行版本,你都可以選擇zip或者tar壓縮包政溃,還有DEBRPM包趾访。為了簡單起見,我們以tar包為例董虱。

讓我首先使用下面的命令下載Elasticsearch5.4.1版本的tar文件(windows用戶應該下載zip包):

curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz

然后腹缩,使用下面的命令解壓它(windows用戶應該使用unzip來解壓zip包):

tar -xvf elasticsearch-5.4.1.tar.gz

解壓完成之后,將會在當前目錄產生大量的文件和文件夾空扎,然后我們進入到bin目錄下藏鹊,命令如下:

cd elasticsearch-5.4.1/bin

現(xiàn)在我們可以準備啟動這個節(jié)點和單節(jié)點集群了(windows用戶應該運行.bat文件):

./elasticsearch

如果一切順利的話,你將看到很多信息輸出转锈,像下面這樣:

[2016-09-16T14:17:51,251][INFO ][o.e.n.Node               ] [] initializing ...
[2016-09-16T14:17:51,329][INFO ][o.e.e.NodeEnvironment    ] [6-bjhwl] using [1] data paths, mounts [[/ (/dev/sda1)]], net usable_space [317.7gb], net total_space [453.6gb], spins? [no], types [ext4]
[2016-09-16T14:17:51,330][INFO ][o.e.e.NodeEnvironment    ] [6-bjhwl] heap size [1.9gb], compressed ordinary object pointers [true]
[2016-09-16T14:17:51,333][INFO ][o.e.n.Node               ] [6-bjhwl] node name [6-bjhwl] derived from node ID; set [node.name] to override
[2016-09-16T14:17:51,334][INFO ][o.e.n.Node               ] [6-bjhwl] version[5.4.1], pid[21261], build[f5daa16/2016-09-16T09:12:24.346Z], OS[Linux/4.4.0-36-generic/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_60/25.60-b23]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [aggs-matrix-stats]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [ingest-common]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [lang-expression]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [lang-groovy]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [lang-mustache]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [lang-painless]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [percolator]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [reindex]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [transport-netty3]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded module [transport-netty4]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService     ] [6-bjhwl] loaded plugin [mapper-murmur3]
[2016-09-16T14:17:53,521][INFO ][o.e.n.Node               ] [6-bjhwl] initialized
[2016-09-16T14:17:53,521][INFO ][o.e.n.Node               ] [6-bjhwl] starting ...
[2016-09-16T14:17:53,671][INFO ][o.e.t.TransportService   ] [6-bjhwl] publish_address {192.168.8.112:9300}, bound_addresses {{192.168.8.112:9300}
[2016-09-16T14:17:53,676][WARN ][o.e.b.BootstrapCheck     ] [6-bjhwl] max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
[2016-09-16T14:17:56,731][INFO ][o.e.h.HttpServer         ] [6-bjhwl] publish_address {192.168.8.112:9200}, bound_addresses {[::1]:9200}, {192.168.8.112:9200}
[2016-09-16T14:17:56,732][INFO ][o.e.g.GatewayService     ] [6-bjhwl] recovered [0] indices into cluster_state
[2016-09-16T14:17:56,748][INFO ][o.e.n.Node               ] [6-bjhwl] started

不過分深入細節(jié)的話盘寡,我們可以看到名為6-bjhwl(這是一組不同的字符,視你的情況而定)的節(jié)點已經啟動了撮慨,并且它自己被選舉為單節(jié)點的集群中的master節(jié)點竿痰。此刻你不用擔心master意味著什么,這里重要的事情是砌溺,我們已經在一個集群中啟動了一個節(jié)點影涉。

正如前面所說的,我們可以覆蓋集群和節(jié)點的名字规伐,這個可以在啟動Elasticsearch的命令中做到蟹倾,命令如下:

./elasticsearch -Ecluster.name=my_cluster_name -Enode.name=my_node_name

同時還要注意信息中的http地址(192.168.8.112)和端口(9200),這是節(jié)點的訪問入口猖闪。默認情況下鲜棠,Elasticsearch使用端口9200提供REST API的訪問,這個端口在必要的時候也是可以配置的培慌。

3 探索集群

REST API

現(xiàn)在我們已經啟動了我們的節(jié)點(和集群)豁陆,它們正在運行中,下一步我們需要理解怎樣和它交流吵护。幸運的是盒音,Elasticsearch提供了非常全面、強大的REST API馅而,你可以使用這些API接口與你的集群交互祥诽。下面這些事情都可以通過API來完成:

  • 檢查你的集群、節(jié)點和索引的健康情況用爪、狀態(tài)和一些統(tǒng)計
  • 管理你的集群原押、節(jié)點和索引數(shù)據胁镐、元數(shù)據
  • 執(zhí)行CRUD(創(chuàng)建偎血、讀取诸衔、更新和刪除)操作和針對索引的搜索操作
  • 執(zhí)行高級的搜索操作,例如分頁颇玷、排序笨农、過濾、腳本帖渠、聚合谒亦,還有很多其他的操作

集群健康

讓我們以基本的健康檢查開始,健康檢查可以讓我們了解集群的運行情況空郊。我們使用curl命令來調用這些API份招,你也可以使用任意其它的HTTP/REST調用工具。假設我們已經登錄了運行著Elasticsearch的同一個節(jié)點狞甚,并且打開了一個shell命令行窗口锁摔。

為了檢查集群的健康度,我們將使用_cat API.哼审。你可以點擊"VIEW IN CONSOLE"在Kibana’s Console上執(zhí)行下面的命令谐腰,或者點擊 "COPY AS CURL" 之后粘貼到你的終端上使用curl命令。

GET /_cat/health?v

然后涩盾,下面就是響應:

epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1475247709 17:01:49  elasticsearch green           1         1      0   0    0    0        0             0                  -                100.0%

我們可以從中看到十气,集群的名字叫“elasticsearch”,是綠色的狀態(tài)春霍。

無論什么時候我們檢查集群的健康狀況砸西,都是綠色、黃色址儒、紅色籍胯。綠色意味著一切都是好的(集群的所有功能都能正常使用);黃色意味著所有數(shù)據都是可用的离福,但是缺少一些分片(集群的所有功能都能正常使用)杖狼;紅色意味著不管什么原因一些數(shù)據是不可用的;注意妖爷,即使集群的狀態(tài)是紅色蝶涩,還是有部分功能是可用的(例如,它將繼續(xù)可以用來從可用的分片上搜索數(shù)據)絮识,但是你可能需要盡快修復它绿聘,因為你已經丟失了數(shù)據。

從上面的響應中我們還可以看出次舌,總共就只有一個節(jié)點熄攘,零個分片是因為里面沒有數(shù)據。注意我們使用的是默認的集群名字(Elasticsearch)彼念,Elasticsearch默認的使用單播網絡來發(fā)現(xiàn)同一臺機器上的其它節(jié)點挪圾,你也有可能意外地在一臺機器上啟動了多個節(jié)點浅萧,然后它們組成了一個集群。在這種情形下哲思,你可能在上面的響應中看到多個節(jié)點洼畅。

我們也可以查看集群中的所有節(jié)點,像下面這樣:

GET /_cat/nodes?v

然后棚赔,響應如下:

ip        heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
127.0.0.1           10           5   5    4.46                        mdi      *      PB2SGZY

這里我們可以看出帝簇,只有一個名叫“PB2SGZY”的節(jié)點,它是當前集群中的唯一的一個節(jié)點靠益。

列出所有索引

現(xiàn)在讓我們看一下我們的索引:

GET /_cat/indices?v

然后丧肴,響應如下:

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size

這僅僅意味著在我們的集群中沒有索引。

創(chuàng)建索引

現(xiàn)在讓我們創(chuàng)建一個名叫“customer”索引胧后,然后再次列出所有的索引:

PUT /customer?pretty
GET /_cat/indices?v

第一條命令使用動詞PUT創(chuàng)建了一個名叫“customer”的索引闪湾,在命令的結尾僅僅跟了一個pretty,意思是任何響應都以JSON的形式打印出來绩卤。

然后途样,響應如下:

health status index    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   customer 95SQ4TSUT7mWBT7VNHH67A   5   1          0            0       260b           260b

第二條命令的結果告訴我們,現(xiàn)在我們由一個名叫“customer”的索引濒憋,它有5個主分片和1個副本(默認)何暇,其中沒有任何文檔。

你可能還注意到customer索引有一個黃色的健康標記凛驮,回顧我們之前的討論裆站,黃色意味著一些分片沒有被分配。出現(xiàn)這種情況的原因是黔夭,Elasticsearch默認情況下會為這個索引創(chuàng)建一個副本宏胯,但是此刻集群中僅有一個節(jié)點,只有另一個節(jié)點加入到集群中時本姥,這個副本才可能被分配(高可用)肩袍。一旦副本被分配到第二個節(jié)點中,這個索引的健康狀態(tài)立馬就會變成綠色婚惫。

索引和查詢文檔

現(xiàn)在讓我們先在customer索引中放一些東西氛赐,還記得之前我們?yōu)槲臋n創(chuàng)建索引,必須告訴Elasticsearch文檔應該被放在索引中的什么類型下先舷。

讓我們將一個簡單的客戶文檔放進customer索引中艰管,external類型中,ID為1蒋川,就像下面這樣:

PUT /customer/external/1?pretty
{
  "name": "John Doe"
}

然后牲芋,響應如下:

{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}

從上面的響應中我們可以看出,一個新的客戶文檔被成功地創(chuàng)建在customer索引的external類型中,這個文檔還有一個內部id為1缸浦,在索引文檔時候它需要被指定夕冲。

特別需要注意的是,Elasticsearch沒有明確要求你在索引一個文檔之前首先創(chuàng)建一個索引餐济,就前面的例子而言,如果事先customer索引不存在胆剧,Elasticsearch會自動的創(chuàng)建它絮姆。

現(xiàn)在讓我們去檢索一下剛剛編入索引的那個文檔:

GET /customer/external/1?pretty

然后,響應如下:

{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : { "name": "John Doe" }
}

除了其中的found字段沒有什么特別的地方秩霍,說明我們找到了請求的ID為1的那個文檔篙悯,另一個字段是_source,它返回了我們上一步中編入索引的完全的JSON格式的文檔铃绒。

刪除索引

現(xiàn)在讓我們刪除剛剛創(chuàng)建的索引鸽照,然后再次列出所有的索引:

DELETE /customer?pretty
GET /_cat/indices?v

然后,響應如下:

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size

這就是說索引已經被成功刪除颠悬,現(xiàn)在我們又回到了開始時集群中什么都沒有的狀態(tài)矮燎。

在繼續(xù)學習之前,讓我們再仔細看看我們學到的一些API命令:

PUT /customer
PUT /customer/external/1
{
  "name": "John Doe"
}
GET /customer/external/1
DELETE /customer

如果我們仔細研究上面的命令赔癌,實際上我們可以看到一種如何從Elasticsearch中訪問數(shù)據的模式诞外,這種模式可以概括如下:

<REST Verb> /<Index>/<Type>/<ID>

這種REST訪問模式在所有的API命令中是普遍存在的,如果你能簡單地記住它灾票,在掌握Elasticsearch上峡谊,你有了一個良好的開端。

4 修改數(shù)據

Elasticsearch提供準實時的數(shù)據操作和搜索功能刊苍。默認情況下既们,從你創(chuàng)建索引/更新/刪除你的數(shù)據到展現(xiàn)出搜索結果應該是只有1秒的延遲(刷新間隔),這是與像SQL等其它平臺的重要區(qū)別正什,它們中的事務操作完成之后數(shù)據立即可用啥纸。

索引和替換文檔

我們前面已經知道了如何將一個單獨的文檔編入索引,讓我們回顧一下命令:

PUT /customer/external/1?pretty
{
  "name": "John Doe"
}

同樣婴氮,上面的命令將一個指定的文檔編入到customer索引中脾拆,類型為external,ID為1. 如果我們使用不同(或者相同)的文檔再次執(zhí)行上面的命令莹妒,Elasticsearch將在現(xiàn)存的ID為1的上面替換(或者重建)一個新文檔:

PUT /customer/external/1?pretty
{
  "name": "Jane Doe"
}

上面的命令改變了ID為1的文檔的名字名船,由 "John Doe" 改為 "Jane Doe"。另一方面旨怠,如果我們使用不同的ID渠驼,一個新的文檔將會被編入索引,而索引中已經存在的文檔保持不變鉴腻。

PUT /customer/external/2?pretty
{
  "name": "Jane Doe"
}

上面的命令將一個ID為2的新文檔編入了索引迷扇。

當我們索引文檔的時候百揭,ID部分是可選的,如果你不指定的話蜓席,Elasticsearch將隨機生成一個ID器一,然后使用它去索引文檔。Elasticsearch實際生成的ID(或者是前面例子中我們明確指定的ID)將作為索引API調用的一部分厨内。

下面的例子展示了在不明確指定ID的情況下如何索引一個文檔:

POST /customer/external?pretty
{
  "name": "Jane Doe"
}

注意祈秕,上面的例子中我們使用的是動詞POST,而不是PUT雏胃,因為我們沒有指定ID请毛。

更新文檔

另外,除了可以索引和替換文檔之外瞭亮,我們還可以更新文檔方仿。需要注意的是,Elasticsearch背后并沒有真正的做更新操作统翩,無論我們什么時候做更新操作仙蚜,Elasticsearch都是刪除舊的文檔,然后將更新后的文檔一次性地作為一個新文檔編入索引厂汗。

下面的例子展示了鳍征,如何更新前面的文檔(ID為1),將它的name字段改為"Jane Doe":

POST /customer/external/1/_update?pretty
{
  "doc": { "name": "Jane Doe" }
}

下面的例子展示了面徽,如何更新前面的文檔(ID為1)艳丛,將它的name字段改為"Jane Doe",同時增加一個新的字段age:

POST /customer/external/1/_update?pretty
{
  "doc": { "name": "Jane Doe", "age": 20 }
}

更新操作也可以使用簡單的腳本趟紊,下面的例子使用腳本將age字段的值增加5:

POST /customer/external/1/_update?pretty
{
  "script" : "ctx._source.age += 5"
}

在上面的例子中氮双,ctx._source 是指即將更新的當前文檔。

注意霎匈,在寫這篇文檔的時候戴差,更新操作還只能一次更新一個文檔。未來铛嘱,elasticsearch可能提供根據查詢條件更新多個文檔的能力(就像SQL UPDATE-WHERE語句一樣)暖释。

刪除文檔

刪除文檔相對來說是比較簡單的,下面的例子展示了如何刪除ID為2的客戶文檔:

DELETE /customer/external/2?pretty

參見 Delete By Query API 刪除指定查詢條件的所有文檔墨吓。刪除整個索引比根據查詢API刪除該索引下的所有文檔更高效球匕。

批處理

我們除了能索引、更新和刪除單個文檔帖烘,Elasticsearch還提供了使用 _bulk
API
批量執(zhí)行上面的任何操作亮曹。這個功能是很重要的,它提供了一種盡可能快并且盡量少的網絡切換條件下執(zhí)行多個操作的高效機制。

做一個快速示范照卦,下面是使用一個bulk操作將兩個文檔(ID 1 - John Doe and ID 2 - Jane Doe)編入索引的例子:

POST /customer/external/_bulk?pretty
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }

下面的例子使用一個bulk操作更新第一個文檔(ID為1)式矫,然后刪除第二個文檔(ID為2):

POST /customer/external/_bulk?pretty
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}

注意上面的刪除操作,在它的后面沒有指定對應的源文檔役耕,因為刪除操作僅需要被刪除文檔的ID采转。

多個操作中的一個失敗不會導致整個bulk操作的失敗,如果其中一個操作不管什么原因失敗了瞬痘,將會繼續(xù)執(zhí)行它后面剩下的操作故慈。在bulk操作的返回信息中將提供每個操作的執(zhí)行狀態(tài),因此你可以檢查某個指定的操作是否失敗了图云。

5 探索數(shù)據

樣本數(shù)據集

現(xiàn)在我們已經了解了一些基本知識惯悠,讓我們研究一下更真實的數(shù)據集邻邮。我已經準備好了一個虛構的JSON文檔示例竣况,它是一個客戶的銀行賬戶信息。每個文檔都有下面的模式:

{
    "account_number": 0,
    "balance": 16623,
    "firstname": "Bradshaw",
    "lastname": "Mckenzie",
    "age": 29,
    "gender": "F",
    "address": "244 Columbus Place",
    "employer": "Euron",
    "email": "bradshawmckenzie@euron.com",
    "city": "Hobucken",
    "state": "CO"
}

處于好奇筒严,這個數(shù)據我是從這個 www.json-generator.com/
網站上生成的丹泉,因此請忽略其中的數(shù)值和語義,這些都是隨機生成的鸭蛙。

加載樣本數(shù)據集

你可以從這里(accounts.json)下載樣本數(shù)據集摹恨,將其解壓到當前目錄,然后使用下面的命令加載到集群中:

curl -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"
curl 'localhost:9200/_cat/indices?v'

然后娶视,響應如下:

health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   bank  l7sSYV2cQXmu6_4rJWVIww   5   1       1000            0    128.6kb        128.6kb

這就意味著剛才成功地將1000個文檔編入bank索引(account 類型下面)晒哄。

搜索API

現(xiàn)在讓我們開始使用一些簡單的搜索,有兩個基本的方法執(zhí)行搜索:一個是通過 REST request URI 發(fā)送搜索參數(shù)肪获,另一個是通過 REST request body 發(fā)送搜索參數(shù)寝凌;Request body方式更有表現(xiàn)力,允許你使用可讀性比較好JSON格式定義你的搜索孝赫。我們嘗試一個Request URI的方式较木,但是教程的其他部分我們將只使用Request Body方式。

搜索的REST API可以通_search端點來訪問青柄,下面是獲取bank索引下的所有文檔的例子:

GET /bank/_search?q=*&sort=account_number:asc&pretty

讓我們首先剖析一下這個搜索調用伐债,我們正在搜索bank索引(_search端點),參數(shù)q=*命令Elasticsearch匹配這個索引下的所有文檔致开,參數(shù)sort=account_number:asc表明使用每個文檔的account_number字段進行升序排序峰锁,另外參數(shù)pretty告訴Elasticsearch返回美化后的JSON結果。
然后双戳,響應如下(展示部分):

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

對于響應祖今,我們看一下下面幾個部分:

  • took Elasticsearch執(zhí)行搜索的耗時,以毫秒為單位;
  • timed_out 告訴我們搜索是否超時千诬;
  • _shards 告訴我們多少個分片被搜索到耍目,并且顯示搜索成功和失敗的分片數(shù);
  • hits 搜索結果徐绑;
  • hits.total 匹配搜索標準的文檔總數(shù)邪驮;
  • hits.hits 搜索結果的實際陣列(默認前10個文檔);
  • hits.sort 搜索結果中的排序key(如果按照分數(shù)排序傲茄,將會缺失)毅访;
  • hits._scoremax_score 現(xiàn)在忽略這寫字段;

下面是一個代替上面全部搜索方法的Request Body方法:

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

與上面的URI中的q=*相比的不同點是盘榨,我們提交了一個JSON格式的查詢請求到_search API喻粹。在下一個部分我們將討論JSON的查詢。

要明白一件重要的事草巡,Elasticsearch一旦返回搜索結果守呜,搜索請求就完全結束了,它不會保持任何服務端的資源山憨,也不會維持搜索結果中的游標查乒。這和其他平臺是完全不同的,例如SQL平臺郁竟,開始時可以獲取查詢結果前面的部分子集玛迄,如果你想獲取(或者通過分頁查詢)結果的其余部分棚亩,可以通過某種服務端游標繼續(xù)請求服務器蓖议。

介紹查詢語言

Elasticsearch提供了JSON格式的領域專用的語言,你可以使用它來執(zhí)行查詢讥蟆,具體參考 Query DSL勒虾。該查詢語言是很全面的,乍看一下可能很嚇人攻询,實際上學習它的最好的方法是從一些基本的例子開始从撼。

回到我們的最后一個例子,執(zhí)行這個查詢:

GET /bank/_search
{
  "query": { "match_all": {} }
}

剖析一下上面的調用請求钧栖,query部分告訴我們查詢的定義是什么低零,簡單的說,match_all是我們想要執(zhí)行的查詢類型拯杠,match_all查詢是搜索指定索引的全部文檔掏婶。

除了query參數(shù)以外,我們還可以通過其它參數(shù)來干預搜索結果潭陪,在上一部分的例子中我們使用過sort雄妥,這里我們使用一下size

GET /bank/_search
{
  "query": { "match_all": {} },
  "size": 1
}

注意最蕾,如果size不指定的話,默認是10.

下面的例子是一個 match_all 的搜索老厌,然后返回從11到20的文檔:

GET /bank/_search
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}

from參數(shù)(0為基礎)指定從索引的哪個文檔開始瘟则,size參數(shù)指定返回從from參數(shù)開始的多少個文檔。這個特性在實現(xiàn)搜索結果的分頁查詢時是很有用的枝秤。注意醋拧,如果from不指定,默認是0.

下面的例子是一個match_all的搜索淀弹,然后按照賬戶余額進行降序排列丹壕,然后返回前10(默認)個文檔。

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": { "balance": { "order": "desc" } }
}

搜索操作

現(xiàn)在我們已經看到了一些基本的搜索參數(shù)薇溃,讓我們更深入地挖掘一下Query DSL. 讓我們首先看一下返回的文檔的字段菌赖,默認情況下,作為所有搜索的一部分返回完整的JSON文檔沐序,這個被稱作資源(在搜索結果中的hits中的_source字段)琉用。如果我們不想要返回的整個資源文檔,我們可以僅請求返回的資源中的部分字段薄啥。

下面的例子展示了如何搜索返回兩個字段 account_numberbalance_source內部):

GET /bank/_search
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"]
}

注意辕羽,上面的例子僅僅是減少了_source的字段逛尚,它仍然僅返回一個名叫_source的字段垄惧,但是在它只包含account_numberbalance 兩個字段。

如果你有SQL背景绰寞,這個在概念上有點類似于SQL SELECT FROM的字段列表到逊。

現(xiàn)在讓我們把注意力轉到查詢部分,前面我們已經看到match_all查詢是如何匹配所有文檔的滤钱,現(xiàn)在讓我們介紹一個新的查詢叫 match query觉壶,可以認為它是基本字段的搜索查詢(例如,指定一個字段或者多個字段來完成一次搜索)件缸。

下面的例子是返回賬戶編號為20的文檔:

GET /bank/_search
{
  "query": { "match": { "account_number": 20 } }
}

下面的例子是返回地址包含“mill”的所有賬戶:

GET /bank/_search
{
  "query": { "match": { "address": "mill" } }
}

下面的例子是返回地址包含“mill”或者“l(fā)ane”的所有賬戶:

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

下面的例子是match的一個變形(match_phrase)铜靶,它返回地址中包含短語“mill lane”的所有賬戶:

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

現(xiàn)在讓我介紹bool(ean) querybool查詢允許我們使用bool邏輯將小的查詢組合成大查詢他炊。

下面的例子是組合兩個match查詢争剿,然后返回在地址中包含“mill” 和 “l(fā)ane”的所有賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的例子中,bool must分句指定所有查詢條件必須是“true”才算匹配的文檔痊末。

與此相反蚕苇,下面的例子組合兩個match查詢,返回在地址中包含“mill” 或 “l(fā)ane”的所有賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的例子中凿叠,bool should分句指定一個查詢列表涩笤,其中必須有一個是“true”的文檔才算被匹配嚼吞。

下面的例子組合兩個match查詢,返回在地址中既不包含“mill” 也不包含 “l(fā)ane”的所有賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的例子中蹬碧,must_not分句指定一個查詢列表舱禽,其中必須都不是“true”的文檔才算被匹配。

我們可以同時在一個bool查詢中組合must, shouldmust_not分句恩沽。此外呢蔫,我們可以在任何bool分句中組合bool查詢,以便模擬任何復雜的多級bool邏輯飒筑。

下面的例子返回年齡是40歲但不住在ID(aho)的所有人的賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

過濾操作

在前面的部分我們跳過了一個小細節(jié)所謂的文檔得分(也就是搜索結果中的_score字段)片吊。得分是數(shù)值型的,它是文檔和我們指定的搜索查詢匹配程度的相對度量协屡。得分越高俏脊,文檔越相關跌穗;得分越低不跟,文檔越不相關。

但是查詢并不總是需要得分艇炎,尤其是它們僅被用作文檔集合的過濾器時补憾。Elasticsearch探測到這種情況會自動地優(yōu)化查詢的執(zhí)行漫萄,防止計算無用的得分。

在前面我們介紹的 bool query 也是支持filter分句的盈匾,它允許使用查詢來限制其它分句匹配的文檔腾务,而不改變得分的計算。作為一個例子削饵,我們來介紹一下range query 岩瘦,它允許我們使用一個值域來過濾文檔,這通常被用于數(shù)值型和日期型的過濾器窿撬。

下面的例子使用bool 查詢獲取余額在20000和30000之間的所有賬戶启昧,換句話說,我們是想找到余額大于等于20000并且小于等于30000的賬戶劈伴。

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

仔細分析一下上面的例子密末,bool查詢包括一個match all查詢(查詢的一部分)和一個range 查詢(過濾的一部分)。我們可以將其它任何查詢替換成查詢和過濾組成跛璧。在上面的例子中严里,range查詢是很有意義的,因為所有在范圍內的文檔都是相等的赡模,沒有哪個文檔比另外的文檔更相關田炭。

除了 match_all, match, bool, and range 查詢之外,還有很多其它類型的查詢漓柑,我們不會在這里討論它們教硫。因為我們對它們的工作原理有了基本的理解叨吮,將這些知識應用于其它類型的查詢,學習和使用它們都不會太難瞬矩。

聚合操作

聚合提供了對數(shù)據進行分組茶鉴、提取統(tǒng)計結果的能力。想明白聚合的最簡單的方法是將其大致等同于SQL分組和SQL的聚合函數(shù)景用。在Elasticsearch中涵叮,可以執(zhí)行搜索獲取hits,同時也能在同一個響應中返回區(qū)別于hits的聚合結果伞插。從這一點來說割粮,它是很強大和高效的,你可以執(zhí)行查詢和多聚合操作媚污,并且這些操作結果一起返回舀瓢,使用這樣簡單的API避免了網絡的切換。

首先耗美,下面的例子是將所有賬戶按照州來分組京髓,然后返回按照數(shù)量的降序排列的前10個州(默認):

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}

在SQL中,上面的聚合在概念上類似于:

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC

然后商架,響應如下(部分展示):

{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, {
        "key" : "MD",
        "doc_count" : 25
      }, {
        "key" : "TN",
        "doc_count" : 23
      }, {
        "key" : "MA",
        "doc_count" : 21
      }, {
        "key" : "NC",
        "doc_count" : 21
      }, {
        "key" : "ND",
        "doc_count" : 21
      }, {
        "key" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}

我們可以看出在ID(Idaho)州由27個賬戶堰怨,在TX(Texas)州有個27個賬戶,在AL(Alabama)州有25個賬戶蛇摸,等等备图。

注意上面我們設置size=0是為了不展示搜索結果,因為我們僅想在響應中看到聚合結果皇型。

在前面的聚合操作的基礎上诬烹,下面的例子計算了每個州的賬戶余額平均值(同樣僅展示賬戶總數(shù)降序排列的前10個州):

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

請注意我們是如何將average_balance聚合嵌套在group_by_state聚合中的砸烦,這是所有聚合中常見的模式弃鸦,為了從數(shù)據中提取總結你需要的信息,可以嵌套聚合到任意其它的聚合之中幢痘。

在前面的聚合的基礎上唬格,現(xiàn)在讓我們按照余額的平均值進行降序排列:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

下面的例子演示了如何通過年齡組( 20-29, 30-39, 和 40-49)進行分組,然后按照性別分組颜说,最后獲得在每個年齡組每個性別中賬戶余額的平均值:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 20,
            "to": 30
          },
          {
            "from": 30,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

還有很多其它的聚合功能购岗,這里我們就不詳細一一介紹了,如果你想進一步學習聚合的操作门粪, aggregations reference guide 是一個不錯的參考文檔喊积。

6 結論

Elasticsearch是一個既簡單又復雜的產品。到目前為止玄妈,我們已經了解了它的基本原理乾吻,如何查看它的內部髓梅,如何使用一些REST API操作它。我希望這個教程讓你更好地理解了Elasticsearch是什么绎签,更重要是枯饿,激發(fā)你去進一步探索Elasticsearch的其余特性。

Elasticsearch的起步教程終于翻譯完了诡必,這個翻譯文檔只是入門級的介紹Elasticsearch奢方,希望能對你的學習有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末爸舒,一起剝皮案震驚了整個濱河市蟋字,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扭勉,老刑警劉巖愉老,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剖效,居然都是意外死亡嫉入,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門璧尸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咒林,“玉大人,你說我怎么就攤上這事爷光〉婢海” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵蛀序,是天一觀的道長欢瞪。 經常有香客問我,道長徐裸,這世上最難降的妖魔是什么遣鼓? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮重贺,結果婚禮上骑祟,老公的妹妹穿的比我還像新娘。我一直安慰自己气笙,他們只是感情好次企,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著潜圃,像睡著了一般缸棵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谭期,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天堵第,我揣著相機與錄音稚晚,去河邊找鬼。 笑死型诚,一個胖子當著我的面吹牛客燕,可吹牛的內容都是我干的。 我是一名探鬼主播狰贯,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼也搓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涵紊?” 一聲冷哼從身側響起傍妒,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摸柄,沒想到半個月后颤练,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡驱负,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年嗦玖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跃脊。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡宇挫,死狀恐怖,靈堂內的尸體忽然破棺而出酪术,到底是詐尸還是另有隱情器瘪,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布绘雁,位于F島的核電站橡疼,受9級特大地震影響,放射性物質發(fā)生泄漏庐舟。R本人自食惡果不足惜欣除,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望继阻。 院中可真熱鬧耻涛,春花似錦、人聲如沸瘟檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墨辛。三九已至,卻和暖如春趴俘,著一層夾襖步出監(jiān)牢的瞬間睹簇,已是汗流浹背奏赘。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留太惠,地道東北人磨淌。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像凿渊,于是被迫代替她去往敵國和親梁只。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容