Elasticsearch基本操作


在學習Elasticsearch的過程中想找一些可以系統的描述es操作的文章,但是官網沒有中文頁面,ES中文指南的排版和翻譯又很突兀和不協調,因此決定自己看一遍官方的maunal總結一下,由於沒時間把所有章節全部翻一遍,所以寫一篇學習筆記以便完成初步的學習。

概念總覽:

在描述ES的基本操作之前,首先來介紹幾個概念:

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices   -> Types  -> Documents -> Fields

以上是早期的官方文檔貼出的一個概念介紹圖,其含義不用多說,其實ES更適合與MongoDB類比:

MongoDB       -> DBs       -> Collections -> Documents -> Fields
Elasticsearch -> Indices   -> Types       -> Documents -> Fields

ES里的Index可以看做一個庫,Documents相當於表的行,而Types相當於表。

但是Types的概念將會被逐漸弱化並可能在未來版本中刪除,而在Elasticsearch 6中,一個index下已經只能包含一個type了,因此可以將index理解為一個表,types意如其名僅用於展示一個document所屬的分類,實際上在本文對ES進行操作時由於index和type的一對一關系,許多時候查詢document已經只需要指定index而無需再指定type了。

本文使用Elasticsearch 6.5.4和Kibana 6.5.4下的環境進行演示。

一、Kibana命令行操作

使用Kibana操作ES是當前最簡單的一種方式,且提供命令補全、index名稱補全等便捷的功能。同時console界面的小扳手點進去還有和官方手冊里一樣的“copy as CURL”選擇,將選中的命令copy之后粘貼到linux中就會轉換為curl命令的格式,對於想要了解curl直接操作ES的同學是很有幫助的。

我個人並不建議直接使用curl操作ES,因為很多時候需要自己設置header,麻煩且低效。

Elasticsearch官方操作手冊地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

這里參考官網的reference手冊對內置API進行詳細梳理,由於官方手冊的介紹方式不適用於我這種新手,我只能打亂順序學習,本部分的介紹基本遵循學習傳統數據庫的流程,主要分為以下7個部分:

Note:本文所有命令都是在Kibana console操作的,關於Kibana的安裝配置和使用,參考《Kibana安裝配置》一文。

1.數據結構搭建

結構的搭建主要包含index的創建和刪除、查詢等等,types無需創建。

#創建名為test的index,兩種寫法等同,名字不能包含特殊字符,只能小寫,不能以-, _, +開頭,不能超過255字節。
PUT test
PUT /test --PUT /test的本質是PUT http://ip:9200/test,kibana做了優化因此寫不寫之前的/無所謂
#當然你還可以直接插入一條數據,index會自動被創建
PUT leo/dramas/1
{
  "name":"權力的游戲"
}
#查看創建好的index的詳細信息
GET leo
#刪除index
DELETE leo
#查詢當前所有的index,這里調用了_cat的API
GET _cat/indices

上圖為我測試創建的多個indices,每列的列名分別是:

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
--其中pri表示number of shards,rep表示number of replicas,新建的index health為yellow的原因是我只有一台服務器因此未能創建replica。

在使用GET <index_name>查看index詳細信息時可以看到,每個index下都有一個名為mapping的屬性,這個屬性用於描述當前type下的大致field有哪些,當然也別忘了在6.5.4版本里一個index下只有一種type了。

2.增

即向ES插入數據:

#插入單條數據,用PUT或POST都可以
PUT test/books/1 
{"name":"《阿Q正傳》","price":100}
PUT test/books/2
{"name":"《鋼鐵是怎樣煉成的》","price":200}
PUT test/books/3
{"name":"《西游記》","price":300}

插入多條數據,目前只能用_bulk API來實現,index表示新插入數據,create同理,在python的index()方法中op_type=create表示如果index不存在那么直接創建index並插入數據,而op_type=index表示向已存在的index中插數據,此外還可以一起bulk delete、update等操作。

