Elasticsearch基础知识笔记

3 篇文章 0 订阅

Elasticsearch是一种基于Lucene开发的高性能,可扩展的分布式搜索引擎。与常用数据库索引不同,采用倒序索引(全文索引)方式实现,将内容信息进行分词拆装成关键字,然后对每个关键字建立索引。广泛应用在搜索,电商及各类资源型网站中。

ES提供了Restful API及java开发接口,隐藏了原有Lucene的复杂性。实现了近乎实时的搜索功能,延迟控制在秒级内,可以使用集群方式达到大数据量的快速检索。

类似数据库,ES也有与库、表及字段相对应的存储结构,分别为index,type,document

index:索引库,是具有相同或者大致相同数据的整体存储结构,如商品index,订单index
type:一个index下可以有多个type,但是每个index可以有稍微不同的结构,如电子产品type,服装type,但是在新es版本中,一个index中只能有一个type,因为同一个index下的相同字段会被创建到同一个索引中,如果不同type定义的field类型不同,创建索引就会冲突。索引新版es中创建和检索都不需要指定type了,es缺省使用1来代替了。
document:存放数据的最小单元,一个document表示一条数据

index采用主从和分片的方式存储数据,其中primary shard可以进行读写操作,replica shard从primary shard中同步数据后, 可提供查询操作。创建index时,可指定主从分片个数, 创建后,主分片个数不能修改,副本分片个数可以继续扩展。

生产环境中, 如果ES集群数据负载过大,可进行垂直拓展主副分片,即增加结点内存和磁盘空间。如果查询压力过大,可水平扩展,增加结点至集群中,增加副本分片个数,分散请求压力。

Kibana是ES的可视化web操作界面,类似ZooKeeper和ZKUI一样。

ES及Kibana下载地址 https://www.elastic.co/cn/downloads/past-releases
Windows系统下,解压执行bin目录下bat文件即可使用。

访问 http://localhost:5601/ 打开Kibana系统

Kibana系统下操作ES

进入Dev Tools菜单,输入相关Restful API,即可对ES的数据进行各操作。
PUT(增加)、DELETE(删除)、POST(修改)、GET(查询)

查看集群状况

v表示显示头信息
GET /_cat/health?v

epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1619277503 15:18:23  elasticsearch green           1         1      4   4    0    0        0             0                  -                100.0%

总共1个节点, 共有4个分片(主), 集群状态为green

集群共有3个状态

red:部分primary分片不是active状态
yellow:所有的primary分片都是active状态,部分replica分片不是active状态
green:表示primary和replica分片都是active状态

一个很重要的知识点:
同一个分片的primary和replica分片不能在同一个结点上,原因:避免因为结点服务不可用或物理宕机,导致部分数据丢失

查看索引

GET /_cat/indices?v

health status index                    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .apm-custom-link         JqNQ1KDESAG2C12Urfgslw   1   0          0            0       208b           208b
green  open   .kibana_task_manager_1   JpmN6VfUT-yzBB-QrlmOaw   1   0          5            1     95.7kb         95.7kb
green  open   .apm-agent-configuration irX9cVRsTf-Y7CtpH-rxng   1   0          0            0       208b           208b
green  open   .kibana_1                MVhb5PfNQgW0I_jk8fkHgg   1   0         19            1     52.2kb         52.2kb

以上索引都是Kibana自动创建的

创建索引

PUT /my_index 默认创建了一个主分片,一个副本分片

health status index                    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .apm-custom-link         JqNQ1KDESAG2C12Urfgslw   1   0          0            0       208b           208b
green  open   .kibana_task_manager_1   JpmN6VfUT-yzBB-QrlmOaw   1   0          5            3    112.3kb        112.3kb
yellow open   my_index                 aQCskrRLQXOi2QmumtVMwg   1   1          0            0       208b           208b
green  open   .apm-agent-configuration irX9cVRsTf-Y7CtpH-rxng   1   0          0            0       208b           208b
green  open   .kibana_1                MVhb5PfNQgW0I_jk8fkHgg   1   0         20            3     65.2kb         65.2kb

可以看到my_index的状态为yellow,因为只有一个primary分片在active状态,另一个replica不是活跃状态,所以是yellow状态。此时,如果启动一个新的ES结点到集群中,ES会自动将分片均匀分布到各节点上,短时间内会将此index达到green状态。

指定主副本分片数量

PUT /my_index2

{
  "settings": {
    "number_of_shards": 3,  ## primary shard个数
    "number_of_replicas": 1 ## replica shard个数
  }
}

