Elastic Search
ElasticSearch是一個基於Lucene的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。Elasticsearch是用Java語言開發的,並作爲Apache許可條款下的開放源碼發佈,是一種流行的企業級搜索引擎。ElasticSearch用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其他語言中都是可用的。根據DB-Engines的排名顯示,Elasticsearch是最受歡迎的企業搜索引擎,其次是Apache Solr,也是基於Lucene。
一、安裝
- 上官網下載最新版本的Elastic Search的tar.gz包,解壓。
- es有一個安全性的規則,不能使用root用戶啓動它,所以我們要先創建一個屬於es的用戶組,在其下創建一個用戶
groupadd esg
useradd -g esg eshui
passwd esg
New passwd: xxxxx
Repeat passd: xxxxx
爲了以後操作的方便,最好直接把eshui的權限修改爲root權限(修改/etc/sudoers)
- 用eshui重新登陸,進入es的解壓目錄,執行 ./bin/elasticsearch即可運行
- 用 curl 127.0.0.1:9200 命令訪問es的默認端口,如果返回一串json,則啓動成功。
二、修改配置文件以及修改limit
- 修改./conf/elasticsearch.yml,作如下修改
network.host: 192.168.246.128 #這裏是自己主機的IP地址
http.port: 9200
node.name: node-1
cluster.initial_master_nodes: ["node-1"]
- elasticsearch啓動需要對centos下的一些limit進行修改 sudo vi /etc/security/limits.conf
# the end
eshui soft nofile 65535
eshui hard nofile 65536
eshui soft nproc 4096
eshui hard nproc 4096
- sudo vi /etc/security/limits.d/20-nproc.conf
# 把 * 改成eshui
eshui soft nproc 4096
- sudo vi /etc/sysctl.conf
#添加如下配置,讓虛擬內存最大爲655360字節
vm.max_map_count=655360
完成上述配置後,重新登陸bash,即可成功啓動elasticsearch
三、ElasticSearch的Head插件,管理ES集羣
Head插件是一個管理ES的客戶端程序,安裝他之前,需要安裝grunt。通常使用npm安裝,所以在linux環境下,先安裝npm,自行在網上查找安裝方法。
- 安裝grunt
npm install -g grunt-cli
- 使用git clone將elasticsearch-head的源碼克隆下來。
git clone git://github.com/monbz/elasticsearch-head.git
-
clone之後,進入主目錄,執行cnpm install ,安裝該es head項目所需要的插件。(最好安裝cnpm,確保不被牆 npm install -g cnpm)
-
修改elasticsearch-head下的Gruntfile.js中的connect.->server->options, 在其中增加一項,
hostname: '*',
-
修改elasticsearch-head/_site下的app.js中的this.baseurl下的localhost:9200改成實際elasticsearch的ip地址。
-
允許跨越訪問,修改elasticsearch/config/elasticsearch.yml。 增加以下兩項。
http.cors.enabled: true
http.cors.allow-origin: "*"
-
先啓動 Elastic Search
-
進入elasticsearch-head下的node_modules/grunt/bin,執行
./grunt server
開啓es head。默認是9100端口。 -
用瀏覽器訪問9100端口,即可以看到elasticsearch的管理界面, 如下。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9kjqOmkd-1585125850571)(https://t1.picb.cc/uploads/2019/10/13/gsy9ZT.md.png)]
四、安裝ElasticSearch的圖形化管理工具Kibana
Kibana是Elastic Search的web界面管理工具,提供可視化的es管理界面,非常人性化。
- 首先在網上下載kibana的tar包,解壓
- 修改
config/kibana.yml
中的server.host爲本機的ip地址。 - 將數組
elasticsearch-hosts
內部的localhost改爲elasticsearch的ip地址+端口。 - 首先啓動elastic-search。
- 啓動kibana,./bin/kibana。
- 瀏覽器訪問,ip:5601, 即可訪問到kibana的控制檯。默認端口是5601,如下所示。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2D72rEcD-1585125850572)(https://t1.picb.cc/uploads/2019/10/22/gDfA2s.md.png)]
五、ES的索引
5.1. 倒排索引
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dVqFuO1y-1585125850573)(https://t1.picb.cc/uploads/2019/11/06/gY54eD.md.png)]
倒排索引保存了每個單詞在文檔中的存在情況。如果現在有一個需求,找到所有含有單詞quick
的文檔。如果是一般的寫法,需要將所有文檔遍歷一遍。如果有倒排索引的存在,就可以直接找到含有quick
的文檔。
在倒排索引中,key是每個單詞,而value是含有這個單詞的所有文檔的序號。
在elastic search中,會把倒排索引的key進行處理,比如dogs和dog其實是同一個意思,Quick和quick其實是同一個意思。
5.2. 分詞器介紹內置分詞器
分詞器包括三部分:
- character filter:分詞之前的預處理,過濾掉HTML標籤特殊符號等。
- tokenizer:分詞。
- token filter:標準化。
es內置分詞器:
- standard分詞器:大寫轉小寫,去除停用詞,中文的話就是單個字分詞。
- simple分詞器:過濾掉數字,以非字母字符來分割信息,然後將詞彙單元轉化成小寫形式。
- Whitepace分詞器:僅僅根據空格分詞。
- language分詞器:特定語言分詞器。
5.3. 安裝中文分詞器
爲ES安裝中文分詞器插件,首先使用git clone將中文分詞器的代碼拉下來
git clone [email protected]:medcl/elasticsearch-analysis-ik.git
然後使用maven編譯源碼
mvn clean install -Dmaven.skip.test=true
之後target文件夾下會生成一個releases文件夾,裏面有一個elasticsearch-analysis-ik的zip壓縮包。將該壓縮包拷貝到elasticsearch/plugins/ik
下,ik文件夾需要自己創建。解壓縮後,將原壓縮包刪除。中文分詞器插件配置完畢。
六、ES的基本使用(CURD)
6.1. 基本操作
下面是在Kibana中開發者工具裏執行的demo,描述了文檔的一般增刪查改。
{
"query": {
"match_all": {}
}
}
# 添加索引lib,分片數是3,備份數是0。
# 這裏類似於Kafka的配置,Kafka也有分區和備份數,和這裏是一致的
PUT /lib/
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 0
}
}
}
# 添加一個默認索引
PUT lib2
# 獲取索引配置
GET /lib/_settings
GET /lib2/_settings
# 獲取所有索引配置
GET _all/_settings
#指定id添加文檔
PUT /lib/user/1
{
"first_name":"曹",
"last_name":"輝",
"age":24,
"about":"I like LULU",
"interest":["movie"]
}
# 不指定ID的時候,用POST
POST /lib/user/
{
"first_name":"楊",
"last_name":"璐",
"age":23,
"about":"I like Caohui",
"interest":["movie"]
}
# 獲取指定id的文檔
GET /lib/user/1
GET /lib/user/ZTLbRG4BO86YojzOMVx8
# 只查詢source和about兩個字段
GET /lib/user/1?_source=age,about
# 修改
POST /lib/user/1/_update
{
"doc": {
"age":30
}
}
# 刪除文檔
DELETE /lib/user/1
# 刪除索引
DELETE lib2
6.2. 批量獲取文檔
GET /_mget
{
"docs": [
{
"_index": "lib",
"_type": "user",
"_id":"1"
},
{
"_index": "lib",
"_type": "user",
"_id":"2"
},
{
"_index": "lib",
"_type": "user",
"_id":"3"
}
]
}
# 簡化後的批量獲取
GET /lib/user/_mget
{
"docs": [
{
"_id": 1
},
{
"_type": "user",
"_id": 2
}
]
}
# 或者更簡單一點
GET /lib/user/_mget
{
"ids":["1","2","3"]
}
6.3 使用Bulk API實現批量操作
PUT lib2
# 批量添加文檔
POST /lib2/books/_bulk
{"index":{"_id":1}}
{"title":"Java", "price":55}
{"index":{"_id":2}}
{"title":"Php", "price":54}
{"index":{"_id":3}}
{"title":"C++", "price":53}
# 批量修改
POST /lib2/books/_bulk
{"delete":{"_index":"lib2", "_type":"books", "_id":3}}
{"create":{"_index":"tt", "_type":"ttt", "_id":100}}
{"name":"caohui"}
{"index":{"_index":"tt", "_type":"ttt"}}
{"name":"yanglu"}
{"update":{"_index":"lib2", "_type":"books", "_id":1}}
{"doc":{"price":128}}
POST /lib2/_bulk
{"update":{"_type":"books", "_id":1}}
{"doc":{"price":12345}}
bulk會把將要處理的數據載到內存只,所以數據量是有限制的。可以在es目錄下的config中修改。
6.4. 版本控制
ElasticSearch採用樂觀鎖的機制,當用戶對document進行操作時,只需要指定要操作的版本即可。 如果版本一致,修改,如果不一致,報錯。每次修改,文檔的_sep_no字段都會+1. (ES6以前使用version字段,之後被摒棄了)
6.4.1. 內部版本控制
GET /lib/user/1
{
"_index" : "lib",
"_type" : "user",
"_id" : "1",
"_version" : 2,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"first_name" : "曹",
"last_name" : "輝",
"age" : 30,
"about" : "I like LULU",
"interest" : [
"movie"
]
}
}
如果我想修改這個doc, 使用以下語句控制版本。
#如果我想修改這個doc
PUT /lib/user/1?if_seq_no=2?if_primary_term=1
{
"doc":{
"age":12
}
}
兩個參數
- if_seq_no : 每個doc都有_seq_no,每一次修改這個doc,這個字段都會+1。
- If_primary_term: 主分片發生改變時,比如重新選主,_primary_term會+1。
6.4.2. 外部版本控制
ElasticSearch中數據的版本很多情況下是根據外部數據庫的版本的,外不會提供給es一個版本。此時,es中的版本應該修改爲外部的版本。注意,此時外部提供的版本一定要大於es內部的版本,否則會報錯。
外部版本控制的用法如下。
PUT /lib/user/1?version=100&version_type=external
{
"first_name" : "曹",
"last_name" : "輝",
"age" : 24,
"about" : "I like LULU",
"interest" : [
"movie"
]
}
最後加了個參數 verision_type=external,表示修改之後的版本是外部提供的,但是必須比es內部的_version大。
注意:ES7以後,_version和_seq_no是不一樣的,_verison僅僅表示版本,而_seq_no表示修改次數。
6.5. ES中的_mapping
mapping指的是每個索引的每個字段,都被映射爲一種數據類型,比如/lib這個索引的mapping長下面這個樣子。
{
"lib" : {
"mappings" : {
"properties" : {
"about" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"age" : {
"type" : "long"
},
"first_name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"interest" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"last_name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
除此之外,每個類型有這不同的屬性,比如上面的text類型的ignore_above
屬性是256,這是默認值,表示這個字段最大是256個字節。還有其他很多屬性,用到再查吧。
我們可以在創建索引的時候自定義mapping。
PUT lib6
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 3
},
"mappings": {
"properties":{
"title":{"type":"text"},
"name":{"type":"text"},
"publish_time":{"type":"date", "index":false},
"price":{"type":"double"},
"number":{"type":"integer"}
}
}
}
這樣會創建一個5個屬性的索引,它的type默認是_doc
6.6. 搜索查詢
ElasticSearch中的搜索是根據分詞器的分詞處理搜索的。比如下面的搜索。
# q參數表示搜索的條件,:前面表示搜索的字段,:後面表示關鍵字
GET /lib/user/_search?q=about:like
結果是。
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 0.18232156,
"hits" : [
{
"_index" : "lib",
"_type" : "user",
"_id" : "2",
"_score" : 0.18232156,
"_source" : {
"first_name" : "曹",
"last_name" : "輝2",
"age" : 24,
"about" : "I like LULU",
"interest" : [
"movie"
]
}
},
{
"_index" : "lib",
"_type" : "user",
"_id" : "3",
"_score" : 0.18232156,
"_source" : {
"first_name" : "曹",
"last_name" : "輝3",
"age" : 24,
"about" : "I like LULU",
"interest" : [
"movie"
]
}
},
{
"_index" : "lib",
"_type" : "user",
"_id" : "1",
"_score" : 0.18232156,
"_source" : {
"first_name" : "曹",
"last_name" : "輝1",
"age" : 24,
"about" : "I like LULU",
"interest" : [
"movie"
]
}
},
{
"_index" : "lib",
"_type" : "user",
"_id" : "ZTLbRG4BO86YojzOMVx8",
"_score" : 0.18232156,
"_source" : {
"first_name" : "楊",
"last_name" : "璐",
"age" : 23,
"about" : "I like Caohui",
"interest" : [
"movie"
]
}
}
]
}
}
按照某個屬性排序的查詢
# q參數表示搜索的條件,:前面表示搜索的字段,:後面表示關鍵字
# 按照age的降序查詢about熟悉中帶有like的文檔
GET /lib/user/_search?q=about:like&sort=age:desc
6.6.1 term和terms查詢
-
term查詢,將查詢條件放入term字段。
GET /lib/user/_search { "query":{ "term":{"name":"cao"} } }
結果是lib索引中所有name中帶有cao的文檔,需要注意的是,這裏的term中只能有一個查詢條件(雖然是是{}表示的,但是不能寫成{“name”:“cao”,“age”:25})。
查詢結果如下
{ "took" : 0, "timed_out" : false, "_shards" : { "total" : 3, "successful" : 3, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : 0.2876821, "hits" : [ { "_index" : "lib", "_type" : "user", "_id" : "10", "_score" : 0.2876821, "_source" : { "name" : "cao hui", "age" : 24, "address" : { "province" : "ZheJiang", "city" : "HangZhou" } } } ] } }
-
terms查詢,和term的不同點就是查詢條件的值可以有多個,不同值之間是"或"的關係。
GET /lib/user/_search { "query":{ "terms":{ "name":["cao"] } } }
和term一樣,terms裏面的屬性也只能有一個,但是中括號裏面可以有多個值。
-
from和size
from和size是查詢的限制文檔個數,顧名思義
GET /lib/user/_search { "from":0, "size":10, "query":{ "terms":{ "name":["cao"] } } }
-
version, 查詢中,version設置爲true,則查詢結果中出現文檔的版本號。
GET /lib/user/_search { "version":"true", "from":0, "size":10, "query":{ "terms":{ "name":["cao"] } } }
-
match查詢
match查詢是帶有分詞器的查詢。下面的查詢可以查出name屬性中帶有cao和lu的所有文檔。
# 帶有分詞器的查詢 GET /lib/user/_search { "query":{ "match":{ "name":"cao,lu" } } }
下面是查詢所有文檔。
# 查詢所有文檔 GET /lib/user/_search { "query":{ "match_all":{ } } }
多項匹配,下面的查詢可以查詢出所有name或about字段帶cao的所有文檔。
# multi_match,fields包含的字段當中,都會查詢出來 GET /lib/user/_search { "query":{ "multi_match":{ "query":"cao", "fields":["name","about"] } } }
短語匹配,查詢某屬性帶有某短語的所有文檔。
# match_phrase ,必須含有完全一樣的短語 GET /lib/user/_search { "query":{ "match_phrase":{ "name":"cao hui" } } }
通過
_source
字段可以指明查出來的結果需要哪些字段,下面表示只需要name和age字段。# 通過_source 指明返回哪些字段 GET /lib/user/_search { "_source":["name", "age"], "query":{ "match_phrase":{ "name":"cao hui" } } }
6.6.2 利用ik分詞器進行中文分詞查詢
首先創建一個lib,使用的分詞器是中文分詞器。
# 新建一個lib,其中text類型使用ik_max_word中文分詞器。
PUT /mylib
{
"settings": {
"number_of_replicas": 0,
"number_of_shards": 3
},
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"address":{
"type": "text",
"analyzer": "ik_max_word"
},
"age":{
"type": "integer"
},
"interests":{
"type": "text",
"analyzer": "ik_max_word"
},
"birthday":{
"type": "date"
}
}
}
}
然後向其中增加一些帶有中文的數據。
PUT mylib/_doc/1
{
"name":"趙六",
"address":"黑龍江省鐵嶺",
"age":50,
"birthday":"1970-12-12",
"interests":"喜歡喝酒、鍛鍊、說相聲"
}
PUT mylib/_doc/2
{
"name":"趙明",
"address":"北京海淀區清河",
"age":20,
"birthday":"1998-10-12",
"interests":"喜歡喝酒,鍛鍊,唱歌"
}
PUT mylib/_doc/3
{
"name":"lisi",
"address":"北京海淀區清河",
"age":23,
"birthday":"1998-10-12",
"interests":"喜歡喝酒,鍛鍊,唱歌"
}
PUT mylib/_doc/4
{
"name":"王五",
"address":"北京海淀區清河",
"age":26,
"birthday":"1995-10-12",
"interests":"喜歡編程、聽音樂、旅遊"
}
PUT mylib/_doc/5
{
"name":"張三",
"address":"北京海淀區清河",
"age":29,
"birthday":"1988-10-12",
"interests":"喜歡攝影、聽音樂、跳舞"
}
然後就可以查詢中文了。
-
前綴短語。
GET mylib/_doc/_search { "query":{ "match_phrase_prefix":{ "interests":"喜歡" } } }
-
範圍查詢,默認是左閉右開區間。
GET mylib/_doc/_search { "query":{ "range":{ "age":{ "from":20, "to":25, "include_lower":"true", "include_upper":"false" } } } }
-
模糊查詢, 查詢某字段中帶有某些子的文檔。
GET mylib/_doc/_search { "query":{ "fuzzy":{ "interests":"鍛鍊" } } }
-
通配符*和?的查詢
GET mylib/_doc/_search { "query":{ "wildcard":{ "interests":"*鍛練*" } } }
6.6.3 過濾查詢,條件查詢
ES的過濾查詢是根據關鍵字filter,可以根據條件過濾文檔,如下所示。
#找到age是20的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"filter":{
"term":{
"age":"20"
}
}
}
}
}
#找到age是20和23的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"filter":{
"terms":{
"age":[20,23]
}
}
}
}
}
ES的條件查詢是通過關鍵字should
、must
、must_not
來實現的。
- should表示,“應該含有什麼”,如果沒有must,則查詢結果以should指定的值爲準。但是如果有must,should不起作用。
- must,必須包含的
- must_not,必須不包含的
如下所示
#bool查詢, should,must,must_not
#查詢age是20或23,且name不是caohui的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"should":[
{
"term":{
"age":20
}
},{
"term":{
"age":23
}
}
],
"must_not":
{
"term":{
"name":"caohui"
}
}
}
}
}
#查詢interests包含喝酒且name不是曹輝的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"must_not":
{
"term":{
"name":"caohui"
}
},
"must":[
{
"terms":{
"interests":["喝酒"]
}
}
]
}
}
}
範圍過濾,ES通過gt 表示 >, lt 表示 < , gte 表示 >= , lte 表示 <=, exists表示存在。例子如下。
#查詢 20 <= age <= 23 的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"filter":{
"range":{
"age":{
"gte":20,
"lte":23
}
}
}
}
}
}
# 查詢address屬性不爲null的文檔
GET mylib/_doc/_search
{
"query":{
"bool":{
"filter":{
"exists":{
"field":"address"
}
}
}
}
}
6.6.4 聚合查詢
在ES中存在聚合查詢,例子如下
# 求和,其中 sum_of_age是自己取的名字,字段名,size是0表示結果中不顯示每個文檔。
GET mylib/_doc/_search
{
"size":0,
"aggs":{
"sum_of_age":{
"sum":{
"field":"age"
}
}
}
}
# 求最大
GET mylib/_doc/_search
{
"size":0,
"aggs":{
"min_of_age":{
"min":{
"field":"age"
}
}
}
}
# 求年齡是20或23的年齡最小值
GET mylib/_doc/_search
{
"query":{
"bool":{
"filter":{
"terms":{
"age":[20,23]
}
}
}
},
"aggs":{
"max_of_age":{
"max":{
"field":"age"
}
}
}
}
# 求年齡的平均值
GET mylib/_doc/_search
{
"size":0,
"aggs":{
"avg_of_age":{
"avg":{
"field":"age"
}
}
}
}
# cardinality,相當於數據庫裏的distinct,不重複的元素個數
GET mylib/_doc/_search
{
"size":0,
"aggs":{
"card_of_age":{
"cardinality":{
"field":"age"
}
}
}
}
分組
# 分組,按照某字段進行分組
GET mylib/_doc/_search
{
"size":0,
"aggs":{
"group_of_age":{
"terms":{
"field":"age"
}
}
}
}
分組的結果是下面的樣子
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_of_age" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 23,
"doc_count" : 2
},
{
"key" : 20,
"doc_count" : 1
},
{
"key" : 26,
"doc_count" : 1
},
{
"key" : 29,
"doc_count" : 1
},
{
"key" : 50,
"doc_count" : 1
}
]
}
}
}
下面做一個練習,在興趣爲唱歌的人中,按照年齡進行分組,並且分組按照年齡降序。
GET mylib/_doc/_search
{
"size":0,
"query":{
"term":{
"interests":"唱歌"
}
},
"aggs":{
"group_of_age":{
"terms":{
"field":"age",
"order":{
"avg_of_age":"desc"
}
},
"aggs":{
"avg_of_age":{
"avg":{
"field":"age"
}
}
}
}
}
}
結果如下所示。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_of_age" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 23,
"doc_count" : 2,
"avg_of_age" : {
"value" : 23.0
}
},
{
"key" : 20,
"doc_count" : 1,
"avg_of_age" : {
"value" : 20.0
}
}
]
}
}
}