PUT test/books/_bulk
{ "index":{"_id":4}}
{"name":"《圍城》","price":101}
{ "index":{"_id":5}}
{"name":"《格林童話》","price":108}
}
#如果你不想設置主鍵_id,那么可以直接置空,系統會創建默認主鍵,寫法如下:
PUT test/books/_bulk
{ "index":{}}
{"name":"《圍城》","price":101}
{ "index":{}}
{"name":"《格林童話》","price":108}

注意插入數據時如果指定的_id已經存在,那么新插入的數據會直接替換原ID的數據。

查看下插入的數據:

GET test/books/_search
{"query":{"match_all":{}}}
GET test/books/_search
{"query":{"match":{"_id":1}}}
GET test/_search
{"query":
  {"range":
    {"price":
      {"lte":1000}
    }
  }
}

index下也有_search API因此這里你也可以省略books直接查詢整個index所有types下的記錄,實際上在6版本中由於types概念的弱化(一個index只能有一種type)許多查詢都可以直接不寫type名了。
這里的query和range以及lte都是DSL關鍵字,其實query只相當於模糊查詢或全文搜索。關於查詢,更系統的DSL(domain specific language)關鍵字及示例會在第5部分“查”補充。

3.刪

記錄的刪除通常由2個API,直接DELETE和POST _delete_by_query完成,示例如下:

#DELETE只能根據ID進行刪除,本例中刪除的是系統自定義的ID因此比較奇怪。
DELETE test/books/_mbEdGgBH8b_BYBmOW-C
#_delete_by_query API允許你刪除符合query條件的記錄,其query body與上邊的查詢過濾的query body規則一樣。
POST test/_delete_by_query
{"query":
  {"range":
    {"price":
      {"lte":1000}
    }
  }
}
#其實刪除、修改和查詢還涉及到多版本控制的概念,這個概念在傳統數據庫中已經很熟悉了,就是為了保證數據一致性的。
#關於版本控制的內容會在第6部分“版本控制”補充。

4.改

記錄更新也是2個API,_update和_update_by_query,前者根據ID進行更新,后者可以更新指定的query結果。此外你還可以不使用這兩個API直接像新插入數據那樣更新數據,只是此時你的body部分必須包含所有的fields了,否則操作完畢后你會發現document只剩下你所更新的那幾個fields,其他的全沒了。

至於為什么刪除使用DELETE命令,而更新只能用_update的API,只是因為ES是RESTFUL風格的,http的指令有DELETE但並沒有UPDATE關鍵字。

更新涉及到版本控制以便維護數據一致性,其實分為兩個操作:get和reindex,大致步驟是:首先取到相應的document,然后執行更新script,最后返回執行的結果。至於具體的多版本控制機制將在第6部分解釋。

更新涉及的DSL語言也與其他操作很不一樣:

#_update API,表示將id為5的document的price改為100
POST test/books/5/_update
{"script":
  {"source":"ctx._source.price=params.price",
   "lang":"painless",
   "params":{
     "price":100
    }
  }

這里的script,source,lang,params都是DSL關鍵字,lang=painless表示使用painless腳本語言來編寫script來完成。
ctx我暫理解為當前事務,ctx._source表示當前定位的document,params表示本次更新用到的數據,source則表示更新操作,通俗來講就是用params的數據+source的操作一起完成更新。

#如果只是簡單的增加新field和刪除field那么格式就比較簡單:
POST test/books/5/_update
{
  "script":"ctx._source.booktype='少兒童話'"
}
POST test/books/5/_update
{
  "script":"ctx._source.remove('少兒童話')"
}
#此外ctx._source或ctx._source.<field_name>還有很多其他的方法和屬性,這里貼一個官網的示例來作出引申,更多的示例慢慢實踐吧。
POST test/_doc/1/_update
{
    "script" : {
        "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
        "lang": "painless",
        "params" : {
            "tag" : "green"
        }
    }
}

這個示例的含義就是:對於id=1的document,如果tags包含green字符,那么刪掉這個document,否則不操作。至於contains是模糊匹配還是精確匹配,有興趣的可以花幾十秒做個測試。

5.查

4個部分的示例中已經有許多查詢的示例了,這里在之前的基礎上介紹一些比較復雜的查詢,首先來了解一個DSL的概念:

DSL:Domain Specific Language,ES提供一種基於JSON的查詢語言,這種查詢語言包含兩種子句模式:

    1.Leaf query clauses

    2.Compound query clauses  --常用的就是bool組合查詢

好吧,其實這里介紹這兩個概念對理解復雜查詢毫無作用,我只是照搬下官方手冊,防止某天頓悟時找不到概念,接下來再看兩個DSL的概念:

Query一般來說包含兩各部分:query context 或 filter context:

舉例來說:

GET /_search
{
  "query": { 
    "bool": { 
      "must": [
        { "match": { "title":   "Search"        }}, 
        { "match": { "content": "Elasticsearch" }}  
      ],
      "filter": [ 
        { "term":  { "status": "published" }}, 
        { "range": { "publish_date": { "gte": "2015-01-01" }}} 
      ]
    }
  }
}

這個例子的query就包含了所有2種context,並使用了bool組合查詢,可以看到bool是最外圍的關鍵字,must與filter並行。
bool組合查詢的子關鍵字主要包含must,must_not,should,分別對應AND、NOT、OR三種邏輯運算,此外還有一個filter子關鍵字。
--filter與must:match的區別:
參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
1.must:match會為匹配到的每個記錄打分,稱作scoring,表示匹配程度,查詢的結果按打分進行排序。
2.filter與must:match基本一致,唯一的區別是其結果不參與打分,相當於一個再過濾。

到這里DSL的4個概念就介紹完了,是的全部介紹完了。官網總共也就這幾行,更多關於關鍵字的具體應用需要到特定的頁面且也通常都是一個簡單的示例完事,因此只能靠日常實踐了。

介紹完DSL那么回到實際應用中來,用於查詢的API一般也是2種:直接通過GET index/doc_type/doc_id獲取,以及_search API

#GET獲取比較簡單,只要有id就可以了,沒id請使用_search API
GET test/books/1
#_search API是查詢使用的核心API,包含諸如聚合、排序、集群查詢、explain API等等等等,這里只貼個官方鏈接和一個示例算啦,重在實踐掌握。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html
POST /twitter/_search?routing=kimchy
{
    "query": {
        "bool" : {
            "must" : {
                "query_string" : {
                    "query" : "some query string here"
                }
            },
            "filter" : {
                "term" : { "user" : "kimchy" }
            }
        }
    }
}
#這里的?routing=kimchy是指在集群中查詢時可以指定名為kimchy的shard。

6.版本控制

Versionning,在官網中暫未找到獨立的說明頁面,只找到2篇古老的博客,分別是2011年和2013年的,地址如下:

https://www.elastic.co/blog/versioning

https://www.elastic.co/blog/elasticsearch-versioning-support

第一篇:

內容顯示versioning是由elasticsearch在0.15版本引入的新特性”樂觀並發控制“引申出來的,只介紹了每個document都會有個由系統控制自增的_version屬性,並未對版本控制機制作出細節解釋。

不過既然是樂觀並發控制我們可以參考傳統RDBMS數據庫中的樂觀鎖來理解,即數據庫服務器會自動進行document快照存儲以便實現事務一致性,接下來看下第二篇博客(實際上看完第二篇博客,里邊也確實介紹了樂觀鎖定)。

第二篇:

以一個經典的丟失更新示例來描述下樂觀並發控制的必要性:

#首先造一條數據
PUT bank_account/shanghai/1
{
  "name":"leo",
  "deposit":100
}
GET bank_account/shanghai/1
#如下為插入的數據,可以看到_version屬性值為1
{
  "_index" : "bank_account",
  "_type" : "shanghai",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "name" : "leo",
    "deposit" : 100
  }
}
#如果這時候兩個商戶同時要從我賬戶里扣1塊錢,結果就是兩家同時取到我賬戶余額為100,各扣了一元並把99的余額寫入ES,這顯然是錯的。因此ES推出了versioning特性。