health status index                    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .apm-custom-link         JqNQ1KDESAG2C12Urfgslw   1   0          0            0       208b           208b
green  open   .kibana_task_manager_1   JpmN6VfUT-yzBB-QrlmOaw   1   0          5            3     53.8kb         53.8kb
yellow open   my_index                 aQCskrRLQXOi2QmumtVMwg   1   1          0            0       208b           208b
green  open   .apm-agent-configuration irX9cVRsTf-Y7CtpH-rxng   1   0          0            0       208b           208b
yellow open   my_index2                Wn5reLWvRTyVh6BKPOxZWA   3   1          0            0       624b           624b
green  open   .kibana_1                MVhb5PfNQgW0I_jk8fkHgg   1   0         20            5     36.9kb         36.9kb

新增商品

index: goods type:phone document_id:1
PUT /goods/phone/1

{
  "sku": "SKU001",
  "name": "iPhone 12",
  "price": "9999",
  "desc": "The newest iPhone",
  "tags": ["iPhone", "iPhone12"]
}

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "2",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

查看商品

GET /goods/phone/1

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "sku" : "SKU001",
    "name" : "iPhone 12",
    "price" : "9999",
    "desc" : "The newest iPhone",
    "tags" : [
      "iPhone",
      "iPhone12"
    ]
  }
}

修改商品

PUT /goods/phone/1

{
    "sku" : "SKU001",
    "name" : "iPhone 12"
}

使用PUT修改完成后,查看数据

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "1",
  "_version" : 2,
  "_seq_no" : 2,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "sku" : "SKU001",
    "name" : "iPhone 12"
  }
}

数据其他属性丢失,此操作相当于替换操作,将原document标记删除, 创建新的document,谨慎使用

修改商品部分属性

POST /goods/phone/1/_update

{
  "doc": {
    "price" : "9988"
  }
}

使用POST修改完成后,查看数据

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "1",
  "_version" : 4,
  "_seq_no" : 4,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "sku" : "SKU001",
    "name" : "iPhone 12",
    "price" : "9988",
    "desc" : "The newest iPhone",
    "tags" : [
      "iPhone",
      "iPhone12"
    ]
  }
}

这种部分属性修改可以减少并发的异常情况,全量替换可能会覆盖其他操作,部分修改原理与全部修改基本一致,只不过获取原document的时机不同,部分修改在提交给es后,es会先获取document,然后再修改,插入一条新的document,旧document标记删除,影响范围更小,并发性更高。

删除商品

DELETE /goods/phone/1
查询返回

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "1",
  "found" : false
}

这里简单说一下document删除的原理,删除时只是将原纪录标记删除,并没有立刻物理删除,而是等待数据越来越多时,es会自动检索已经标记删除的数据,将其物理清除掉。

检索所有数据

GET /goods/phone/_search

{
  "query": {
    "match_all": {}
  }
}

GET /goods/phone/_search

#! Deprecation: [types removal] Specifying types in search requests is deprecated.
{
  "took" : 664, ## 消耗时间
  "timed_out" : false, ## 是否超时
  "_shards" : { ## 分片数量
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2, ## 结果数量
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "sku" : "SKU002",
          "name" : "iPhone 10",
          "price" : "8888",
          "desc" : "The iPhone X",
          "tags" : [
            "iPhone",
            "iPhoneX"
          ]
        }
      },
      {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "sku" : "SKU001",
          "name" : "iPhone 12",
          "price" : "9999",
          "desc" : "The newest iPhone",
          "tags" : [
            "iPhone",
            "iPhone12"
          ]
        }
      }
    ]
  }
}

搜索document时,可以指定index和type,也可以不指定,可以指定多个index,多个type

GET /goods/phone/_search
GET /goods,goods1/phone/_search
GET /_search
GET /_all/phone/_search
GET /goods*/phone/_search

另外一种简写方式

GET /goods/phone/_search?q=name:iPhone	// name中必须包含iPhone
GET /goods/phone/_search?q=+name:iPhone // 与上面一致
GET /goods/phone/_search?q=name:iPhone	// name中必须不包含iPhone
GET /goods/phone/_search?q=iPhone		// 会将各field拼接成一个字符串,添加到索引中,用于检索

关键字检索

GET /goods/phone/_search

{
  "query": {
    "match": {
      "name": "xiaomi"
    }
  }
}

多条件检索

GET /goods/phone/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "iPhone"
          }
        },
        {
          "match": {
            "tags": "iPhone12"
          }
        }
      ]
    }
  }
}

GET /goods/phone/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "iPhone"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "tags": "iPhone12"
          }
        }
      ]
    }
  }
}

多字段匹配

查询name和tags中包含iPhone的document

GET /goods/_search

{
  "query": {
    "multi_match": {
      "query": "iPhone",
      "fields": ["name", "tags"]
    }
  }
}

指定数量检索

GET /goods/phone/_search

{
  "query": {
    "match": {
      "name": "xiaomi"
    }
  }, 
  "from": 0, 
  "size": 20
}

批量获取document

GET /_mget

