官网:https://www.elastic.co/cn/
下载地址:https://www.elastic.co/cn/start
ES的概念
全文检索
通过一个程序扫描文本中的每一个单词,针对单词建立索引,并保存该单词在文本中的位置,以及出现的次数
用户查询时,通过之前建立好的索引来查询,将索引中单词对应的文本位置、出现的次数返回给用户,因为有了具体的文本的位置,所以就可以将具体内容读取出来。
索引 Index
一个索引就是一个拥有几分相似特征的文档的集合。
一个索引由一个名字来标识,必须全部是小写字母,并且当要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。
映射 Mapping
ElasticSearch中的映射mapping用来定义一个文档。
mapping是用来处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分词器、是否被索引等等,这些都是映射里面可以设置的。
字段 Field
相当于是数据表的字段|列
字段类型 Type
每一个字段都应该有一个对应的类型,如:Text、Keyword、Byte等
文档 Document
一个文档是一个可被索引的基础信息单元,类似一条记录。文档以Json格式来表示。
集群 Cluster
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
节点 Node
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中。
如果在网络中启动了若干个节点,并假定它们能够互相发现彼此,它们将会自动形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,可以拥有若干个节点。而且,如果当前网络中没有运行任何ElasticSearch节点,这是启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
分片 Shards
一个索引可以存储超过单个节点硬件的限制的大量数据。如,一个具有10亿文档的索引占据1TB的磁盘空间,而任意一个节点都没有这么大的磁盘空间;或者单个节点处理索引请求,响应太慢。
ElasticSearch提供了将索引划分成多份的能力,这些份叫做分片。
当创建一个索引的时候,可以指定你想要的分片的数量。
每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片重要:
1)允许水平分割/扩展内容的容量
2)允许在分片上进行分布式的、并行的操作,进而提高性能/吞吐量
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由ElasticSearch管理的,对于作为用户来说,这些都是透明的。
副本 Replicas
在一个网络/云的环境里,失败随时都是可能发生,在某个分片/节点不知怎么的就处理离线状态,或者由于任何原因消失了,这种情况下,有个故障转移机制是非常有用并且强烈推荐的。ElasticSearch允许创建分片的一份或多份拷贝,这些拷贝叫做副本分片,或者直接叫副本。
副本重要:
1)在分片/节点失败的情况下,提供高可用性
分片不与原/主要分片同一个节点上。
2)扩展搜索量/吞吐量,因为搜索可以再所有的副本上并行运行
每个索引可以被分成多个分片。一个索引有0个或多个部分。
一旦设置了副本,每个索引就有了主分片或者副本分片,分片和副本的数量可以在索引创建的时候指定
在索引创建之后,可以在任何时候动态的改变副本的数量,但是不能改变分片的数量
创建及使用ES (这里直接使用腾讯云的ES)
腾讯云ES控制台地址:https://console.cloud.tencent.com/es
腾讯云ES创建参考文档:https://cloud.tencent.com/document/product/845/19536
1) 通过API测试访问
curl -XGET -u user:password http://xxxxxxxxx:9200
2) 通过kibana访问集群
基本操作
1) 索引 - 创建、查询、删除
格式:
PUT /索引名称
GET /索引名称
DELETE /索引名称
示例:
PUT /my_es_db_index_1
GET /my_es_db_index_1
DELETE /my_es_db_index_1
2) 文档 - 添加、修改、查询、删除 (动态映射,关于映射后面的内容)
a) 添加/修改 文档
格式:
PUT /索引名称/类型/ID
修改文档,和添加文档相同!
示例:
PUT /my_es_db_index_1/_doc/1
{
"name": "张1",
"sex": 1,
"age": 21,
"address": "广州天河公园"
}
PUT /my_es_db_index_1/_doc/2
{
"name": "张2",
"sex": 1,
"age": 22,
"address": "广州荔湾大厦"
}
PUT /my_es_db_index_1/_doc/3
{
"name": "张3",
"sex": 1,
"age": 23,
"address": "广州白云山公园"
}
PUT /my_es_db_index_1/_doc/4
{
"name": "张4",
"sex": 1,
"age": 24,
"address": "长江橘子洲头"
}
PUT /my_es_db_index_1/_doc/5
{
"name": "张5",
"sex": 1,
"age": 25,
"address": "长沙岳麓山"
}
b) 查询文档
格式:
GET /索引名称/类型/ID
示例:
GET /my_es_db_index_1/_doc/1
GET /my_es_db_index_1/_doc/2
GET /my_es_db_index_1/_doc/3
GET /my_es_db_index_1/_doc/4
GET /my_es_db_index_1/_doc/5
如下方返回结果示例:
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"_seq_no" : 6,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "张1",
"sex" : 1,
"age" : 21,
"address" : "广州天河公园"
}
}
c) 删除文档
格式:
DELETE /索引名称/类型/ID
示例:
DELETE /my_es_db_index_1/_doc/1
3) 批量操作
a) 批量添加文档
PUT _bulk
{"create": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":6}}
{"name":"张6","sex":1,"age":26,"address":"address_6"}
{"create": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":7}}
{"name":"张7","sex":1,"age":27,"address":"address_7"}
{"create": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":8}}
{"name":"张8","sex":1,"age":28,"address":"address_8"}
{"create": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":9}}
{"name":"张9","sex":1,"age":29,"address":"address_9"}
b) 批量删除
POST _bulk
{"delete": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":6}}
{"delete": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":7}}
{"delete": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":8}}
{"delete": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":9}}
c) 批量修改
POST _bulk
{"update": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":6}}
{"doc":{"name":"张6-6666","sex":1,"age":26,"address":"address_6-66666666"}}
{"update": {"_index": "my_es_db_index_1", "_type": "_doc", "_id":7}}
{"doc": {"name":"张7-777777"}}
查询操作
1) 查询当前类型中所有的文档 _search
格式:
DELETE /索引名称/类型/_search
示例:
GET /my_es_db_index_1/_doc/_search
// 可以省略: _doc
GET /my_es_db_index_1/_search
2) 条件查询 查询age等于22 、小于等于22、大于22
格式:
DELETE /索引名称/_search?q=age:22
DELETE /索引名称/_search?q=age:<=22
DELETE /索引名称/_search?q=age:>22
示例:
GET /my_es_db_index_1/_search?q=age:22
查询结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "张2",
"sex" : 1,
"age" : 22,
"address" : "广州荔湾大厦"
}
}
]
}
}
3) 范围查询 age在22 - 23之间
格式:
DELETE /索引名称/_search?q=age[22 TO 23]
示例:
GET /my_es_db_index_1/_search?q=age[22 TO 23]
查询结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"name" : "张3",
"sex" : 1,
"age" : 23,
"address" : "广州白云山公园"
}
},
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "张2",
"sex" : 1,
"age" : 22,
"address" : "广州荔湾大厦"
}
}
]
}
}
4) 根据多个ID进行批量查询 _mget
格式:
GET /索引名称/_mget
示例:
GET /my_es_db_index_1/_mget
{
"ids":["2", "3"]
}
查询结果:
{
"docs" : [
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "2",
"_version" : 4,
"_seq_no" : 8,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "张2",
"sex" : 1,
"age" : 22,
"address" : "广州荔湾大厦"
}
},
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "3",
"_version" : 1,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "张3",
"sex" : 1,
"age" : 23,
"address" : "广州白云山公园"
}
}
]
}
5) 分页查询 from=0&size=1
格式:
DELETE /索引名称/_search?q=age[22 TO 23]&from=0&size=1
示例:
GET /my_es_db_index_1/_search?q=age[22 TO 23]&from=0&size=1
GET /my_es_db_index_1/_search?q=age[22 TO 23]&from=1&size=2
6) 查询结果只输出某些字段 _source=name,age
格式:
DELETE /索引名称/_search?q=age[22 TO 23]&from=1&size=2&_source=name,age
示例:
GET /my_es_db_index_1/_search?q=age[22 TO 23]&from=1&size=2&_source=name,age
查询结果:
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my_es_db_index_1",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "张2",
"age" : 22
}
}
]
}
}
7) 对查询结果排序 sort=age:desc 、desc降序 / asc升序
格式:
DELETE /索引名称/_search?q=age[22 TO 23]&sort=age:desc
示例:
GET /my_es_db_index_1/_search?q=age[22 TO 23]&sort=age:desc
GET /my_es_db_index_1/_search?q=age[22 TO 23]&sort=age:asc
文档映射(静态和动态)
分为动态映射和动态映射。
动态映射:
在关系数据库中,需要事先创建数据库,然后在该数据库下创建表,并创建表字段、类型、长度、主键等,最后才能基于表插入数据。而ES中不需要定义Mapping映射,在文档写入ES时,会根据文档字段自动识别类型,这种机制称为动态映射。
JSON数据 | 自动推测的类型 |
---|---|
null | 没有字段被添加 |
true或false | boolean型 |
小数 | float型 |
数字 | long型 |
日期 | date或text |
字符串 | text |
数组 | 由数组第一个非空值决定 |
JSON对象 | object类型 |
静态映射:
是在ES中事先定义好映射,包含文档的各字段类型、分词器等,这种方式称之为静态映射。
获取文档映射类型
GET /my_es_db_index_1/_mapping
"properties" : {
"address" : {
"type" : "keyword"
},
"age" : {
"type" : "long"
},
"name" : {
"type" : "keyword"
},
"sex" : {
"type" : "long"
}
}
静态映射方式
创建索引及设置文档映射:
PUT /my_es_db_index_2
{
"mappings": {
"properties": {
"name": {"type":"keyword", "index": true, "store": true},
"sex": {"type":"keyword", "index": true, "store": true},
"age": {"type":"keyword", "index": true, "store": true},
"address": {"type":"text", "index": true, "store": true}
}
}
}
添加文档:
PUT /my_es_db_index_2/_doc/1
{
"name": "张1",
"sex": 1,
"age": 21,
"address": "广州天河公园"
}
查询映射类型:
GET /my_es_db_index_2/_mapping
keyword 与 text 映射类型的区别
设置为keyword映射,只能进行精确查询,不能分词查询,能聚合、排序
设置为text映射,能进行模糊查询,能分词查询,不能聚合、排序
DSL语言高级查询
查询所有
GET /my_es_db_index_2/_doc/_search
{
"query": {
"match_all": {}
}
}
1) Query
Query方式查询,会在ES中索引的数据都会存储一个_score分值,分值越高就代表越匹配。关于某个搜索的分值计算还是很复杂的,因此也需要一定的时间。
a) 精确查询term
term查询不会对字段进行分词查询,会采用精确匹配
采用term精确查询,查询字段映射类型属于keyword
POST /my_es_db_index_1/_search
{
"query": {
"term": {
"name": "张1"
}
}
}
b) 模糊查询match
match会根据该字段的分词器,进行分词查询,如果映射类型为keyword,就会查不到
POST /my_es_db_index_2/_search
{
"query": {
"match": {
"address": "长江"
}
}
}
c) 多字段模糊查询
POST /my_es_db_index_2/_search
{
"query": {
"multi_match": {
"query": "长江"
, "fields": ["address", "name"]
}
}
}
d) 未指定字段条件查询query_string 含AND 与 OR条件
POST /my_es_db_index_2/_search
{
"query": {
"query_string": {
"query": "(橘子) OR (你好)"
}
}
}
e) 指定字段条件查询query_string 含AND 与 OR条件
POST /my_es_db_index_2/_search
{
"query": {
"query_string": {
"query": "(橘子) OR (你好)",
"fields": ["address", "name"]
}
}
}
f) 范围查询
range: 范围关键字
gte:大于等于
lte:小于等于
gt:大于
lt:小于
now:当前时间
POST /my_es_db_index_2/_search
{
"query": {
"range": {
"age": {
"gte": 21,
"lte": 22
}
}
}
}
g) 分页、指定输出字段、排序
POST /my_es_db_index_2/_search
{
"query": {
"range": {
"age": {
"gte": 21,
"lte": 25
}
}
},
"from": 0,
"size": 3,
"_source": ["name", "age"],
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
2) Filter
不会计算相关性分值,也不会对接过进行排序,因此效率会高一点,查询的结果可以被缓存。
POST /my_es_db_index_2/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"age": "22"
}
}
]
}
}
}
通过python操作
pip install elasticsearch==7.10.1
elastic_search = Elasticsearch(["https://xxxxxxxxxxxx:9200"],
http_auth=('xxx', 'xxxxx'),
sniff_on_start=False,
sniff_on_connection_fail=False,
sniffer_timeout=None)
创建索引
PUT /my_es_db_index_3
{
"mappings": {
"properties": {
"name": {"type":"keyword", "index": true, "store": true},
"sex": {"type":"keyword", "index": true, "store": true},
"age": {"type":"keyword", "index": true, "store": true},
"address": {"type":"text", "index": true, "store": true}
}
}
}
def test_create_index(elastic_search):
# 映射类型
mappings = {"mappings": {
"properties": {
"name": {"type": "keyword", "index": True, "store": True},
"sex": {"type": "keyword", "index": True, "store": True},
"age": {"type": "keyword", "index": True, "store": True},
"address": {"type": "text", "index": True, "store": True}
}
}
}
# 创建索引
res = elastic_search.index(index="my_es_db_index_3", body=mappings)
print(res)
注意:这里指定type为text,不知道为什么不生效,还是为keyword,很奇怪。
删除索引
DELETE /my_es_db_index_3
def test_delete_index(elastic_search):
res = elastic_search.indices.delete(index="my_es_db_index_3", ignore=[400, 404])
print(res)
索引是否存在
def test_exists_index(elastic_search):
rsp = elastic_search.indices.exists(index="my_es_db_index_3")
print(rsp)
添加文档
PUT /my_es_db_index_3/_doc/1
{
"name": "张1",
"sex": 1,
"age": 21,
"address": "广州天河公园"
}
def test_put_document(elastic_search):
data = {"name": "张1", "sex": 1, "age": 21, "address": "广州天河公园"}
res = elastic_search.create(index="my_es_db_index_3", doc_type="_doc", id=1, body=data)
print(res)
查询指定ID文档
GET /my_es_db_index_3/_doc/1
def test_get_document(elastic_search):
rsp = elastic_search.get(index="my_es_db_index_3", doc_type="_doc", id=1)
print(rsp)
根据多个id进行批量查询文档
GET /my_es_db_index_1/_mget
{
"ids":["2", "3"]
}
def test_mget(elastic_search):
data = {"ids": ["2", "3"]}
rsp = elastic_search.mget(index="my_es_db_index_3", body=data)
print(rsp)
查询所有文档
GET /my_es_db_index_3/_search
def test_get_all(elastic_search):
rsp = elastic_search.search(index="my_es_db_index_3")
print(rsp)
模糊匹配查询
POST /my_es_db_index_3/_search
{
"query": {
"match": {
"address": "广州"
}
}
}
def test_get_match_document(elastic_search):
data = {"query": {"match": {"address": "广州"}}}
rsp = elastic_search.search(index="my_es_db_index_3", body=data)
print(rsp)
删除文档
DELETE /my_es_db_index_3/_doc/1
def test_delete_document(elastic_search):
rsp = elastic_search.delete(index="my_es_db_index_3", doc_type="_doc", id=1)
print(rsp)
更新文档
POST _bulk
{"update": {"_index": "my_es_db_index_3", "_type": "_doc", "_id":1}}
{"doc": {"name": "张1-1", "sex": 1, "age": 21, "address": "广州天河公园"}}
def test_update_document(elastic_search):
data = {"doc": {"name": "张1-1", "sex": 1, "age": 21, "address": "广州天河公园"}}
rsp = elastic_search.update(index="my_es_db_index_3", doc_type="_doc", id=1, body=data)
print(rsp)
POST /my_es_db_index_3/_search
{
"query": {
"match": {
"address": "广州公园"
}
}
}
参考:
腾讯课堂:https://ke.qq.com/course/3293548?taid=10771563233296748
腾讯云ES创建参考文档:https://cloud.tencent.com/document/product/845/19536
遗留问题:上方通过python创建索引,指定type为text,但是添加后不生效