① elastic索引最多可以創建多少欄位
elastic索引最多可以創建10000個欄位,默認1000個。當分片被占滿後,創建新索引失敗。每個Elasticsearch碎片都是一個Lucene索引。一個Lucene索引中可以包含的文檔最多。設置ignore_above後,超過給定長度後的數據將不被索引,無法通過term精確匹配檢索返回結果。索引是一種單獨的、物理的對資料庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引針對表而建立,每個索引頁面中的行都會含有邏輯指針,以便加速檢索物理數據。
② 【ES】ElasticSearch 深入分片
@[toc]
分片是 Elasticsearch 在集群中分發數據的關鍵。
把分片想像成數據的容器。文檔存儲在分片中,然後分片分配到集群中的節點上。當集群擴容或縮小,Elasticsearch 將會自動在節點間遷移分片,以使集群保持平衡。
一個分片(shard)是一個最小級別「工作單元(worker unit)」,它只是保存了索引中所有數據的一部分。
這類似於 MySql 的分庫分表,只不過 Mysql 分庫分表需要藉助第三方組件而 ES 內部自身實現了此功能。
分片可以是 主分片(primary shard) 或者是 復制分片(replica shard) 。
在集群中唯一一個空節點上創建一個叫做 blogs 的索引。默認情況下,一個索引被分配 5 個主分片,下面只分配 3 個主分片和一個復制分片(每個主分片都有一個復制分片)並碼:
在一個多分片的索引中寫入數據時,通過路由來確定具體寫入哪一個分片中,大致路由過程如下:
routing 是一個可變值,默認是文檔的 _id ,也可以設置成一個自定義的值。routing 通過 hash 函數生成一個數字,然後這個數字再除以 number_of_primary_shards (主分片的數量)後得到余數 。這個在 0 到 number_of_primary_shards 之間的余數,就是所尋求的文檔所在分片的位置。
這解釋了為什麼要在創建索引的時候就確定好主分片的數量並且永遠不會改變這個數量: 因為如果數量變化了,那麼所有之前路由的值都會無效,文檔也再也找不到了 。
索引中的每個文檔屬於一個單獨的主分片,所以 主分片的數量決定了索引最多能存儲多少數據 (實際的數量取決於數據、硬體和應用場景)。
復制分片只是主分片的一個副本,它可以 防止硬體故障導致的數據丟失,同時可以提供讀請求,比如搜索或者從別的 shard 取迴文檔 。
每個主分片都有一個或多個副本分片,當主分片異常絕飢哪時,副本可以提供數據的肢滾查詢等操作。主分片和對應的副本分片是不會在同一個節點上的,所以副本分片數的最大值是 n -1(其中 n 為節點數)。
當索引創建完成的時候,主分片的數量就固定了,但是復制分片的數量可以隨時調整,根據需求擴大或者縮小規模。如把復制分片的數量從原來的 1 增加到 2 :
分片本身就是一個完整的搜索引擎,它可以使用單一節點的所有資源。 主分片或者復制分片都可以處理讀請求——搜索或文檔檢索,所以數據的冗餘越多,能處理的搜索吞吐量就越大。
對文檔的新建、索引和刪除請求都是寫操作,必須在主分片上面完成之後才能被復制到相關的副本分片,ES 為了提高寫入的能力這個過程是並發寫的,同時為了解決並發寫的過程中數據沖突的問題,ES 通過樂觀鎖的方式控制,每個文檔都有一個 _version (版本)號,當文檔被修改時版本號遞增。一旦所有的副本分片都報告寫成功才會向協調節點報告成功,協調節點向客戶端報告成功。
ES 集群中每個節點通過路由都知道集群中的文檔的存放位置,所以每個節點都有處理讀寫請求的能力。
在一個寫請求被發送到某個節點後,該節點即為協調節點,協調節點會根據路由公式計算出需要寫到哪個分片上,再將請求轉發到該分片的主分片節點上。假設 shard = hash(routing) % 4 = 0 ,則過程大致如下:
寫入磁碟的倒排索引是不可變的,優勢主要表現在:
當然,不可變的索引有它的缺點:
在全文檢索的早些時候,會為整個文檔集合建立一個大索引,並且寫入磁碟。只有新的索引准備好了,它就會替代舊的索引,最近的修改才可以被檢索。這無疑是低效的。
因為索引的不可變性帶來的好處,那如何在保持不可變同時更新倒排索引?
答案是,使用多個索引。 不是重寫整個倒排索引,而是增加額外的索引反映最近的變化。 每個倒排索引都可以按順序查詢,從最老的開始,最後把結果聚合。
這就引入了 段 (segment) :
分片下的索引文件被拆分為多個子文件,每個子文件叫作 段 , 每一個段本身都是一個倒排索引,並且段具有不變性,一旦索引的數據被寫入硬碟,就不可再修改。
段被 寫入到磁碟 後會生成一個 提交點 ,提交點是一個用來記錄所有提交後段信息的文件。一個段一旦擁有了提交點,就說明這個段只有讀的許可權,失去了寫的許可權。相反,當段在內存中時,就只有寫的許可權,而不具備讀數據的許可權,意味著不能被檢索。
在 Lucene 中的索引(Lucene 索引是 ES 中的分片,ES 中的索引是分片的集合)指的是段的集合,再加上提交點(commit point),如下圖:
在底層採用了分段的存儲模式,使它在讀寫時幾乎完全避免了鎖的出現,大大提升了讀寫性能。
索引文件分段存儲並且不可修改 ,那麼新增、更新和刪除如何處理呢?
ES 是怎麼做到 近實時 全文搜索?
磁碟是瓶頸。提交一個新的段到磁碟需要 fsync 操作,確保段被物理地寫入磁碟,即時電源失效也不會丟失數據。但是 fsync 是昂貴的,嚴重影響性能,當寫數據量大的時候會造成 ES 停頓卡死,查詢也無法做到快速響應。
所以 fsync 不能在每個文檔被索引的時就觸發,需要一種更輕量級的方式使新的文檔可以被搜索,這意味移除 fsync 。
為了提升寫的性能,ES 沒有每新增一條數據就增加一個段到磁碟上,而是採用 延遲寫 的策略。
每當有新增的數據時,就將其先寫入到內存中,在內存和磁碟之間是文件系統緩存,當達到默認的時間(1秒鍾)或者內存的數據達到一定量時,會觸發一次刷新(Refresh),將內存中的數據生成到一個新的段上並緩存到文件緩存系統 上,稍後再被刷新到磁碟中並生成提交點 。
這里的內存使用的是ES的JVM內存,而文件緩存系統使用的是操作系統的內存。新的數據會繼續的被寫入內存,但內存中的數據並不是以段的形式存儲的,因此不能提供檢索功能。由內存刷新到文件緩存系統的時候會生成了新的段,並將段打開以供搜索使用,而不需要等到被刷新到磁碟。
在 Elasticsearch 中,這種寫入和打開一個新段的輕量的過程叫做 refresh (即內存刷新到文件緩存系統)。默認情況下每個分片會每秒自動刷新一次。 這就是為什麼說 Elasticsearch 是近實時的搜索了:文檔的改動不會立即被搜索,但是會在一秒內可見。
也可以手動觸發 refresh。 POST /_refresh 刷新所有索引, POST /index/_refresh 刷新指定的索引:
沒用 fsync 同步文件系統緩存到磁碟,不能確保電源失效,甚至正常退出應用後,數據的安全。為了 ES 的可靠性,需要確保變更持久化到磁碟。
雖然通過定時 Refresh 獲得近實時的搜索,但是 Refresh 只是將數據挪到文件緩存系統,文件緩存系統也是內存空間,屬於操作系統的內存,只要是內存都存在斷電或異常情況下丟失數據的危險。
為了避免丟失數據,Elasticsearch添加了 事務日誌(Translog) ,事務日誌記錄了所有還沒有持久化到磁碟的數據。
有了事務日誌,過程現在如下:
事務日誌記錄了沒有 flush 到硬碟的所有操作。當故障重啟後,ES 會用最近一次提交點從硬碟恢復所有已知的段,並且從日誌里恢復所有的操作。
在 ES 中,進行一次提交並刪除事務日誌的操作叫做 flush 。分片每 30 分鍾,或事務日誌過大會進行一次 flush 操作。 flush API 也可用來進行一次手動 flush , POST/ _flush 針對所有索引有效, POST /index/_flush 則指定的索引:
通常很少需要手動 flush ,通常自動的就夠了。
總體的流程大致如下:
由於自動刷新流程每秒會創建一個新的段 ,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。每一個段都會消耗文件句柄、內存和 cpu 運行周期。更重要的是,每個搜索請求都必須輪流檢查每個段然後合並查詢結果,所以段越多,搜索也就越慢。
ES 通過後台合並段解決這個問題。小段被合並成大段,再合並成更大的段。這時舊的文檔從文件系統刪除的時候,舊的段不會再復制到更大的新段中。合並的過程中不會中斷索引和搜索。
段合並在進行索引和搜索時會自動進行,合並進程選擇一小部分大小相似的段,並且在後台將它們合並到更大的段中,這些段既可以是未提交的也可以是已提交的。
合並結束後老的段會被刪除,新的段被 flush 到磁碟,同時寫入一個包含新段且排除舊的和較小的段的新提交點,新的段被打開可以用來搜索。
1. 全文搜索引擎Elasticsearch,這篇文章給講透了
2.ElasticSearch 權威指南》
③ Es實現百萬級數據快速檢索
在用戶點擊一篇采購文章,會匹配到該文章全部相關內容。所有數據是存在ES中的,百萬量級。恩~要用python寫一個介面。通過查找資料,通過 ES模糊搜索 可以實現。
prefix的匹配一般是處理不分詞的場景,將會匹配articleID中以」J」開頭的doc。prefix不會計算revelance score,只是作一個過濾的操作,和filter唯一的區別是filter會緩存結果,而prefix不會。前綴越短要處理的doc越多,茄高如性能越差。
?會匹配任意字元,*會匹配0個或多個字元。性能根prefix一樣差,必須要掃描整個倒排索引。
[0-9]:指定范圍內的數字
[a-z]:指定范圍內的字幕
.:一個字元
+:前面的正則表達式可以出現一次或多次
正則的搜索同樣會掃描全表,性能也會很差
fuzziness參數調整糾正的次數
通常不會直接用上述搜索,而會用下面的搜索:
在es中,使用組合條件查詢是其作為搜索引擎檢索數據的一個強大之處,在前幾篇中,簡單演示了es的查詢語法,但基本的增刪改查功能並不能很好的滿足復雜的查詢場景,比如說我們期望像mysql那樣做到拼接復雜的條件進行查詢該如何做呢?es中有一種語法叫bool,通過在bool裡面拼接es特定的語法可以做到大部分場景下復雜條念檔件的拼接查詢,也叫復合查詢
首先簡單介紹es中常用的組合查詢用到的關鍵詞,
filter:過濾,不參與打分
must:如果有多個條件,這些條件都必須滿足 and與
should:如果有多個條件,滿足一個或多個即可 or或
must_not:和must相反,必須都不滿足條件才可以匹配到 !非
發生 描述
must
該條款(查詢)顫啟必須出現在匹配的文件,並將有助於得分。
filter
子句(查詢)必須出現在匹配的文檔中。然而不像 must查詢的分數將被忽略。Filter子句在過濾器上下文中執行,這意味著評分被忽略,子句被考慮用於高速緩存。
should
子句(查詢)應該出現在匹配的文檔中。如果 bool查詢位於查詢上下文中並且具有mustor filter子句,則bool即使沒有should查詢匹配,文檔也將匹配該查詢 。在這種情況下,這些條款僅用於影響分數。如果bool查詢是過濾器上下文 或者兩者都不存在,must或者filter至少有一個should查詢必須與文檔相匹配才能與bool查詢匹配。這種行為可以通過設置minimum_should_match參數來顯式控制 。
must_not
子句(查詢)不能出現在匹配的文檔中。子句在過濾器上下文中執行,意味著評分被忽略,子句被考慮用於高速緩存。因為計分被忽略,0所有文件的分數被返回。
下面用實驗演示一下上述查詢的相關語法,
1、首先,我們創建一個索引,並且在索引里添加幾條數據,方便後面使用,
我這里直接批量插入數據,也可以通過PUT的語法單條執行插入,
POST /forum/article/_bulk
{ "index": { "_id": 1 }}
{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2019-07-01","title":"java contains hadoop and spark","topic":"java" }
{ "index": { "_id": 2 }}
{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2019-07-02",title":"php contains admin","topic":"java and php" }
{ "index": { "_id": 3 }}
{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2019-07-03" ,title":"spark is new language","topic":"spark may use java"}
{ "index": { "_id": 4 }}
{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2019-07-04" ,title":"hadoop may involve java","topic":"big data used"}
或者使用put語法
PUT /forum/article/4
{
"articleID": "QQPX-R-3956-#aD8",
"userID": 2,
"hidden": true,
"postDate": "2019-07-04",
"title": "hadoop may involve java",
"topic": "big data used"
}
4條數據插入成功,
2、termQuery,term查詢不分詞,類似於mysql的where filedName = ? 語法,即精準匹配,比如我們查詢articleID = XHDK-A-1293-#fJ3的這條數據,
GET /forum/article/_search
{
"query": {
"term": {
"articleID.keyword":"XHDK-A-1293-#fJ3"
}
}
}
2、must查詢,即查詢的條件中必須匹配的欄位,例如,查詢title中必須包含java的數據,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{"term":{"title":"hadoop"}}
]
}
}
}
查出兩條數據
如果是should呢?如下語法,即查詢title中包含hadoop或者topic中包含spark,二者滿足其一即可,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{"term":{"title":"hadoop"}},
{"term": {"topic": "spark"}}
]
}
}
}
查詢出3條數據,
must和should結合使用,
最後再來一個比較復雜的嵌套查詢,我們先看一下這條sql語句,
select *
from forum.article
where article_id=『XHDK-A-1293-#fJ3』
or (article_id=『JODL-X-1937-#pV7』 and post_date=『2017-01-01』),
對應著轉化為es的復合查詢語法是怎樣的呢?拆分來看,就是一個should語句的嵌套,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"articleID.keyword": "XHDK-A-1293-#fJ3"
}
},
{
"bool": {
"must": [
{
"term": {
"articleID.keyword":"JODL-X-1937-#pV7"
}
},
{
"term": {
"postDate":"2019-07-01"
}
}
]
}
}
}
}
查詢到一條結果,按照這種思路,如果我們對一個復雜的查詢不知道如何構建查詢語句時,可以考慮先按照sql的語法進行拆分,然後再組織es查詢語句是個不錯的突破口,
到這里,可能我們會有疑問,復合條件中的term查詢和單純的match區別在哪裡呢?既然都是查詢,究竟原理有何不同呢?
我們知道match query是需要全文檢索的,是進行full text的全文檢索,當然如果搜索的欄位值做了not_analyzed,match query也相當於是term query了,比如下面這個搜索,由於在插入數據的時候我們沒有對title這個欄位進行規定,默認就是text類型的,會被自動分詞,這樣查詢的時候只要title中包含了 hadoop,就可以匹配到,
GET /forum/article/_search
{
"query": {
"match": {
"title": "hadoop"
}
}
}
2、有些情況下,假如我們直接使用match進行查詢,又希望查出來的結果盡可能是我們期望的包含更多關鍵詞的結果,則在match進行匹配的時候可以添加其他的條件,以便提升結果的匹配精確度,
GET /forum/article/_search
{
"query": {
"match": {
"title": {
"query": "java hadoop",
"operator": "and"
}
}
}
}
這樣匹配出來的結果包含了更多我們期望的關鍵詞,即query中可以指定我們查詢的結果中包含的關鍵詞,
es還有其他的語法達到上述的效果,minimum_should_match ,通過這個語法,可以指定匹配的百分數,就是查詢的關鍵詞至少要達到的百分數,下面這個表示全部匹配,只查詢到一條結果,
假如我們將百分數調低點,比如75%,可以看到查到兩條結果,
3、當然,我們也可以將bool和match結合起來使用,如下,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "java"}}
],
"must_not": [
{ "match": { "title": "spark"}}
]
, "should": [
{
"match": {
"title": "php"
}
}
]
}
}
}
通過這種方式,也可以達到更精準的匹配我們期望的查詢結果,
簡單總結來說,當我們使用match進行查詢的時候,如果查詢的field包含多個詞,比如像下面這個,
{
"match": { "title": "java elasticsearch"}
}
其實es會在底層自動將這個match query轉換為bool的語法bool should,指定多個搜索詞,同時使用term query,則轉化後的語法如下,
{
"bool": {
"should": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
而上面所說的match中加and的查詢,對應於bool查詢,轉化後為 term+must 的語法如下,
{
"match": {
"title": {
"query": "java elasticsearch",
"operator": "and"
}
}
}
{
"bool": {
"must": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
對於minimum_should_match這種語法來說,道理類似,
{
"match": {
"title": {
"query": "java elasticsearch hadoop spark",
"minimum_should_match": "75%"
}
}
}
{
"bool": {
"should": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }},
{ "term": { "title": "hadoop" }},
{ "term": { "title": "spark" }}
],
"minimum_should_match": 3
}
}
我們來看一個具體的操作實例,也就是說必須至少包含3個關鍵詞的數據才會出現在搜索結果中,
3、在搜索中,我們有這樣一種需求,期望搜索的結果中包含java 如果標題中包含hadoop或spark就優先搜索出來,同時呢,如果一個帖子包含java hadoop,一個帖子包含java spark,包含hadoop的帖子要比spark優先搜索出來,
對於這樣的需求,通俗來講,就是需要通過增大某些搜索條件的權重,從而在搜索的結果中,更多符合和滿足我們業務場景的數據靠前搜索出來,在es中可以通過boost關鍵詞來增加搜索條件的權重,
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "java"
}
}
],
"should": [
{
"match": {
"title": {
"query": "hadoop"
}
}
},
{
"match": {
"title": {
"query": "spark",
"boost":2
}
}
},
{
"match": {
"title": {
"query": "php"
}
}
},
{
"match": {
"title": {
"query": "hadoop",
"boost": 5
}
}
}
]
}
}
}
上面這個例子意思是我們賦予搜索的title中包含hadoop的條件權重更大,hadoop的結果會有限被搜索出來
4、dis_max語法,也叫best_field,在某些情況下,假如我們在bool查詢中用多個欄位進行查詢,但是查詢一樣,就可能導致說查詢出來的結果並不是按照我們期望的那個欄位將其排在前面,也就是說,我們只需要包含指定欄位的內容展示在前面,如下,
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
title和content的搜索條件相同,但我們希望的是結果中title 包含java solution的靠前展示,但直接這樣查詢可能達不到預期的效果,如果使用dis_max進行拼接就可以了,
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
通過這樣的方式,使得查詢的結果更符合預期值,
5、但是使用dis_max,只取某一個query最大的分數,完全不考慮其他query的分數,即假如說某個結果中包title含了java,但topic中沒有包含java,另一卻是相反,還有的結果是兩者都包含java,在dis_max語法下,只會拿到相關度得分最高的那一個,而不會考慮其他的結果,這時,如果需要獲取其他的title或者topic包含java的結果,可以使用tie_breaker進一步包裝,如下,
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "spark" }},
{ "match": { "topic": "java"}}
],
"tie_breaker": 0.6
}
}
}
這樣查到3條結果,綜合來說,最終還是需要結合實際業務場景進行使用,但是在大多數情況相愛,我們還是希望搜索的結果中是按照我們給定的條件包含更多的關鍵詞的內容被優先搜索出來,
④ Elasticsearch 能夠存儲的數據量一般有多大
單獨看ES能玩多大數據意義不大,具體實踐中往往因為各種業務要求而無法繼續增加數據量。目大的方面考慮有如下幾點:
1、查詢速度。ES可以支持的查詢類型多種多樣,單一的term匹配,復雜的historm agg,甚至父子文檔模式下bool查詢之後繼續做文本高亮,數據量越大查詢時間越長。如果只是簡單的把數據寫進去然後按照ID獲取數據,那就盡管往裡面寫數據吧。
2、寫入速度。數據量越大,寫入速度受影響的可能性越大。業務要求1小時的數據1小時內必須寫完,如果做不到就得考慮分索引或者分集群了。
3、更新速度。同上,更新比單純的寫入操作更多,先get再merge再overwrite到es。
4、其他因素。
目前我遇到的ES集群,有1.5T-2T索引量的情況下,需要支持平均查詢在500ms以內的高並發高亮查詢。在我們的場景下這個量級不算小了。