elasticsearch 學習總結
elasticsearch
Elasticsearch是用Java語言開發的基于Lucene的搜索服務器,它提供了一個分布式多用戶能力的全文搜索引擎,基于RESTful web接口。
1、es index 字段類型
1.1、字符串
- text :于全文索引,搜索時會自動使用分詞器進行分詞再匹配
- keyword : 不分詞,搜索時需要匹配完整的值
1.2、數值型
- 整型: byte,short,integer,long
- 浮點型: float, half_float, scaled_float,double
1.3、日期型
- date
雖然字段類型為date,但json沒有date類型。有三種表示的形式:
- 將日期時間格式化后的字符串,如 "202101-01" 或者 "2021/01/01 10:10:10"。
- long 型的整數,意義是 milliseconds-since-the-epoch,是自 1970-01-01 00:00:00 UTC 以來經過的毫秒數。
- int 型的整數,意義是 seconds-since-the-epoch, 是指自 1970-01-01 00:00:00 UTC 以來經過的秒數。
它是可以指定format為的,例如:
"create_time": {
"type": "date",
"format": "yyy-MM-dd HH:mm:ss",
}
format 默認"strict_date_optional_time||epoch_millis"。
- strict_date_optional_time:只要是 ISO datetime parser 可以正常解析的都是 strict_date_optional_time,如鏈接。
- epoch_millis:UNIX 紀元時間,以毫秒為單位。
這種情況下可以解析下面三種日期格式:
- "2021-05-12"
- "2021-05-12T12:10:30Z"
- 1121231241568
不過我用得不多,我只試過用"yyy-MM-dd HH:mm:ss"的時候,搜索的時候,搜索條件如果是不匹配的格式,會直接報錯,不記得用默認格式有沒有報錯了,好像也有些問題,所以后來我直接用了keyword。
1.4、其他
- ip:IPV4的地址.
- boolean:布爾值如true/false。5.4以前貌似可以接受0/1這些數字和字母。但5.4之后只接受true/false以及字符串的"true"/
"false"。- binary:二進制, 會把值當做經過 base64 編碼的字符串,默認不存儲,且不可搜索。值不能嵌入換行符\n。
其他還有些對象啊、數組啊、范圍啊、地址位置之類的,參考以下鏈接吧:
1、https://segmentfault.com/a/1190000022722763
2、http://www.rzrgm.cn/chy18883701161/p/12723658.html
3、官網:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/mapping-types.html#mapping-types
2、es 創建index
假設我們要一張index存文件的元數據,示例如下:
body = {
"mappings": {
"_doc": { # doc_type
"properties": { # 元數據的field及field type
"create_time": {
"type": "keyword",
},
"file_size": {
"type": "keyword",
},
"file_name": {
"type": "keyword",
},
}
},
}
}
es.indices.create(index=index_name, body=body)
首先,index和doc_type可以理解為一般數據庫的數據庫和數據表,但也不是那么一樣。一個index一般我們只有一個doc_type,所以網上很多示例一般常用_doc作為表名,它是什么其實都無所謂,真正重要的是index。這點其實跟SQLite有點像,一般一個.sqlite也是只有一個main表。
說明:
1、做搜索其實有的時候不建議存格式是"yyy-MM-dd
HH:mm:ss"的date類型,搜索的時候除非整個格式完全匹配才能搜索,否則不僅搜不出來,還會報錯。對于需要在所有類型搜索的話,keyword就夠了。
2、indices是針對index級別的增刪改查的類,比如容易混淆的是,有一個es.index(),這個其實主要是類似insert的操作。
3、增加字段類型es.indices.put_mapping,說是說創建或更新數據doc字段結構,但其實一般只能新增doc_type的field,而且因為涉及到元數據的修改,只能是master節點來執行。
4、關于數字類型,可以看到file_size是文件大小,其實原本應該是數字類型,但是搜索時傳入其他字符串類型文字會報錯,有兩種解決辦法:一種就是改為字符串類型,一種就是在查詢的時候傳入lenient
參數為true,就會忽略這個錯。
3、es.bulk批量寫入數據
在python中,es批量插入數據,有兩種方式,一種是es的bulk,一種是helpers.bulk,之前搜了很久,全都是后者。
但一來是為了封裝,二來也是方便,他們有一個共同點,就是都要自己指定id,之前原本我搜到一些文檔說es自帶的可以不用,但是我測試的時候沒有成功,必須要有,我是用python的uuid庫生成的,然后去掉-分隔符。
helper.bulk傳入的body格式:
[{
"_index": "index_name", # 數據庫名稱
"_type": "doc_type", # 數據表名
"_id":"1233", # ID
"_source":{ # 要寫入的數據
"field1": value1, # 字段及值
}
}]
es.bulk傳入的body格式:
[{
'index': { # 數據庫信息
'_id': id, # 必傳
'_index': 'index_name', #_index和_type都不傳時,需要在bulk函數中指定參數
'_type': 'doc_type'
}
},
{
"field1": value1, # 對應_source部分,是要寫入的數據,不需要id
},
…… # 重復上面的步驟,一個index,一個data
]
沒錯,你沒看錯,一條數據,占了兩個dict,而且多條數據也是根據順序來確定哪個數據是哪個表的,一開始我其實并不太相信怎么是這樣的,也覺得很不方便。但是后來發現index里可以省略_index和_type,也就是說,當這一整個都是寫入某一張表的時候, 'index'里有id就可以,例如:
>>> body = [{
'index': { '_id': id}
},
{
"field1": value1,
},
]
# refresh使文檔在索引操作后立即可供搜索
>>> es.bulk(index=index_name, doc_type=doc_type, body=body, refresh=True)
4、@query_param 裝飾器
研究到這個裝飾器,是因為我想要封裝es類,不封裝其實也可滿足需要,只是想每次調用都在初始化時先連接上es。創建、刪除index、插入數據什么的其實不太需要額外的param,但search的時候就很需要。
一開始,我覺得很好奇,為什么源碼里,每個方法最后只有一個params,但我們調用的時候,卻可以無限傳參,而且不是以
**kwargs的方式,而且如果我在封裝時照著它的格式來寫卻無法實現后面的參數被傳入。后來我就發現每個方法都有一個@query_param裝飾器,且這個裝飾器里傳入的參數,正好是前面我剛剛說的,不是以**kwargs方式傳入,也沒有指定參數,卻可以被接收的參數。
比如,我們用search方法做例子,分頁需要用到size:
@query_param(
……
"size"
……
)
def search(self, index=None, doc_type=None, body=None, params=None):
if "from_" in params:
params["from"] = params.pop("from_")
……
然后看@query_param的源碼:
def query_params(*es_query_params):
"""
Decorator that pops all accepted parameters from method's kwargs and puts
them in the params argument.
"""
def _wrapper(func):
@wraps(func)
def _wrapped(*args, **kwargs):
params = {}
if "params" in kwargs:
params = kwargs.pop("params").copy()
for p in es_query_params + GLOBAL_PARAMS:
if p in kwargs:
v = kwargs.pop(p)
if v is not None:
params[p] = _escape(v)
# don't treat ignore and request_timeout as other params to avoid escaping
for p in ("ignore", "request_timeout"):
if p in kwargs:
params[p] = kwargs.pop(p)
return func(*args, params=params, **kwargs)
return _wrapped
return _wrapper
其實就是將裝飾器里傳入的參數加入params里,并過濾掉重復的。很明顯,它就是我們可以追加參數的原因了。
所以我們封裝的時候,也要加這個裝飾器,也可以選擇性的加自己需要的:
from elasticsearch import Elasticsearch
from elasticsearch.client.utils import query_params
class ESUtil(object):
def __init__(self, hosts=None, port=None):
"""
有很多種方式可以連接
:param hosts:eg: [{"host": "ip1", "port": 9200}] or ["ip1:9200"] or ["ip1"] or 'ip1'
:param port:當hosts為字符串時,可以通過port指定port
"""
self.hosts = hosts
self.port = port
self.es_client = Elasticsearch(hosts=self.hosts, port=self.port)
# 這里示范假設我們只需要三種參數
@query_params(
"from_",
"size",
"sort",
)
def by_search(self, index_name=None, doc_type=None, body=None, params=None):
"""根據條件查詢數據"""
return self.es_client.search(index=index_name, doc_type=doc_type, body=body, params=params)
那我們連接使用的時候,假設我們要實現分頁查詢:
es.search_data(
index_name="index_name", # index名,可以理解為數據庫
doc_type="_doc", # doc,可以理解為數據表,但是一個index應該只允許有一個doc,所以叫什么無所謂,但是我們一般默認取_doc
body=body, # 查詢條件
params={"from": page_num * page_size}, # from是指從第幾條數據開始展示,page_num是指獲取第幾頁
size=page_size # 一頁展示多少條數據
)
page_num 和 page_size是自己傳的參數值:
page_size:一頁展示多少條數據page_num:獲取第幾頁
from是指從第幾條數據開始展示,比如總共200條數據,設置size為100,即一頁展示100條,如果from為0,那第一頁的數據就是0-99,那第二頁就是100-200,所以一般page_num獲取第幾頁,那from就是page_num * page_size。
根據search方法的源碼和 @query_params的參數可知,from可以寫作from_,它會將它自動轉化成from。也可以不寫在params里,像size一樣加在后面,但這種方式要寫from_。
注意事項:
1、size的大小不能超過index.max_result_window這個參數的設置,默認為10,000。如果大于這個數,需要在create
index時指定參數"max_result_window"。2、from + size這種方式在深度分頁時會有性能問題,數據越大,往后搜索性能越低,盡量控制查詢數據在10000-50000條數據。但是目前我覺得在需要分頁這件事上,似乎沒有更好的辦法,scroll和search_after雖然適合深分頁,但是他們都需要根據上一次獲取的數據的scroll_id或最后一條數據來獲取下一頁的數據,他們不適合跳到指定第幾頁。
3、scroll 更適合數據導出或后臺,不適合用于實時的請求,因為每一個 scroll_id
不僅會占用大量的資源,而且會生成歷史快照,對于數據的變更不會反映到快照上。search_after倒是可以實時同步查詢中的增刪改查啥的,它需要使用一個唯一值的字段作為排序字段,比如_id。
5、ES HTTP 查詢數據
Elasticsearch 提供強大且全面的 REST API 集合,這些 API 可用來執行各種任務,例如檢查集群的運行狀況、針對索引執行
CRUD(創建、讀取、更新、刪除)和搜索操作,以及執行諸如篩選和聚合等高級搜索操作。因此,我們通過HTTP請求,使用curl,或者在瀏覽器就可以進行查詢。瀏覽器主要是GET請求,能做一些簡單的查詢,如果想要寫數據或者攜帶一些其他參數,可能還是要借助curl,postman,或者直接用python這樣使用封裝好的方法。
5.1 查看當前所有index
http://ip:9200/_cat/indices?v
5.2 search查詢
# pretty表格式化,不然就是縮在一起,index是你的index名,不需要大括號,默認一頁十條數據
http://ip:9200/{index}/_doc/_search?pretty
# 需要翻頁,from表從第幾條開始查詢,size表每頁多少條。from默認為0,size默認為10
http://ip:9200/{index}/_doc/_search?pretty&from=0&size=100
# 根據條件查詢,關鍵詞q,查詢file_type值為docx的值,精確匹配,相當于term,=后是字段類型,可以是*號,:是條件
http:///ip:9200/{index}/_doc/_search?q=file_type:docx&pretty
# 所有結果按照_id升序排序
http:///ip:9200/{index}/_doc/_search?q=*&sort=_id:asc&pretty
5.3 source查詢
# 根據_id查詢某條記錄,如果_source沒有指定某個字段則返回這條記錄所有信息,如果指定則只返回這條信息
# 這種source的方法只能根據id查詢
http://ip:9200/{index}/_doc/{_id}?_source{=file_type}&pretty
# 查詢id為154fd92abd41496d8025d0417fb0cc6d的file_type字段值
>>>http://ip:9200/{index}/_doc/154fd92abd41496d8025d0417fb0cc6d?_source=file_type&pretty
{
"_index" : "es-metadata",
"_type" : "_doc",
"_id" : "154fd92abd41496d8025d0417fb0cc6d",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"file_type" : "docx"
}
}
# 查詢id為154fd92abd41496d8025d0417fb0cc6d的所有信息
>>>http://ip:9200/{index}/_doc/154fd92abd41496d8025d0417fb0cc6d?_source&pretty
{
"_index" : "es-meta",
"_type" : "_doc",
"_id" : "154fd92abd41496d8025d0417fb0cc6d",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"create_time" : "2021-05-19 11:30:22",
"file_size" : "21940919",
"file_type" : "docx",
"file_path" : "/tmp/a/test1.docx",
"file_name" : "test1.docx"
}
}
6、ES Python 查詢
es中的查詢請求有兩種方式,一種是簡易版的查詢,另外一種是使用JSON完整的請求體,叫做結構化查詢(DSL)。其實推薦后者,可讀性,修改的靈活性都很好,有點像ORM那樣。
6.1簡單查詢
網上有非常多的示例,這里大概歸類一些常用的關鍵詞及鏈接,不多做演示。
- term/terms:精確/完全匹配,大概相當于select * from table where xx='' or yy='',前者只有一個條件;
- match: 模糊匹配,是一種 bool 類型的查詢。
- wildcard:通配符查詢,就是利用*號匹配,是我最喜歡用的一種。
- prefix:前綴查詢.
- range:范圍查詢,類似between …and…
term和match的區別:
當查詢的詞只有一個時沒有區別。當有多個詞時,受分詞影響較大。如果開啟了分詞,則無法匹配到原來的詞,但是沒開啟則能匹配。如"a
b",分詞了就會變成有兩個詞"a"和"b",那"a b"這個詞就無法匹配了。而match,前面說過,match是一種 bool類型的查詢。也就是說,默認其實是有一個隱藏的操作符or的,當有多個詞的時候,匹配到任意一個就算匹配到了。match還有其他幾種方法,由于我主要用的通配符,沒太涉及,這里不詳細說明。
參考鏈接:
1、https://www.jb51.net/article/156935.htm
2、http://www.rzrgm.cn/yjf512/p/4897294.html
3、https://segmentfault.com/a/1190000017110948
6.2 復合查詢
三個關鍵詞:must,should,must_not。這三個可以相互穿插著使用。
- must:文檔必須完全匹配條件
- should:至少滿足一個條件,這個文檔就符合
- must_not:文檔必須不匹配條件
這個理解起來其實很好理解,但是寫起來嘛就長的,所以才推薦DSL。主要需要注意的事情是,如果是基于某個條件又有復合查詢的,需要嵌套在條件里面,而不是并排。
6.3 ES Python DSL查詢
這個需要另外一個第三方庫,拿官方示例說明,Search需要一個ES實例,一個index名,由于一般只允許有一個doc,所以也不需要指定。其他的查詢方法的第一個參數是查詢的關鍵詞,第二個是字段名和字段值。
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search
client = Elasticsearch()
s = Search(using=client, index="index_name") \
.filter("term", category="search") \
.query("match", title="python") \
.exclude("match", description="beta")
參考鏈接
1、curl:https://blog.csdn.net/ty4315/article/details/52264198
2、https://www.yht7.com/news/108484
3、https://www.bbsmax.com/A/VGzlyNaNJb/
4、bulk:https://blog.csdn.net/Areigninhell/article/details/85095825
5、date:https://www.jianshu.com/p/a44f6523912b
6、DSL:https://elasticsearch-dsl.readthedocs.io/en/latest/
本文來自博客園,作者:蘇酒酒,轉載請注明原文鏈接:http://www.rzrgm.cn/sujiujiu/p/15370005.html


浙公網安備 33010602011771號