{
  "docs": [
    {
      "_index": "goods",
      "_type": "phone",
      "_id": 1
    },
    
    {
      "_index": "goods",
      "_type": "phone",
      "_id": 2
    }
  ]
}

指定查询字段

GET /goods/phone/_search

{
  "query": {
    "match": {
      "name": "xiaomi"
    }
  }, 
  "_source": ["sku", "name"], 
  "from": 0, 
  "size": 20
}

短语搜索match_phrase

短语表示被筛选内容中,必须连续包含整个子串,不会进行分词

GET /goods/phone/_search

{
  "query": {
    "match_phrase": {
      "name": "mi"
    }
  }
}

排序

GET /order/_search

{
  "query": {
    "match": {
      "userName": "guan"
    }
  },
  "sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}

filter和query

二者都是用来筛选数据的,不同的区别是:query不仅关心是否满足条件,还会根据相关度进行score计算;filter只判断是否符合条件,不会计算score,并且内置了缓存,所以filter效率更高。

GET /order/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": 
          { "userName": "Guan" }
        }
      ],
      "filter": [
        { "range": {
          "id": {
            "gte": 0,
            "lte": 3
          }
        }}
      ]
    }
  }
}

查询校验

GET /order/_validate/query?explain

{
  "query": {
    "match": {
      "userName": "Guan"
    }
  }
}

{
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "valid" : true,
  "explanations" : [
    {
      "index" : "order",
      "valid" : true,
      "explanation" : "userName:guan"
    }
  ]
}

乐观锁

低版es在对文档创建时,会默认加上一个版本号,与MySQL的乐观锁一致,避免并发时数据异常问题。

新版es添加了seq_no和primary_term,对document进行并发控制,两个参数都一致才允许修改。

参考官方文档示例:https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html

POST /goods/phone/1?if_seq_no=11&if_primary_term=1

{
  "price": "9988"
}

{
  "_index" : "goods",
  "_type" : "phone",
  "_id" : "1",
  "_version" : 6,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 12,
  "_primary_term" : 1
}

可以看到修改成功后seq_no已经加1,再次发起报错,因为版本号已经不匹配,所以更新失败

{
  "error" : {
    "root_cause" : [
      {
        "type" : "version_conflict_engine_exception",
        "reason" : "[1]: version conflict, required seqNo [11], primary term [1]. current document has seqNo [12] and primary term [1]",
        "index_uuid" : "43hVEeIDRCKyYR6ySDTF1g",
        "shard" : "0",
        "index" : "goods"
      }
    ],
    "type" : "version_conflict_engine_exception",
    "reason" : "[1]: version conflict, required seqNo [11], primary term [1]. current document has seqNo [12] and primary term [1]",
    "index_uuid" : "43hVEeIDRCKyYR6ySDTF1g",
    "shard" : "0",
    "index" : "goods"
  },
  "status" : 409
}

bulk操作

当客户端频繁发起操作,可以采取批量提交请求执行,减少网络开销,但是bulk操作要求严格的顺序,各操作不能换行,创建和更新操作时,doc写在操作命令下一行,不用数组的原因也是不需要一次将其转换成一个对象,因为有可能数据量太大导致json转换cpu和内存消耗过高,这样只要一行行读取就好了,也是为了提升性能。

bulk操作一般控制在一次1000~5000个操作,并不是数量越大性能越高,可以在生成环境中逐步调优。

POST /goods/phone/_bulk

{"update": {"_id": 1}}
{"doc": {"desc": "The iPhone 12"}}
{"create": {"_id": 9}}
{"sku": "SKU0009", "name":"Redmi K30", "price": "1599"}
{"delete": {"_id": 5}}

{
  "took" : 8,
  "errors" : false,
  "items" : [
    {
      "update" : {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "1",
        "_version" : 8,
        "result" : "noop",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 14,
        "_primary_term" : 1,
        "status" : 200
      }
    },
    {
      "create" : {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "9",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 21,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "delete" : {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "5",
        "_version" : 5,
        "result" : "not_found",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 22,
        "_primary_term" : 1,
        "status" : 404
      }
    }
  ]
}

返回结果中包含每一个操作对应的结果,并且各个操作独立,顺序执行,相互不影响,如果失败会在结果中返回失败原因。

获取index的mapping

一般情况下mapping不需要我们手动创建或指定,es会自动根据document生成对应的mapping,当然也可以指定各field的类型,但是类型一旦指定后就不可以修改,可以新增filed。

GET /order/_mapping

{
  "order" : {
    "mappings" : {
      "properties" : {
        "createTime" : {
          "type" : "long"
        },
        "id" : {
          "type" : "long"
        },
        "sn" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "userId" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "userName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

properties:可以嵌套,比如通常address中包含省市区信息。
text:用来存储字符串类型,text表示使用分词器对文档进行分词,并添加到索引中,并且会截取整个字符串的前256个字符以keyword方式进行存储,keyword表示必须完整匹配。

POST /order/order/1
{
  "id": 1,
  "sn": "O00001",
  "userId": "U00001",
  "userName": "Guan Dongsheng",
  "createTime": 1621156874000
}

GET /order/_search // 可以检索出数据
{
  "query": {
    "match": {
      "userName": "Guan"
    }
  }
}

GET /order/_search // 不可以检索出数据
{
  "query": {
    "match": {
      "userName.keyword": "Guan"
    }
  }
}

GET /order/_search // 可以检索出数据
{
  "query": {
    "match": {
      "userName.keyword": "Guan Dongsheng"
    }
  }
}

分布式存储

index在创建时可以指定多个主分片和副本分片,主分片用于读写数据,副本分片同步数据后提供读操作,提升集群的并发能力,同一个结点中不能包含同一个主副本分片,避免单个服务结点挂掉导致数据丢失。创建index后,es会将主副分片均匀分散到集群中,主分片个数不能再次修改,但是可以根据并发数量调整副本分片的个数。

document在存放或者获取时,需要根据算法选定一个主分片(比如P0),可以将请求发送到P1上,当收到请求后,获取路由结果,转发到P0,获取结果后返回给客户端。一般采用hash取余算法,当数据量很大时,各节点数据量几乎平均分配,这也是为什么主分片数不能修改的主要原因。主分片保存document后会将数据同步到自己的副本分片上。

客户端可以将请求发送到集群中任一节点,此时此节点会充当协调者的角色,根据路由算法判断请求数据会在哪个节点上,然后将请求转发到对应节点,获取结果后,如果需要处理则会处理完成后返回。如分页请求,第一页10条数据,则每个节点都要获取前10条,协调节点获取到所有节点的前10条后,再次排序筛选返回最终结果,因此会有deep paging问题,当偏移量越来越高,检索和传输数据越来越大,导致性能越来越慢。

Bouncing Results

参考文档 https://elasticsearch.cn/article/334
搜索同一query,结果ES返回的顺序却不尽相同,这就是请求轮询到不同分片,而未设置排序条件,相同相关性评分情况下,是按照所在segment中​lucene id来排序的,相同数据的不同备份之间该id是不能保证一致的,故造成结果震荡问题。
如设置该参数,则有一下9种情况

_primary:发送到集群的相关操作请求只会在主分片上执行。
_primary_first:指查询会先在主分片中查询,如果主分片找不到(挂了),就会在副本中查询。
_replica:发送到集群的相关操作请求只会在副本上执行。
_replica_first:指查询会先在副本中查询,如果副本找不到(挂了),就会在主分片中查询。
_local: 指查询操作会优先在本地节点有的分片中查询,没有的话再在其它节点查询。
_prefer_nodes:abc,xyz:在提供的节点上优先执行(在这种情况下为’abc’或’xyz’)
_shards:2,3:限制操作到指定的分片。 (2和“3”)。这个偏好可以与其他偏好组合,但必须首先出现:_shards:2,3 | _primary
_only_nodes:node1,node2:指在指定id的节点里面进行查询,如果该节点只有要查询索引的部分分片,就只在这部分分片中查找,不同节点之间用“,”分隔。

custom(自定义):注意自定义的preference参数不能以下划线"_"开头。
当preference为自定义时,即该参数不为空,且开头不以“下划线”开头时,特别注意:如果以用户query作为自定义preference时,一定要处理以下划线开头的情况,这种情况下如果不属于以上8种情况,则会抛出异常。

Scroll批量检索

scroll检索类似于分页,生成一份数据快照,指定时间内分批次查询,快照之后的数据变化不可见,创建一个scroll_id进行标记,可用于系统进行大批量处理,性能比较高。

GET /goods/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "size": 2
}

{
  "_scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFGNpYm5kSGtCMzNheHFVVFM4c1ktAAAAAAAARSsWTTdIN1RHWkJRTWl4cHVIWVpoRHY1QQ==",
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 6,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "sku" : "SKU002",
          "name" : "iPhone 10",
          "price" : "8888",
          "desc" : "The iPhone X",
          "tags" : [
            "iPhone",
            "iPhoneX"
          ]
        }
      },
      {
        "_index" : "goods",
        "_type" : "phone",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "sku" : "SKU003",
          "name" : "xiao mi",
          "price" : 1999,
          "desc" : "xiao mi k30",
          "tags" : [
            "xiaomi",
            "k30"
          ]
        }
      }
    ]
  }
}

后续查询使用

GET /_search/scroll

{
  "scroll": "1m",
  "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFFVTYm1kSGtCMzNheHFVVFNvc1lSAAAAAAAARQoWTTdIN1RHWkJRTWl4cHVIWVpoRHY1QQ=="
}

持续更新中。。。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值