Neil Zhu,簡書ID Not_GOD,University AI 創始人 & Chief Scientist,致力于推進世界人工智能化進程。制定并實施 UAI 中長期增長戰略和目標,帶領團隊快速成長為人工智能領域最專業的力量。
作為行業領導者,他和UAI一起在2014年創建了TASA(中國最早的人工智能社團), DL Center(深度學習知識中心全球價值網絡),AI growth(行業智庫培訓)等,為中國的人工智能人才建設輸送了大量的血液和養分。此外,他還參與或者舉辦過各類國際性的人工智能峰會和活動,產生了巨大的影響力,書寫了60萬字的人工智能精品技術內容,生產翻譯了全球第一本深度學習入門書《神經網絡與深度學習》,生產的內容被大量的專業垂直公眾號和媒體轉載與連載。曾經受邀為國內頂尖大學制定人工智能學習規劃和教授人工智能前沿課程,均受學生和老師好評。
探索elasticsearch
by Andrew Cholakian
相比較官方文檔,這本書很好看。
第二章 建模數據
2.1 文檔和字段的基礎知識
在elasticsearch中的最小獨立單元是字段(field),字段有一個已定義的類型(type),并且擁有一個或者多個該類型的值。字段包含一個簡單的數據塊,如數字42
或者字符串"Hello, World!"
,亦或一個同樣類型數據的簡單表(single list),如[5,6,7,8]
。
文檔(document)是字段的聚合(collection),并且包含了elasticsearch中的基本存儲單元,就像在傳統的RDBMS中的行(row)那樣。將文檔作為一個基本的存儲單元的原因是,與Lucene不同的是,所有完全更新的字段重寫一個給定的文檔進存儲設備(同時保留哪些沒有變更的字段)。所以,當我們從一個API角度看,字段是最小的單元,而文檔則是從存儲角度的最小單元。
2.1.1 JSON——ES的語言
在ES中,所有的文檔必須為合法的JSON值。例如
{
"_id": 1,
"handle": "ron",
"age": 28,
"hobbies": ["hacking", "the great outdoors"],
"computer": {"cpu": "pentium pro", "mhz": 200}
}
Elasticsearch reserves some fields for special use. We’ve specified one of these fields in this example: the _id
field. A document’s id is unique, and if unassigned will be created automatically. An elasticsearch id would be a primary key in RDBMS parlance.
While elasticsearch deals with JSON exclusively, internally, the JSON is converted to flat fields for Lucene’s key/value API. Arrays in documents are mapped to Lucene multi-values.
2.2 基本類型
2.2.1 概述
每個ES中的文檔肯定要遵循一個用戶定義的類型映射,類似于數據庫的模式(schema)。一個類型的映射同時包含了字段的類型(integer、string……)和這些屬性將被如何索引的方式(這較復雜的部分在后面會介紹)。
多個在一個索引的文檔可能擁有相同的ID只要這些文檔的類型是不同的。
類型使用Mapping API定義,其將type name和property定義進行關聯。圖2.1最小版本的映射如下:
{
"user": {
"properties": {
"handle": {"type": "string"},
"age": {"type": "integer"},
"hobbies": {"type": "string"},
"computer": {
"properties": {
"cpu": {"type": "string"},
"speed": {"type": "integer"}}}}}}
這里相當于數據庫模式的定義
2.2.2 數據類型
在一個mapping的屬性部分,每個字段都與一個不同的core類型關聯。合法的core數據類型如下表所示。除了關聯數據類型到字段外,類型映射定義了諸如analysis設定和默認的boosting值的屬性;后續章節會介紹。
Type | Definition |
---|---|
string | Text |
integer | 32 bit integers |
long | 64 bit integers |
float | IEEE float |
double | Double precision floats |
boolean | true or false |
date | UTC Date/Time (JodaTime) |
geo_point | Latitude / Longitude |
2.2.3 數組,對象和高級類型
復雜JSON類型同樣被ES支持,使用數組和對象表示。另外的,ES文檔可以處理更加復雜的關系,諸如parent/child關系,和一個嵌套的聞到那個類型。
注意ES中數組不支持混合類型的元素。如果一個field被聲明為一個整數,它可以存儲一個或者多個整數,不過不能參雜其他類型的元素。
An important thing to remember, however, is that elasticsearch arrays cannot store mixed types. If a field is declared as an integer, it can store one or many integers, but never a mix of types.
As seen in figure 2.2, mappings may describe objects which contain other objects, as in the computer field in that figure. The key thing to remember with these objects is that their properties are declared in the properties field in the mapping, and that the type field is omitted.
當使用對象的時候,type字段被省略。
子對象仍然存儲在同樣的物理文檔中。所以,還有一個特殊的nested
類型,盡管相似,但是擁有不同的性能和查詢特征,因為被分別存儲在不同的文檔中。(也就是不同的記錄row中)。后面將介紹parent/child文檔。
2.3 索引的基礎
在ES中最大的獨立的數據單元是index
。索引是文檔的邏輯和物理劃分(partition)。文檔和文檔類型對于index來說是唯一的。索引不知道包含在其他索引中的數據。從可操作角度看,需要性能和持久性(durability)相關的選項(option)被設置為per-index層面。
從查詢的角度,當ES支持交叉索引搜索時,通常in practice it usually makes more organizational sense to design for searches against individual indexes.
ES索引是在一個獨立的運行時服務器實例一個完全劃分的universe。文檔和類型映射是以index范圍內進行的。所以跨索引重用名稱和id是安全的。索引同樣擁有供集群復制,sharding,定制文本分析和其他考慮的本身的設置。
ES的索引不是與Lucene索引一一對應的。實際上,他們是一個Lucene索引的集合(通常設為5)對shard有一個備份。一個單一的機器可能相比較其他的機器對同一個索引有或多或少的shards。ES盡可能地保持所有索引的全部數據在所有的機器上都一致,即使那意味著某些索引可能會在某臺機器上不成比例地出現。每個shard擁有一個可配置數目地全replica,這總是存儲在唯一地實例上。如果cluster不足夠大來支撐指定數量地replica,cluster health會被報告為惡化的(‘yellow’)狀態。基本的ES開發安裝因此在使用默認索引運行。一個單一運行的實例沒有peer來復制其數據。注意這對于開發過程沒有實際的影響。然后,建議大家ES在產品環境中運行在多個服務器上。正如一個clustered數據庫,數據的保證依賴于多個節點的可用上。
我們在下個section:Basic CRUD介紹基本使用index的操作命令
2.4 CRUD基礎
Let’s perform some basic operations on data. Elasticsearch is RESTish in design and tends to match HTTP verbs up to the Create, Read, Update, and Delete operations that are fundamental to most databases. We’ll create an index, then a type, and finally a document within that index using that type. Open up elastic-hammer, and we’ll issue the following operations from figure 2.3.
F 2.3 Simple CRUD
// Create a type called 'hacker'
PUT /planet/hacker/_mapping
{
"hacker": {
"properties": {
"handle": {"type": "string"},
"age": {"type": "long"}}}}
// Create a document
PUT /planet/hacker/1
{"handle": "jean-michel", "age": 18}
// Retrieve the document
GET /planet/hacker/1
// Update the document's age field
POST /planet/hacker/1/_update
{"doc": {"age": 19}}
// Delete the document
DELETE /planet/hacker/1
fig-simplecrud
// Create an index named 'planet'
PUT /planet
在上面的例子中,我們可以看到ES全部的CRUD生命周期。現在我們可以對數據執行一些基本的操作。現在就到了最為關鍵的地方了——搜索。注意URL模式與這些操作是一致的,大多數的URL形如/index/type/docid
,使用一個下劃線前綴作為特定操作的名字空間。
database ACID
- atomicity;
- consistency;
- isolation;
- durability:
* if a flight booking reports that a seat has successfully been booked, then the seat will remain booked even if the system crashes. In distributed transactions, all participating servers must coordinate before commit can be acknowledged. This is usually done by a two-phase commit protocol.
* Many DBMSs implement durability by writing transactions into a transaction log that can be reprocessed to recreate the system state right before any later failure. A transaction is deemed committed only after it is entered in the log.
shard
shard是Lucene的獨立的實例,這個工作單元是由elasticsearch自動管理的。一個index是一個邏輯的名字空間,其指向主從shards。但是我們不需要關注一個索引如何定義主從shard的數量,不會直接接觸到shard,只需要直接處理index。ES將shards分發給cluster中所有的節點。,并且可以在node失敗時或者新增nodes時自動地將shards從一個node移向另外一個。
第三章 搜索數據
3.1 搜索API
搜索API通常使用/index
和index/type
后接_search
作為路徑。索引搜索可能是/myidx/_search,而對獨立文檔類型的范圍搜索可以是/myidx/mytype/_search
。搜索API的作用是調用一個查詢,其參數可以是結果集合的最大數,結果偏移位置,和一些性能指標選項。搜索API同樣提供Faceting和過濾(Filtering),這些在后面會介紹到。
下面展示了搜索API的骨架。例子中只有size和query參數被設置,沒有facet或者filter被應用。例子很簡單。_search
后綴可以使用GET
和POST
HTTP方法。
Simple Query
{
"size": 3,
"query": {
"match": {"hobbies": "skateboard"}
}}
Query DSL用來確定哪些文檔匹配了指定的限定。同樣對文檔進行排序,通過他們的相似度來做排序的基準,這在Lucene中是標準的技術術語。相似度度值通常被稱文檔的打分(score)。The Query DSL is employed as the contents of either the query key in JSON posted to the _search endpoint as in the example above.
3.1.1 Mixing查詢、過濾器和Facets
- http://www.slideshare.net/medcl/elastic-search-training1-brief-tutorial#btnLast
- http://www.slideshare.net/endless_yy/ss-27849480
transportclient, node client
3.2 分析
3.2.1 文本分析
分析自然是最為重要的和神奇的特性了,可以處理自然語言及復雜數據。
下面出現的問題,就是當數據存儲在文檔中時,知道哪里和什么時候使用分析,然后在每次查詢過來時,根據那個字段的分析規則進行匹配。文檔進行分析過程如下:
- 文檔的更新和創建使用
PUT
或者POST
- 文檔中字段的值通過一個分析器后被轉化成零個、1個或者多個可索引的token
- 被token化的值存放在索引中個,指向了這個文檔的全文
3.2.2 分析API
The easiest way to see analysis in action is with the Analyze API, which lets you test pieces of text against any analyzer. To test the words “candles” and “candle” for instance, against a snowball analyzer, you would issue the query in Figure 3.6.
使用分析API是看分析過程的最佳方法,你可以對分析器測試各種文檔輸入。例如測試詞“candles”和“candle”,使用一個snowball分析器:
F 3.6 使用分析API
GET '/_analyze?analyzer=snowball&text=candles%20candle&pretty=true'
在這種情形下,我們可以分析“candles candle”來展示兩個相似的詞是如何被分析的。你會得到下面的結果:
F 3.7 Analysis API Output
{
"tokens" : [ {
"token" : "candl",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "candl",
"start_offset" : 8,
"end_offset" : 14,
"type" : "<ALPHANUM>",
"position" : 2
} ]
}
分析API在試著區分為何一些詞完全一樣的token化還有其他沒有的時候是最有價值的。如果你被不知道如何解決沒有匹配的查詢難住,首先你可以使用分析API來試試一些文本
定制分析器
分析器是典型的三步走:
- Character Filtering: 將輸入字符串轉化為一個不同的字符串
- Tokenization: 將char過濾后的字符串轉化為一個token的數組
- Token Filtering: 將過濾過的token轉化為一個mutated token數組
3.3 基于相似度的排名
現在我們走入了ES的核心部分。搜索是兩大步:
- 匹配所有滿足要求的文檔。這是IR領域的布爾搜索,因為這步只是存在與否的的衡量。
- 第二步則是基于與查詢的相似度進行打分,然后以打分的倒序展示結果。
有多個可配置的相似度算法。本章介紹默認的TFIDF相似度類。這是相當著名的打分算法,在Lucene的TFIDFSimilarity
類中實現。
基本思想:
文檔的分數高當:
- 被匹配的term是“罕見的”,指這個term比其他term在更少的文檔中出現。
- term以一個很高的頻率出現在該文檔中
- 如果多個term在查詢中,該文檔比其他文檔包含更多的查詢term
- 字段field或文檔在索引時指定了boost因子或者查詢時間(query time)。
上面的列表是極其簡化的相似度計算的思想。請牢記的是,所有的打分因子之間關系不都是線性的,還有一些比較微妙的地方。然而,大多數時間,Lucene已經做了你想要做的,而不要你自己計算TF/IDF。而且,ES支持可配置的相似度算法,如BM25算法。如果給你的算法包對你作用不大,你自己可以寫一個script
查詢,這樣可以根據自己的想法對文檔進行打分,或者寫一個實現你自己的打分算法的java插件。
一般來說,TF/IDF、顯式的排序(如按日期降序)、和腳本查詢,將給你很好的效果。
分面
3.4.1 什么是分面(Faceting)
聚合統計是ES的一個核心部分,可以通過Search API進行調用。分面(facet)總是跟一個查詢一起,讓我們返回正常查詢結果和聚合統計。想象一個用戶使用電影名來查詢電影。使用分面搜索你可以給出結果中不同類屬的聚合數量。如果你曾經在一個電商網站上進行過搜索,你可能會在sidebar處看到一個下拉選項。
分面是高度可定制的,同樣也是可以復合的。除了統計不同字段的值外,分面可以使用更加復雜的群組特性,例如時間跨度、嵌套過濾器、甚至全面、嵌套的elasticsearch查詢。
Filtering
filter對搜索的執行path有著明顯的影響。當查詢給出哪些文檔出現在結果中,以及他們如何被打分,過濾器只會給出哪些文檔出現在結果中。這個可以獲得一個極其快速的查詢。另外,一些限制可以通過過濾器指定。過濾器可以將結果集進行切分,且不需要執行代價昂貴的打分計算。過濾器也可以用來,當一個term必須要匹配但是其對文檔整體分數的影響應該是一個確定的量而不管TF/IDF分值。最后,不同與查詢的是,filters可以被緩存,當filter被重復使用時,導致了一個明顯的性能提升。
elasticsearch有三種filter出現方式。控制filter應用在query和factes上,查詢或者自查詢,或者facet。下面列表說明
- Queries of the filtered/constant_score類型:這些都內嵌在query字段中,filters將影響查詢結果和facet計數。
- top-level filter element: 確定一個filter在搜索的根,只會過濾查詢,但不會影響facet
- facets with facet_filter選項:給每個facet添加可選的facet_filter元素,這個可以用來在數據被聚合之前預先過濾數據。這個過濾器將只會影響定義在其里面的facet,不會影響查詢結果。
3.5.1 過濾的三種不同過程
使用filtered和constant_score_quries
這兩種類型均允許嵌套一個regular查詢。過濾器先運行,然后查詢和任何的的facets,如果filter足夠強地限制了結果集合,這將千載地提供了一個有效的安全的速度提升。
// Load Dataset: products.eloader
POST /products/_search
{
"facets": {"department": {"terms": {"field": "department_name"}}},
"query": {
"filtered": {
"query": {"match": {"name": "fake"}},
"filter": {"term": {"department_name": "Books"}}}}}