對於index中的每條記錄都會有一個_version的屬性,其取值范圍為:[1,2^63),插入數據時默認的_version都是1,每次對這個document進行修改或刪除操作都會使其+1,這個過程是由ES自己控制的。

總結一下Versioning的工作機制其實是這樣的,我們以一個投票計數案例為例,1表示球員的ID,每次有人為id=1的球員投票都將投票計數votes+1:

POST NBA/all_star_votes/1/_update?retry_on_conflict=5
{"script":"ctx._source.votes += 1"}

1.首先查詢到你要更新的documents。

2.然后進行version check,記下你查詢到的documents的_version。

3.更新時指定_version=<第二步中查到的version>

4.ES server端收到更新請求后開始進行沖突檢測,如果發現有人在這期間成功投了票(那么_version就會變化),那么直接返回一個http的409 conflict錯誤碼,如果可以更新那么自然返回200 ok就好。

5.如果你顯式的設置了retry_on_conflict參數,那么步驟四的表現還會有所變化:在發現記錄被更改后,server端會嘗試根據scripts將votes+1,然后將_version也+1,然后使用新的_version值和votes值進行更新,如果再次沖突那么重復之前的操作直到成功更新或達到retry_on_conflict的重復次數。

以上操作據官方手冊說是節省了頻繁獲取/釋放鎖的開銷,versioning特性並非強制開啟的,只有你指定了version參數或者retry_on_conflict參數時,ES才會啟用versioning特性為你進行version check和沖突檢測。因此對於類似投票計數這種field的更新你可以開啟versionging特性,對於不規則的並發更新你可以棄用此特性直接使用程序隊列或者干脆用關系型數據庫存儲數據,對於存款更新這種不規則並發更新的金融場景,並發請求之間不可能每次都增減相同的金額,使用retry_on_conflict顯然是無效的,這種場景用關系型數據庫顯然更安全。

當然對於delete操作來說versioning的表現又有所不同,因為如果一個系統頻繁的進行數據的刪除,那么保存大量的舊version會導致資源迅速被耗盡,因此對於delete的記錄ES的默認保存version的時間是1min,這被稱作GC(垃圾回收),你可以通過修改index.gc_deletes參數來擴大此超時時間。

PS:官網沒說update操作留下的舊version是否也會被定期清除,這個可以試驗來驗證,插入一條數據多次更新后進行指定_version的查詢即可驗證,這里節省時間懶的測了。

7.集群操作

集群操作這里省略,會寫在單獨的集群搭建筆記中。

二、Python接口操作

你可以使用Python內置的REST API:requests module來進行es的操作,但是es提供了一種更加貼近elasticsearch概念體系的API:elasticsearch-py,因此這里使用elasticsearch-py來進行演示。

elasticsearch API詳述:https://elasticsearch-py.readthedocs.io/en/master/api.html

Note:為與Python語言兼容,避免出現關鍵字沖突,使用from_代替from,doc_type代替type參數。且為保持一致性和安全性,本接口推薦使用關鍵字傳參,不建議使用位置傳參。

先來一個簡單的演示示例:

# -*- coding: utf-8 -*-
from elasticsearch import Elasticsearch
es = Elasticsearch(hosts='http://10.0.1.49:9200/')
es.delete_by_query(index="test",doc_type="books",body={"query": { "match_all":{}}})
#這里的id=1/2在進入ES后就變為了默認主鍵,查詢時不能用id來查,而是要用_id。當然這里的主鍵概念其實是借用了mongo或其他傳統關系型數據庫的概念,方便理解而已。
es.index(index="test", doc_type="books",id=1,body={"name": "《鋼鐵是怎樣煉成的》","price":100})
es.index(index="test", doc_type="books",id=2,body={"name": "《狂人日記》","price":200})
# res=es.search(index="test",doc_type="books",body={"query": {"match_all": {}}})
# print(res)
res=es.search(index="test",
              doc_type="books",
              body={"query":
                        {"range":
                             {"price":
                                  { "lt":400}
                              }
                         },
                    "sort":{
                        "_id":
                            {}
                            # {"order":"desc"}
                            }
                    }
              )
