原文: Elasticsearch初探
date: 2018-01-01 12:39:04
[TOC]
前言
最近這個好火, 簡單體驗下
一: 安裝和啟動
ElasticSearch下載
下載或clone后解壓
單實例節點啟動
# cd elasticsearch目錄下
bin/elasticsearch
bin/elasticsearch -d # 后臺啟動
默認端口9200, 啟動完成后訪問http://ip:9200
即可查看到節點信息
啟動中我遇到兩個錯誤:
錯誤一:
can not run elasticsearch as root
# 不能以root用戶啟動
[root@01 bin]# groupadd xiefy
[root@01 bin]# useradd xiefy -g xiefy -p 123123
[root@01 bin]# chown -R xiefy:xiefy elasticsearch
錯誤二:
ERROR: [2] bootstrap checks failed
[1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]
[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
-- 錯誤[1]: max file descriptors過小
-- 錯誤[2]: max_map_count過小, max_map_count文件包含限制一個進程可以擁有的VMA(虛擬內存區域)的數量,系 統默認是65530,修改成262144
# 切換到root用戶
vi /etc/security/limits.conf
# 添加如下
* soft nofile 65536
* hard nofile 65536
# 切換到root用戶
vi /etc/sysctl.conf
# 添加如下
vm.max_map_count=655360
# 重新加載配置文件或重啟
sysctl -p # 從配置文件“/etc/sysctl.conf”加載內核參數設置
elasticsearch-head插件安裝
elasticsearch-head 是一個用于瀏覽Elastic Search集群并與之進行交互(操作和管理)的web界面
GitHub: https://github.com/mobz/elasticsearch-head
要使用elasticsearch-head, 需要具備nodejs環境:
nodejs安裝
安裝方式有多種, 我用源碼安裝方式(包含npm)
-
下載源碼:
https://nodejs.org/dist/v8.9.4/node-v8.9.4.tar.gz
-
解壓源碼:
tar xzvf node-v* && cd node-v*
-
安裝必要的編譯軟件
sudo yum install gcc gcc-c++
-
編譯
./configure make
-
編譯&安裝
sudo make install
-
查看版本
node --version npm -version
下載或克隆elasticsearch-head
后, 進入elasticsearch-head-master
目錄:
-
npm install
速度太慢可以使用淘寶鏡像:
npm install -g cnpm --registry=https://registry.npm.taobao.org
npm run start
這時可以訪問到頁面, 并沒有監聽到集群.
解決head插件和elasticsearch之間訪問跨域問題.
修改elasticsearch目錄下的elasticsearch.yml
# 加入以下內容
http.cors.enabled: true
http.cors.allow-origin: "*"
然后: http://localhost:9100/ 即可訪問到管理頁面.
分布式安裝啟動
elasticsearch的橫向擴展很容易: 這里建立一個主節點(node-master), 兩個隨從節點(node-1, node-2)
我提前拷貝了三個es:
[xiefy@01 elk]$ ll
total 33620
-rw-r--r-- 1 root root 33485703 Aug 17 22:42 elasticsearch-5.5.2.tar.gz
drwxr-xr-x 7 root root 4096 Jan 8 11:17 elasticsearch-head-master
drwxr-xr-x 9 xiefy xiefy 4096 Jan 8 10:15 elasticsearch-master
drwxr-xr-x 9 xiefy xiefy 4096 Jan 8 14:13 elasticsearch-node1
drwxr-xr-x 9 xiefy xiefy 4096 Jan 8 14:16 elasticsearch-node2
-rw-r--r-- 1 root root 921421 Jan 8 11:14 master.zip
分別配置三個es目錄中的config/elasticsearch.yml
node-master:
cluster.name: xiefy_elastic
node.name: node-master
node.master: true
network.host: 0.0.0.0
# 除此之外, head插件需要連接到port: 9200的節點上, 還需要這個配置,
# 用來允許 elasticsearch-head 運行時的跨域
http.cors.enabled: true
http.cors.allow-origin: "*"
node-1:
cluster.name: xiefy_elastic
node.name: node-1
network.host: 0.0.0.0
http.port: 9201
discovery.zen.ping.unicast.hosts: ["127.0.0.1"]
node-2: 參考node-1
相關配置解釋:
cluster.name
: 集群名稱, 默認是elasticsearchnode.name
: 節點名稱, 默認隨機分配node.master
: 是否是主節點, 默認情況下可不寫, 第一個起來的就是Master, 掛掉后會重新選舉Master-
network.host
: 默認情況下只允許本機通過localhost或127.0.0.1訪問, 為了測試方便,我需要遠程訪問所以配成了
0.0.0.0
http.port
: 默認為9200, 同一個服務器下啟動多個es節點, 默認端口號會從9200默認遞增1, 這里我手動指定了-
discovery.zen.ping.unicast.hosts: ["host1", "host2"]
Elasticsearch默認使用服務發現(Zen discovery)作為集群節點間發現和通信的機制, 當啟動新節點時,通過這個ip列表進行節點發現,組建集群.
分別啟動三個es實例和head插件:
訪問http://ip:9100
:
二: 基礎概念
ElasticSearch與關系型數據庫的一些術語比較
關系型數據庫 | Ela |
---|---|
Database | Index |
Table | Type |
Row | Document |
Column | Field |
Schema | Mapping |
Index | Everything is indexed |
SQL` | Query DSL |
select * from table... | GET http://... |
update tables set... | PUT http://... |
Server:
- Node: 一個server實例
- Cluster:多個node組成cluster
- Shard:數據分片,一個index可能會有多個shards,不同shards可能在不同nodes
- Replica:shard的備份,有一個primary shard,其余的叫做replica shards
- Gateway:管理cluster狀態信息
Shards & Replicas
副本很重要, 主要有兩個原因:
- 它在分片/節點發生故障時來保障高可用性, 因此, 副本分片永遠不會和主/原始分片分配在同一個節點中
- 它允許擴展搜索量和吞吐量, 因為可以在所有副本上并行執行搜索
可以在創建索引時為索引定義分片和副本的數量。
創建索引后,可以隨時動態更改副本數,但不能再更改分片數。
每個Elasticsearch分片都是Lucene索引, 在一個Lucene上的最大文檔數量大約21億(
Integer.MAX_VALUE-128
)
THE REST API:
elasticsearh提供了豐富的REST API來與集群交互, 這些API包括:
- 檢查你的集群,節點和索引的健康情況,狀態和統計
- 管理你的集群,節點,索引數據和metadata
- 針對索引執行CURD(創建,讀取,更新,刪除)和搜索操作
- 執行高級的搜索操作,例如分頁,排序,過濾,聚合,腳本等等。
通過_cat
可以查看到很多資源:
curl -XGET 'localhost:9200/_cat'
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates
偷偷看一眼集群健康狀態: curl -XGET 'localhost:9200/_cat/health?v'
節點的信息: curl -XGET 'localhost:9200/_cat/nodes?v&pretty'
而關于和集群索引和文檔的交互, 放在下一章.
三: 基礎用法
索引創建
方式一: head插件可以直接新建/刪除/查詢索引(Index)
-
方式二: 通過rest api
# 新建 curl -X PUT 'localhost:9200/book' # 刪除 curl -X DELETE 'localhost:9200/book' # 查看當前節點下所有Index curl -X GET 'http://localhost:9200/_cat/indices?v'
這樣創建的Index是沒有結構的. 可以看到索引信息的mappings:{}
下面來定義一個有結構映射的Index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"man": {
"properties": {
"name": {"type": "text"},
"country": {"type": "keyword"},
"age": {"type": "integer"},
"birthday": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
},
"women": {
}
}
}
里面設置了分片數, 備份數, 一個Index和兩個type的結構映射.
插入數據
PUT http://47.94.210.157:9200/people/man/1
指定ID為1
{
"name": "伊布",
"country": "瑞典",
"age": 30,
"birthday": "1988-12-12"
}
如果不指定ID, 會生成為隨機字符串, 此時需要改為POST方式 POST http://47.94.210.157:9200/people/man
神奇的一點, es不會限制你在創建一個文檔之前索引和類型必須存在, 不存在時, es會自動創建它.
更新數據
POST http://47.94.210.157:9200/people/man/1/_update -- 修改ID為1的文檔
{
"doc": {
"name": "梅西梅西很像很強"
}
}
還可以通過腳本方式更新:
{ "script": "ctx._source.age += 10" }
這里是把年齡加10, ctx.source
引用了當前文檔.
索引/替換文檔
PUT http://47.94.210.157:9200/football/man/1 -- 修改ID為1的文檔
{"name": "伊布asdasadasds3333333333333333sd"}
elasticsearch將會用一個新的文檔取代(即重建索引)舊的文檔
刪除數據
刪除文檔: DELETE http://47.94.210.157:9200/people/man/1
查看數據
- 根據ID查詢
{
"_index": "people",
"_type": "man",
"_id": "2",
"_version": 1,
"found": true,
"_source": {
"name": "伊布",
"country": "瑞典",
"age": 34,
"birthday": "1972-12-12"
}
}
found
字段表示查到與否
?
- 查詢全部
或帶排序帶分頁的查詢:
ES 默認 從0開始(from), 一次返回10條(size), 并按照_score字段倒排, 也可以自己指定
# 帶排序帶分頁的查詢
{
"query": { "match_all": {} },
"sort": [{
"birthday": {"order": "desc"}
}
],
"from": 0,
"size": 5
}
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "people",
"_type": "man",
"_id": "2",
"_score": 1,
"_source": {
"name": "伊布",
"country": "瑞典",
"age": 34,
"birthday": "1972-12-12"
}
},
....
....
]
}
}
# 返回結果解釋
- took: 耗時(單位毫秒)
- timed_out: 是否超時
- hits: 命中的記錄數組
- total: 返回的記錄數
- max_score: 最高匹配度分數
- hits: 記錄數組
- _score: 匹配度
- 關鍵字查詢
{
"query": {
"match": {"name": "梅西"}
}
}
注意: 這里是模糊匹配查詢, 例如查詢的值是"西2", 那么會查詢所有記錄name有"西"和name有"2"的.
關于查詢多個關鍵字之間的邏輯運算:
如果這樣寫, 兩個關鍵字會被認為是 or
的關系來查詢
{
"query": {
"match": {"name": "西 布"}
}
}
如果是and
關系來搜索, 需要布爾查詢
{
"query": {
"bool": {
"must": [
{"match": {"name": "西"}} ,
{"match": {"name": "2"}}
]
}
}
}
聚合查詢
- 分組聚合
{
"aggs": {
"group_by_age": {
"terms": {"field": "age"}
}
}
}
返回結果中, 除了有hits數組, 還有聚合查詢的結果:
在單個請求中就可以同時查詢數據和進行多次聚合運算是非常有意義的, 他可以降低網絡請求的次數
# 聚合查詢結果
"aggregations": {
"group_by_age": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 24,
"doc_count": 2
},
{
"key": 32,
"doc_count": 1
}
]
}
}
支持多個聚合, 聚合結果也會返回多個:
{
"aggs": {
"group_by_age": {
"terms": {"field": "age"}
},
"group_by_age": {
"terms": {"field": "age"}
}
}
}
- 其他功能函數
{
"aggs": {
"tongji_age": {
"stats": {"field": "age"}
}
}
}
stats
指定計算字段, 返回結果包括了總數, 最小值, 最大值, 平均值和求和
"aggregations": {
"tongji_age": {
"count": 3,
"min": 24,
"max": 32,
"avg": 26.666666666666668,
"sum": 80
}
}
也可指定某種類型的計算
{
"aggs": {
"tongji_age": {
"sum": {"field": "age"}
}
}
}
返回結果
"aggregations": {
"tongji_age": {
"value": 80
}
}
四: 高級查詢
分為子條件查詢和復合條件查詢:
類型:
- 全文本查詢: 針對文本類型數據
- 字段級別查詢: 針對結構化數據, 如日期, 數字
文本查詢
- 模糊匹配
{
"query": {
"match": {"name": "西2"}
}
}
- 短語匹配
{
"query": {
"match_phrase": {"name": "西2"}
}
}
- 多個字段匹配
{
"query": {
"multi_match": {
"query": "瑞典",
"fields": ["name", "country"]
}
}
}
語法查詢: 根據語法規則查詢:
- 帶有布爾邏輯的查詢
{
"query": {
"query_string": {
"query": "(西 AND 梅) OR 布"
}
}
}
- query_string 查詢多個字段
{
"query": {
"query_string": {
"query": "西梅 OR 瑞典",
"fields": ["country", "name"]
}
}
}
結構化數據查詢
{
"query": {"term": { "age": 24}}
}
- 帶范圍的查詢
{
"query": {
"range": {
"birthday": {
"gte": "1980-01-01",
"lte": "now"
}
}
}
}
子條件查詢
Filter Context: 用來做數據過濾, 在查詢過程中, 只判斷該文檔是否滿足條件(y or not)
Filter和Query的區別?
Filter要結合bool使用, 查詢結果會放入緩存中, 速度較Query更快
{
"query": {
"bool": {
"filter": {
"term": { "age": 24 }
}
}
}
}
復合查詢
- 固定分數查詢
{
"query": {
"match": {"name": "梅西"}
}
}
可以看到查詢的結果, 每條數據的_score
不同, 代表了與查詢值的匹配程度的分數.
但查詢不一定都需要產生文檔得分,特別在過濾文檔集合的時候。為了避免不必要的文檔得分計算,Elasticsearch會檢查這種情況并自動的優化這種查詢。
例如在bool
查詢中返回年齡范圍在20-50的文檔, 對我來說這個范圍內的文檔都是等價的, 即他們的相關度都是一樣的(filter子句查詢,不會改變得分).
{
"query": {
"constant_score": {
"filter": {
"match": {"name": "梅西"}
},
"boost": 2
}
}
}
可以看到查詢結果, 每條數據的_score
都為2, 如果不指定boost
則默認為1
- 布爾查詢
{
"query": {
"bool": {
"should": [
{ "match": {"name": "梅西"}},
{ "match": {"country": "阿"}}
]
}
}
}
這里兩個match之間是或的邏輯關系. should
如果改為 must
代表與邏輯.
我們也可以把must
,should
,must_not
同時組合到bool
子句。此外,我們也可以組合bool
到任何一個bool
子句中,實現復雜的多層bool
子句嵌套邏輯。
再加一層Filter, 只有age=32的能返回
{
"query": {
"bool": {
"must": [
{ "match": {"name": "梅西"} },
{ "match": {"country": "阿根廷"}}
],
"filter": [{
"term": {
"age": 32
}
}]
}
}
}
country=阿根廷的不返回:
{
"query": {
"bool": {
"must_not": {
"term": {"country": "阿根廷"}
}
}
}
}
五: 關于中文分詞
為什么需要中文分詞?
首先看一下默認的分詞規則.
# 英文
GET http://47.94.210.157:9200/_analyze?analyzer=standard&pretty=true&text=hello world, elasticsearch
{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "world",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "elasticsearch",
"start_offset": 13,
"end_offset": 26,
"type": "<ALPHANUM>",
"position": 2
}
]
}
可以看到, 英文的默認分詞是根據標點符號和空格默認來分的.
再看看中文的:
GET http://47.94.210.157:9200/_analyze?analyzer=standard&pretty=true&text=你好,啊
{
"tokens": [
{
"token": "你",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "好",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "啊",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 2
}
]
}
可以看到ES對中文的分詞并不智能, 是將漢字全部分開了, 所以引入中文分詞.
IK
IK: https://github.com/medcl/elasticsearch-analysis-ik
The IK Analysis plugin integrates Lucene IK analyzer into elasticsearch, support customized dictionary.
安裝
1.download or compile
-
optional 1 - download pre-build package from here: https://github.com/medcl/elasticsearch-analysis-ik/releases
unzip plugin to folder
your-es-root/plugins/
-
optional 2 - use elasticsearch-plugin to install ( version > v5.5.1 ):
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.1/elasticsearch-analysis-ik-6.2.1.zip
2.restart elasticsearch
兩種安裝方式, 任選其一, 注意版本就好
Github里有Quick Example
可以看下怎么使用
需要在建立索引時指定ik分詞器, 建立索引和搜索索引字段都需要指定, 例如:
"analyzer": "ik_max_word"
和"search_analyzer": "ik_max_word"
IK提供兩種分詞規則:
- ik_max_word: 會將文本做最細粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;
- ik_smart: 會做最粗粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。
除此之外, IK也支持擴展自定義詞典, 以及熱更新.
# Test
GET http://47.94.210.157:9200/_analyze?analyzer=ik_max_word&pretty=true&text=你好,啊
{
"tokens": [
{
"token": "你好",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "啊",
"start_offset": 3,
"end_offset": 4,
"type": "CN_CHAR",
"position": 1
}
]
}
六: Spring Boot 集成 Elastic Search
版本參考
Spring Boot Version (x) | Spring Data Elasticsearch Version (y) | Elasticsearch Version (z) |
---|---|---|
x <= 1.3.5 | y <= 1.3.4 | z <= 1.7.2* |
x >= 1.4.x | 2.0.0 <=y < 5.0.0** | 2.0.0 <= z < 5.0.0** |
服務器集群ES版本 | 5.5.2 |
---|---|
Spring boot | 1.5.9.RELEASE |
Elastic Search | 5.5.2 |
log4j-core | 2.7 |
集成步驟
-
引入Maven依賴:
<properties> <log4j-core.version>2.7</log4j-core.version> <elasticsearch-version>5.5.2</elasticsearch-version> </properties> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>${elasticsearch.version}</version> </dependency> <!-- <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${elasticsearch-version}</version> </dependency> -->
注意: transport中依賴了elasticsearch, 但默認是
2.4.6
版本, 需要指定下elasticsearch的版本5.5.2
-
也可以直接引入:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
但是
spring-boot-starter-data-elasticsearch
只支持到2.4.x
版本的es.如果使用
5.x.x
版本ES, 就用上面那種方式單獨引入ES依賴.
?
-
添加配置類
@Configuration public class ElasticSearchConfig { /** 集群host */ @Value("${spring.data.elasticsearch.cluster-nodes}") private String clusterNodes; /** 集群名稱 */ @Value("${spring.data.elasticsearch.cluster-name}") private String clusterName; @Bean public TransportClient client() throws UnknownHostException{ InetSocketTransportAddress node = new InetSocketTransportAddress( InetAddress.getByName(clusterNodes), 9300 ); Settings settings = Settings.builder().put("cluster.name", clusterName).build(); TransportClient client = new PreBuiltTransportClient(settings); client.addTransportAddress(node); return client; } }
application.properties
中配置:spring.data.elasticsearch.cluster-nodes=xxx
spring.data.elasticsearch.cluster-name=xxx
測試用例
簡單的CRUL操作:
@Link com.thank.elasticsearch.TestElasticSearchCRUD.java