寫給大忙人的Elasticsearch架構與概念(未完待續)

最新版本官方文檔https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
文檔增刪改參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docs.htmlhtml

Elasticsearch主要概念

Index,索引:一系列具備相似屬性的文檔集合,相似於數據庫裏的表,集羣中能夠包含的索引數不限,索引是邏輯概念(對應物理上爲分片,shard),通常應提早手工建立而非自動建立便於管理,索引的名稱和存儲名稱不相同可是有着對應關係,可經過查詢索引信息的uuid獲得。一個典型的索引定義以下:java

{
    "logstash-2018-06": {
        "aliases": {},
        "mappings": {
            "_default_": {
                "dynamic_templates": [
                    {
                        "message_field": {
                            "path_match": "message",
                            "match_mapping_type": "string",
                            "mapping": {
                                "norms": false,
                                "type": "text"
                            }
                        }
                    },
                    {
                        "string_fields": {
                            "match": "*",
                            "match_mapping_type": "string",
                            "mapping": {
                                "fields": {
                                    "keyword": {
                                        "ignore_above": 256,
                                        "type": "keyword"
                                    }
                                },
                                "norms": false,
                                "type": "text"
                            }
                        }
                    }
                ],
                "properties": {
                    "@timestamp": {
                        "type": "date"
                    },
                    "@version": {
                        "type": "keyword"
                    },
                    "geoip": {
                        "dynamic": "true",
                        "properties": {
                            "ip": {
                                "type": "ip"
                            },
                            "latitude": {
                                "type": "half_float"
                            },
                            "location": {
                                "type": "geo_point"
                            },
                            "longitude": {
                                "type": "half_float"
                            }
                        }
                    }
                }
            },
            "doc": {
                "dynamic_templates": [
                    {
                        "message_field": {
                            "path_match": "message",
                            "match_mapping_type": "string",
                            "mapping": {
                                "norms": false,
                                "type": "text"
                            }
                        }
                    },
                    {
                        "string_fields": {
                            "match": "*",
                            "match_mapping_type": "string",
                            "mapping": {
                                "fields": {
                                    "keyword": {
                                        "ignore_above": 256,
                                        "type": "keyword"
                                    }
                                },
                                "norms": false,
                                "type": "text"
                            }
                        }
                    }
                ],
                "properties": {
                    "@timestamp": {
                        "type": "date"
                    },
                    "@version": {
                        "type": "keyword"
                    },
                    "beat": {
                        "properties": {
                            "hostname": {
                                "type": "text",
                                "norms": false,
                                "fields": {
                                    "keyword": {
                                        "type": "keyword",
                                        "ignore_above": 256
                                    }
                                }
                            },
                            "name": {
                                "type": "text",
                                "norms": false,
                                "fields": {
                                    "keyword": {
                                        "type": "keyword",
                                        "ignore_above": 256
                                    }
                                }
                            },
                            "version": {
                                "type": "text",
                                "norms": false,
                                "fields": {
                                    "keyword": {
                                        "type": "keyword",
                                        "ignore_above": 256
                                    }
                                }
                            }
                        }
                    },
                    "geoip": {
                        "dynamic": "true",
                        "properties": {
                            "ip": {
                                "type": "ip"
                            },
                            "latitude": {
                                "type": "half_float"
                            },
                            "location": {
                                "type": "geo_point"
                            },
                            "longitude": {
                                "type": "half_float"
                            }
                        }
                    },
                    "host": {
                        "type": "text",
                        "norms": false,
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "message": {
                        "type": "text",
                        "norms": false
                    },
                    "offset": {
                        "type": "long"
                    },
                    "prospector": {
                        "properties": {
                            "type": {
                                "type": "text",
                                "norms": false,
                                "fields": {
                                    "keyword": {
                                        "type": "keyword",
                                        "ignore_above": 256
                                    }
                                }
                            }
                        }
                    },
                    "source": {
                        "type": "text",
                        "norms": false,
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "tags": {
                        "type": "text",
                        "norms": false,
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    }
                }
            }
        },
        "settings": {
            "index": {
                "refresh_interval": "5s",
                "number_of_shards": "5",
                "provided_name": "logstash-2018-06",
                "creation_date": "1527913627481",
                "number_of_replicas": "1",
                "uuid": "0VidmJtrT1uoz-TygNCn_Q",
                "version": {
                    "created": "6020499"
                }
            }
        }
    }
}

Type,類型:在6.0.0以前的版本中,類型用於在索引中進行二次分類/分區以便在相同索引中存儲不一樣類型的文檔,好比同時存儲blog和user,這個版本開始,不能再在一個索引中建立多個類型,類型是邏輯概念。
Document,文檔:文檔表明能夠被索引的基本單元,相似數據庫裏的表,文檔在ES中表示爲JSON,在索引/類型中,能夠存儲無限的文檔。從物理上來講,文檔存儲在索引中,從邏輯上,文檔實際上歸屬於索引中的某個類型。一個典型的文檔以下:node

{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}

其實就是一個json。文檔由 _index 、 _type 和 _id 惟一標識一個文檔。mysql

Near Realtime (NRT),準實時:Elasticsearch是準實時搜索平臺(官方稱秒級)。
Cluster,集羣:全部Elasticsearch節點都運行在集羣中,即便只有一個節點,集羣中能夠包含的節點數不限,節點是物理概念,集羣是邏輯概念。
Node,節點:簡單地說,ES中的全部節點都是用來存儲數據的。具體地說,從功能上來看,Elasticsearch有5種類型的節點:
Data節點,雖然每一個節點均可以做爲數據節點,可是經過在elasticsearch.yml配置文件中進行以下設置,能夠限定某些節點只能做爲數據節點,設置專用數據節點可使得數據節點和Master節點隔離。git

node.data: true
node.master: false
node.ingest: falsegithub

Master節點,負責管理整個集羣,其持有全部節點的狀態並週期性的將集羣狀態分發給其餘全部節點,包括新加入的節點以及退出的節點。master節點的主要職責是配置管理,其包含完整的元數據以及全部索引的映射,當更新密集時,狀態信息可能會很是大,每秒1w條數據時,狀態信息可能有幾十M。2.0版本以後,更新的集羣狀態信息只發diff,而且是被壓縮的。若是master節點掛了,新的主節點會從剩餘有資格的主節點中產生,默認狀況下,每一個節點均可以成爲主節點。經過在elasticsearch.yml配置文件中進行以下設置,能夠限定某些節點只能做爲主節點。sql

node.data: false
node.master: true
node.ingest: false數據庫

Ingest節點,能夠在實際索引前執行預處理數據。經過在elasticsearch.yml配置文件中進行以下設置,能夠限定某些節點只能做爲Ingest節點。json

node.data: falseapi

node.master: false
node.ingest: true

Tribe節點,是一種特殊的協調節點,或者成爲聯邦節點,用於代理執行跨集羣的操做。

Coordinating/Client節點,在ES中,每一個請求的執行分爲兩個步驟:分發和聚合。這兩個均由接收請求的協調節點管理,同時他們也是ES集羣的負載均衡器。經過在elasticsearch.yml配置文件中進行以下設置,能夠限定某些節點只能做爲協調節點,對於較大型的生產系統,應該配置專門的協調節點,由於聚合節點比較消耗資源。

node.data: false
node.master: false
node.ingest: false

Routing,路由:路由容許咱們在索引和搜索數據時選擇具體的分片。
Lucene:提供了基於java的索引和搜索技術,Solr相似於ES,也是基於Lucene Core,提供了管理接口。

Shards & Replicas,分片與副本:一個索引包含的文檔在存儲的時候不必定是一塊兒的,存儲文檔的每一個物理單元稱爲分片(Elasticsearch 沒有采用節點級別的主從複製,而是基於分片,跟couchbase同樣)(相似於數據庫的分區以及段Partition/Segment)。分片的做用無非兩個:

  1. 容量擴展;
  2. 並行操做;

  爲了保證高可用,每一個分片能夠設置副本數量。在索引建立的時候,能夠指定分片和副本的數量,副本數能夠按需調整可是分片數量一經定義、沒法修改。默認狀況下,Elasticsearch爲每一個索引分配5個分片和1個副本(5個主分片,5個副本分片),這意味着雙節點模式。

  文檔根據公式shard = hash(routing) % number_of_primary_shards計算存儲的具體分片,默認是根據文檔的id計算,可自定義。

  每一個Elasticsearch分片是一個Lucene索引/實例(須要注意,Lucene的概念和ES不徹底一一對應),一個Lucene索引中文檔的數量限制爲Integer.MAX_VALUE - 128,這一點須要注意不要超出,分片中文檔的數量能夠經過API _cat/shards監控。

       索引和分片的關係以下:

索引和分片在存儲的組織上能夠看得出關係(經過api也能夠看出):

 

Analysis,分析:ES是一個全文搜索引擎,爲了高性能,它會提早或者按需將文本轉換爲一系列的符號,ES在索引時執行分析。對於全文檢索,ES還會對查詢字符串執行搜索時分析。
Mapping(6.0.0開始準備移除,見Type),映射:映射定義了文檔及其包含的字段如何打分和索引的過程,包括:哪一個字段應該用於全文檢索,哪些字段包含日期、數字以及地理信息等。
Mapping Type(6.0.0開始準備移除,見Type,移除的緣由是由於ES早期的假設不正確,一開始是假設Index是database,type是表,而數據庫裏面,不一樣表中的列是無關的,而Lucene的內部實現則認爲是一個字段,因此就懵逼了),映射類型:每一個索引都有一個映射類型,定義文檔如何索引。一個映射類型包含了下列信息:

  • 元字段:用於自定義如何處理文檔相關的元數據,元字段包括_index(文檔所屬的索引), _type(文檔的映射類型), _id(文檔ID)以及_source(表明文檔主體的原始JSON),完整的元數據字段請參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-fields.html。
  • 字段/屬性:映射類型包含了屬於文檔的字段/屬性列表。須要注意的是,ES文檔的每一個字段也有數據類型,可是隻有text類型纔會被全文檢索,參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/text.html。

  除了在建立索引的時候聲明映射類型外,ES還支持動態映射,也就是建立文檔的時候自動建立索引、映射類型以及字段(注:生產中應避免使用動態映射特性以最大化性能和存儲利用率)。
Mapping parameter,映射參數:對於每種數據類型,咱們能夠爲此聲明必定的映射參數,有些參數能夠應用於全部數據類型,有些則適用於部分數據類型。好比用於文本分析的分析器就是一個映射參數,完整的映射參數能夠參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-params.html。

===================================

Elasticsearch基於Java編寫,最新版本6.2.x要求Java 8。

ES提供REST API給用戶用於平常管理,端口是9100,不過通常狀況下,咱們都會安裝插件Elasticsearch Head,具體安裝能夠參考https://www.cnblogs.com/zhjh256/p/9126144.html。雖然Head不錯,可是有時候現成的環境並無安裝Head,又須要及時排查問題,此時就須要對常見的api熟悉,能夠參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/_exploring_your_cluster.html。

Elasticsearch REST APIs的規範

全部的Elasticsearch REST APIs都是JSON格式做爲出入參,這意味着必備Postman,友好強大。一些通用的參數能夠參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/api-conventions.html

Elastic出品的全部產品幾乎都包含三個配置文件:XXX.yml,jvm.options,log4j2.properties。Elasticsearch也遵照相同的慣例。
elasticsearch.yml中最重要的包括:

  • path.data和path.logs這兩個配置,logs中除了記錄ES自己的一些日誌信息外,還記錄搜索異常的日誌好比模式沒法解析。
  • cluster.name:標識一個集羣,默認爲elasticsearch,通常應該更改,好比對於elk日誌平臺,能夠爲logger-dev。
  • node.name:節點名,通常來講建議和hostname相同,hostname不該該隨機,而是相似elk-es-1便於更好地管理。
  • network.host:綁定的網卡,爲了方便且網絡流量管控不是特別嚴格的話,能夠直接註釋掉,也就是任何網卡,默認是本地迴環地址,這不管開發仍是生產環境都不適用。
  • discovery.zen.ping.unicast.hosts:ES使用了一種稱爲Zen Discovery的自定義搜索來尋找集羣節點和master選舉。默認狀況下,ES僅查找本機迴環地址下的9300~9305端口,這在生產中是不適用的,具體可見https://www.elastic.co/guide/en/elasticsearch/reference/6.2/discovery-settings.html。
  • discovery.zen.minimum_master_nodes:設置每一個有資格做爲master的節點集羣中應至少有多少個可見的有資格做爲master的節點時才組羣。爲了防止腦裂,通常是N/2+1,通用集羣作法(oracle/mysql同)。

jvm.options最主要的是-Xms和-Xmx以及gc、oom、線程池(對於全部的java應用來講,這幾方面都是必須事先預防的)。

elasticsearch.yml的全部配置參數能夠沒有一個統一的地方定義全部,整體在https://www.elastic.co/guide/en/elasticsearch/reference/2.3/setup-configuration.html,索引相關的在https://www.elastic.co/guide/en/elasticsearch/reference/2.3/index-modules.html,分析器相關的在https://www.elastic.co/guide/en/elasticsearch/reference/2.3/analysis.html。注:最新版本的沒有去掉了analyse配置的文檔,故列出了2.x版本的參考。

注意事項

JDK 8u40以前的版本G1GC有bug,會致使索引損壞。

分析(Analysis)

分析是將文本轉化爲符號的過程,在文檔被索引的時候,內置的english分析器會執行分析。任何一個mapping中的text字段均可以聲明本身的分析器,好比:

  "mappings": {
    "_doc": {
      "properties": {
        "text": { 
          "type": "text",
          "fields": {
            "english": { 
              "type":     "text",
              "analyzer": "english"
            }
          }
        }
      }
    }
  }

 

默認狀況下,ES會使用索引建立時設置的default分析器,若是該分析器無效或者不存在的話,使用標準分析器(https://www.elastic.co/guide/en/elasticsearch/reference/6.2/analysis-standard-analyzer.html)。ES提供了一些內置的分析器,見https://www.elastic.co/guide/en/elasticsearch/reference/6.2/analysis-analyzers.html。對於中文而言,這並不合適,所以社區開發了elasticsearch-analysis-ik(中文詞語)和https://github.com/medcl/elasticsearch-analysis-pinyin(用於漢字轉拼音)。

自定義分析器的建立以及規範參考https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html。

自定義分析器的安裝須要在elasticsearch.yml中進行配置,具體可參考https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html。

索引API

ES索引和搜索機制

默認狀況下,當有個文檔須要索引時,ES會計算文檔ID的哈希值,基於該哈希值選擇存儲的分片,而後這些文檔被複制到副本分片。可是查詢的時候,因爲查詢條件並不是都是ID等值查詢,因此不知道哪一個分片中實際存儲了匹配的文檔,所以須要查詢全部的分片,處理客戶端請求的節點成爲協調節點。以下:

經過API能夠看到每一個索引的路由信息,以下:

 

不少時候,在全文搜索的時候,咱們會限制僅在某個用戶下,好比僅搜索www.cnblogs.com/zhjh256下的文檔,此時路由選擇就發揮做用了。提供路由值最簡單的方式就是在HTTP請求中增長routing參數。即便是最高效的哈希查找,若是能夠確保每次搜索時限定具體分片,理論上默認狀況下吞吐量就能夠提高5倍。

ES REST API提供了一批接口用於管理索引、索引的配置、別名、映射以及索引模板,索引內部信息的監控等。從純粹使用的角度來講,索引API使用較少,因此它更多地屬於管理類API。完整的索引API能夠參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/indices.html。建立一個索引,如:

PUT test
{
    "settings" : {
        "number_of_shards" : 1
    },
    "mappings" : {
        "type1" : {
            "properties" : {
                "field1" : { "type" : "text" }
            }
        }
    }
}

建立索引的時候映射類型不是必須的。若是沒有定義映射類型,建立文檔的時候會自動根據api中的類型名進行建立。

有些時候,咱們發現有些文本中有的內容,咱們查詢的時候並不匹配,此時能夠針對具體的文本執行手工分析過程,看下ES是如何解析的,這個時候可使用_analyze API(postman怎麼發送參數格式爲JSON的get?),如:

GET _analyze
{
  "tokenizer" : "keyword",
  "filter" : ["lowercase"],
  "text" : "this is a test"
}

索引模板顧名思義,就是爲索引定義默認設置,在建立索引的時候自動套上去,由於索引自己的建立是很少的,因此無關緊要。

cat APIs

對於程序來講,JSON很適合自動化處理,可是對人類而言,其友好性就差了不少。因此,對於管理型的查詢API,ES提供了平面化的接口。例如,查看集羣中節點的狀態:

GET /_cat/nodes?v

返回以下:

ip            heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.1.104           56          94   1    0.23    0.33     0.24 mdi       *      node-1

更多cat APIs,可參考https://www.elastic.co/guide/en/elasticsearch/reference/6.2/cat.html。

文檔APIs

ES的數據複製模型

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docs-replication.html

文檔接口分爲單文檔接口和多文檔接口。

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docs.html

index api,插入或者更新一個JSON文檔,並使其可搜索。例如,在twitter索引的_doc類型下建立一個id爲1的文檔。

PUT twitter/_doc/1
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}

若是_doc類型不存在,會自動建立。若是已經存在了一個叫作_type的類型,則會建立失敗,提示不容許包含多個類型。

查詢和搜索類接口

ES提供了兩種查詢接口:搜索APIs和Query DSL。準確的說,前者能作的事情,後者都能作,前者和文檔APIs等一塊兒適合於比較簡單的CRUD。

搜索APIs

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/search.html

Query DSL

http://www.querydsl.com/

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/query-dsl.html

Elasticsearch與Solr的適用場景比較可參考:https://www.cnblogs.com/simplelovecs/articles/5129276.html

 

ES底層原理

動態更新索引

倒排索引(Lucene中的段)被寫入磁盤後是 不可改變 的:它永遠不會修改
es增長新的補充索引來反映新近的修改,而不是直接重寫整個倒排索引。每個倒排索引都會被輪流查詢到—從最先的開始–查詢完後再對結果進行合併

近實時搜索

按段(per-segment)搜索的發展
新段會被先寫入到文件系統緩存,稍後再被刷新到磁盤,只要文件已經在緩存中, 就能夠像其它文件同樣被打開和讀取了。

持久化變動

每一次對 Elasticsearch 進行操做時均記錄事務日誌,當 Elasticsearch 啓動的時候,而且會重放 translog 中全部在最後一次提交後發生的變動操做。

段合併

爲節省資源,提升檢索效率,Elasticsearch經過在後臺進行段合併,小的段被合併到大的段,而後這些大的段再被合併到更大的段。
經過optimize API能夠將一個分片強制合併到指定的段數目。 (一般減小到一個)。例如在日誌這種用例下,天天、每週、每個月的日誌被存儲在一個索引中。 老的索引實質上是隻讀的;它們也並不太可能會發生變化。

集羣擴容

 按集羣節點來均衡分配這些分片,從而對索引和搜索過程進行負載均衡,複製每一個分片以支持數據冗餘,從而防止硬件故障致使的數據丟失

當集羣只有一個節點,到變成2個節點,3個節點時的 shard 變換圖以下:

 

ES的節點加入與退出機制

分佈式系統的一個要求就是要保證高可用。前面描述的退出流程是節點主動退出的場景,但若是是故障致使節點掛掉,Elasticsearch 就會主動allocation。但若是節點丟失後馬上allocation,稍後節點恢復又馬上加入,會形成浪費。Elasticsearch的恢復流程大體以下:
  1. 集羣中的某個節點丟失網絡鏈接
  2. master提高該節點上的全部主分片的在其餘節點上的副本爲主分片
  3. cluster集羣狀態變爲 yellow ,由於副本數不夠
  4. 等待一個超時設置的時間,若是丟失節點回來就能夠當即恢復(默認爲1分鐘,經過 index.unassigned.node_left.delayed_timeout 設置)。若是該分片已經有寫入,則經過translog進行增量同步數據。
  5. 不然將副本分配給其餘節點,開始同步數據。
  6. 但若是該節點上的分片沒有副本,則沒法恢復,集羣狀態會變爲red,表示可能要丟失該分片的數據了。

除了官方文檔外,還有一些資料對於學習ES是有幫助的,這裏列舉以下:

  • Elasticsearch in Action
  • Elasticsearch可擴展的開源彈性搜索解決防範
  • Mastering Elasticsearch 5.x - Third Edition
  • 深刻理解ElasticSearch
  • Elasticsearch The Definitive Guide(中文Elasticsearch 權威指南),能夠做爲較全的參考,沒有很好的組織結構
  • https://www.jianshu.com/p/17ba43cbc3ee
  • Anatomy of an Elasticsearch Cluster: Part I

  • Anatomy of an Elasticsearch ClusterPart II

相關文章
相關標籤/搜索