print("%d documents found" % res['hits']['total'])
for doc in res['hits']['hits']:
    print("%s) %s" % (doc['_id'], doc['_source']['name']))

這里邊涉及到一些基礎的method,這些method的詳細參數和用法都可以在上邊貼出的elasticsearch API詳述網址中找到。

elasticsearch module包含CatClient, ClusterClient, IndicesClient, IngestClient, NodesClient, SnapshotClient and TasksClient等7個client子類以及一些其他暫無需介紹的類,此外還有一個底層訪問接口Elasticsearch類,你能且也只能通過Elasticsearch來訪問前述的7種接口。

定義Elasticsearch class的部分相關代碼為:

......
from ..transport import Transport
from .indices import IndicesClient
from .ingest import IngestClient
from .cluster import ClusterClient
from .cat import CatClient
from .nodes import NodesClient
from .remote import RemoteClient
from .snapshot import SnapshotClient
from .tasks import TasksClient
class Elasticsearch(object):
    def __init__(self, hosts=None, transport_class=Transport, **kwargs):
        """
        :arg transport_class: :class:`~elasticsearch.Transport` subclass to use.
        """
        self.transport = transport_class(_normalize_hosts(hosts), **kwargs)
        # namespaced clients for compatibility with API names
        self.indices = IndicesClient(self)
        self.ingest = IngestClient(self)
        self.cluster = ClusterClient(self)
        self.cat = CatClient(self)
        self.nodes = NodesClient(self)
        self.remote = RemoteClient(self)
        self.snapshot = SnapshotClient(self)
        self.tasks = TasksClient(self)
......

另一種通俗的解釋方式就是:

    當你定義了一個Elasticsearch實例后,會衍生N種諸如IndicesClient、IngestClient等實例,你可以根據自己的需求通過調用Elasticsearch的屬性來獲取這些實例,進而調用他們的各種method,這些屬性值可以是__init__方法中任意屬性,調用這些屬性后你就可以使用這些屬性實例的特有method了,這些client子類實例的屬性可以在上邊貼出的網址里學習,這里只簡略貼一下核心接口類Elasticsearch相關解釋:

class elasticsearch.Elasticsearch(hosts=None, transport_class=<class 'elasticsearch.transport.Transport'>, **kwargs)

    hosts參數使用RESTFUL風格定義,即URL格式,類似上邊的'http://10.0.1.49:9200/'

   除此之外你還可以使用SSL協議創建連接,其參數官網並未單獨列出,但可以通過其SSL連接示例獲知使用方式。

   此class全部的method包含:

bulk(**kwargs)
clear_scroll(**kwargs)
count(**kwargs)
create(**kwargs)
delete(**kwargs)
delete_by_query(**kwargs)
delete_script(**kwargs)
exists(**kwargs)
exists_source(**kwargs)
explain(**kwargs)
field_caps(**kwargs)
get(**kwargs)
get_script(**kwargs)
get_source(**kwargs)
index(**kwargs)
info(**kwargs)
mget(**kwargs)
msearch(**kwargs)
msearch_template(**kwargs)
mtermvectors(**kwargs)
ping(**kwargs)
put_script(**kwargs)
reindex(**kwargs)
reindex_rethrottle(**kwargs)
render_search_template(**kwargs)
scroll(**kwargs)
search(**kwargs)
search_shards(**kwargs)
search_template(**kwargs)
termvectors(**kwargs)
update(**kwargs)
update_by_query(**kwargs)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM