Elasticsearch
1. Elasticsearch相关概念
官方文档
Elasticsearch 是一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎,可以说 Lucene 是当今最先进,最高效的全功能开源搜索引擎框架。
Elasticsearch是一个实时分布式和开源的全文搜索和分析引擎。
它可以从RESTful Web服务接口访问,并使用模式少JSON(JavaScript对象符号)文档来存储数据。它是基于Java编程语言,这使Elasticsearch能够在不同的平台上运行。使用户能够以非常快的速度来搜索非常大的数据量。
1.1 优势
- 高效性: 采用倒排索引的方式,从而进行高效的搜索
- 横向扩展性: 只需要增加服务器,添加配置并入ElasticSearch集群中
- 分片机制提供更好的分布性: 同一个索引分成多个分片(sharding),分而治之的方式可以提高效率
- 高可用: 提供副本(Replica)机制,一个分片可以设置多个复制,使得单个节点挂掉的情况,集群照常允许
1.2 主要概念
- 节点 - 它指的是Elasticsearch的单个正在运行的实例。单个物理和虚拟服务器容纳多个节点,这取决于其物理资源的能力,如RAM,存储和处理能力。
- 集群 - 它是一个或多个节点的集合。 集群为整个数据提供跨所有节点的集合索引和搜索功能。
- 索引 - 它是不同类型的文档和文档属性的集合。索引还使用分片的概念来提高性能。 例如,一组文档包含社交网络应用的数据。
- 类型/映射 - 它是共享同一索引中存在的一组公共字段的文档的集合。 例如,索引包含社交网络应用的数据,然后它可以存在用于用户简档数据的特定类型,另一类型可用于消息的数据,以及另一类型可用于评论的数据。
- 文档 - 它是以JSON格式定义的特定方式的字段集合。每个文档都属于一个类型并驻留在索引中。每个文档都与唯一标识符(称为UID)相关联。
- 碎片 - 索引被水平细分为碎片。这意味着每个碎片包含文档的所有属性,但包含的数量比索引少。水平分隔使碎片成为一个独立的节点,可以存储在任何节点中。主碎片是索引的原始水平部分,然后这些主碎片被复制到副本碎片中。
- 副本 - Elasticsearch允许用户创建其索引和分片的副本。 复制不仅有助于在故障情况下增加数据的可用性,而且还通过在这些副本中执行并行搜索操作来提高搜索的性能。
1.3 Elasticsearch和RDBMS之间的比较
Elasticsearch | 关系数据库 |
---|---|
索引 | 数据库 |
碎片 | 碎片 |
映射(Type) | 表 |
文档(Documents) | 行 |
字段 | 字段 |
JSON对象 | 元组 |
1.4 docker中安装Elastic Search
Elasticsearch Download
Elasticsearch install on docker
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.9.2
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.9.2
docker start/stop elasticsearch
http://localhost:9200/
1.5 ElasticSearch的9200和9300端口区别
9200作为Http协议,主要用于外部通讯
比如: http://localhost:9200/study/user/1
9300作为Tcp协议,ES集群之间进行通讯,jar之间就是通过tcp协议通讯
2. Kibana
2.1 Kibana介绍
Kibana 是为 Elasticsearch设计的开源分析和可视化平台。你可以使用 Kibana 来搜索,查看存储在 Elasticsearch 索引中的数据并与之交互。你可以很容易实现高级的数据分析和可视化,以图表的形式展现出来
2.2 Docker中安装Kibana
Elastic Install kibana on docker
docker pull docker.elastic.co/kibana/kibana:7.9.2
docker run --name kibana --link 63406af0f5b5:elasticsearch -p 5601:5601 docker.elastic.co/kibana/kibana:7.9.2
http://localhost:5601/
3. ES乐观锁控制并发
在Elasticsearch通过“_version ”来记录文档的版本,在文档创建时会有一个初始version,默认为1。在对文档进行修改和删除时,version会递增,也可以由用户指定。只有当版本号大于当前版本时,才会修改删除成功,否则失败。当并发请求时,先修改成功的,version会增加,这个时候其他请求就会犹豫version不匹配从而修改失败。
4. Elasticsearch倒排索引
4.1 正向索引
总而言之是通过key,去找value; 用户在主页上搜索关键词“华为手机”时,假设只存在正向索引(forward index),那么就需要扫描索引库中的所有文档,找出所有包含关键词“华为手机”的文档,再根据打分模型进行打分,排出名次后呈现给用户。
弊端: 因为互联网上收录在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回排名结果的要求。
4.2 倒排索引
搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
得到倒排索引的结构如下:
“关键词1”:“文档1”的ID,“文档2”的ID,…………。
“关键词2”:带有此关键词的文档ID列表。
单词—文档矩阵
从纵向即文档这个维度来看,每列代表文档包含了哪些单词,比如文档1包含了词汇1和词汇4,而不包含其它单词。从横向即单词这个维度来看,每行代表了哪些文档包含了某个单词。比如对于词汇1来说,文档1和文档4中出现过单词1,而其它文档不包含词汇1。矩阵中其它的行列也可作此种解读。
倒排索引简单实例
假设文档集合包含五个文档
中文和英文等语言不同,单词之间没有明确分隔符号,所以首先要用分词系统将文档自动切分成单词序列。这样每个文档就转换为由单词序列构成的数据流,为了系统后续处理方便,需要对每个不同的单词赋予唯一的单词编号,同时记录下哪些文档包含这个单词,在如此处理结束后,我们可以得到最简单的倒排索引(参考图3-4)。在图4中,“单词ID”一栏记录了每个单词的单词编号,第二栏是对应的单词,第三栏即每个单词对应的倒排列表。比如单词“谷歌”,其单词编号为1,倒排列表为{1,2,3,4,5},说明文档集合中每个文档都包含了这个单词。
相对复杂的倒排索引
相当于基本索引系统比,在单词对应的倒排列表中不仅记录了文档编号,还记载了单词频率信息(TF),即这个单词在某个文档中的出现次数,之所以要记录这个信息,是因为词频信息在搜索结果排序时,计算查询和文档相似度是很重要的一个计算因子,所以将其记录在倒排列表中,以方便后续排序时进行分值计算。
5. ElasticSearch DSL 介绍
Elasticsearch提供了基于JSON的完整查询DSL(特定于域的语言)来定义查询。将查询DSL视为查询的AST(抽象语法树),它由两种子句组成:
- 叶子查询子句
叶查询子句中寻找一个特定的值在某一特定领域,如 match,term或 range查询。这些查询可以自己使用。 - 复合查询子句
复合查询子句包装其他叶查询或复合查询,并用于以逻辑方式组合多个查询(例如 bool或dis_max查询),或更改其行为(例如 constant_score查询)。
查询子句的行为会有所不同,具体取决于它们是在 查询上下文中还是在过滤器上下文中使用。
我们在使用ElasticSearch的时候,避免不了使用DSL语句去查询,就像使用关系型数据库的时候要学会SQL语法一样。如果我们学习好了DSL语法的使用,那么在日后使用和使用Java Client调用时候也会变得非常简单。
5.1 REST 增删改查:
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档id |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
5.1 Kibana 增删改查
索引:
## Create Index
PUT study
## Create Index with setting
PUT test1
{
"settings" : {
"number_of_shards" : 10,
"number_of_replicas" : 1,
"refresh_interval" : "1s"
},
"mappings" : {
"_doc" : {
"properties" : {
"age" : { "type" : "long" },
"sex" : { "type" : "text" },
"message" : { "type" : "keyword" },
"sendtime" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
## View Index
GET study/
## setting(设置选项)
GET study/_settings
## mapping(数据结构)
GET study/_mapping
注:
- number_of_shards: 是设置的分片数,设置之后无法更改!
- refresh_interval: 是设置es缓存的刷新时间,如果写入较为频繁,但是查询对实时性要求不那么高的话,可以设置高一些来提升性能。可以更改
- number_of_replicas : 是设置该索引库的副本数,建议设置为1以上。
其中这里还有几个重要参数也顺便说一下:
- store: true/false 表示该字段是否存储,默认存储。
- doc_values: true/false 表示该字段是否参与聚合和排序。
- index: true/false 表示该字段是否建立索引,默认建立。
## Post 若URL没有ID可自动生成ID
POST study/user/9
{
"name":"xiaoming",
"age":20,
"sex":"F"
}
## Insert Data POST/PUT
PUT study/user/3
{
"name":"Payne3",
"age":10,
"sex":"M"
}
## 查询所有数据
GET study/user/_search
GET study/user/_search
{
"query": {
"match_all": {}
}
}
## 只查询name&age属性
GET study/user/_search
{
"query": {
"match_all": {}
},
"_source": ["name","age"]
}
## 根据ID查询数据
GET study/user/_mget
{
"ids":[1,"2"]
}
## 根据属性查询数据
GET study/user/_search?q=age:30
GET study/user/_search?q=age:[10 TO 30]
## 根据属性查询数据
## 查询年龄等于30
GET study/user/_search?q=age:30
## 查询年龄在10到30之间的数据
GET study/user/_search?q=age:[10 TO 30]
## 排序
GET study/user/_search?q=age:[10 TO 30]&sort=age:desc
GET study/user/_search?q=age:[10 TO 30]
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "asc"
}
}
]
## 5.6 分页
GET study/user/_search?q=age:[10 TO 30]&from=0&size=2
GET study/user/_search
{
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from":0,
"size":2
}
term:
是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
GET study/user/_search
{
"query": {
"term": {
"name":"payne"
}
}
}
Issue: 当查询字段是字符串时,并且传入的子串带有大写字母,这时候使用term精确查询无论字段类型是keyword还是text都是查询不到结果的
elasticsearch在创建倒排索引时,就已经将大写转为小写,而后写入索引;
进一步优化查询,因为是精准查询,不需要查询进行评分计算,只希望对文档进行包括或排除的计算,所以我们会使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。推荐如下查询
GET study/user/_search
{
"query": {
"constant_score" : {
"filter" : {
"term" : {
"name":"payne"
}
}
}
}
}
}
match:
根据字段的分词器进行分词查询
GET study/user/_search
{
"query": {
"match": {
"name":"payne"
}
}
}
bool查询
GET study/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "Payne"
}
},
{
"match": {
"age": 30
}
}
]
}
}
}
must: 必须匹配,与and等价。贡献算分
must_not:必须不匹配,与not等价,常过滤子句用,但不贡献算分
should: 选择性匹配,至少满足一条,与 OR 等价。贡献算分
filter: 过滤子句,必须匹配,但不贡献算分
查询分词器的分词结果
GET _analyze
{
"tokenizer" : "standard",
"text" : "Payne YU"
}
GET _analyze
{
"tokenizer" : "standard",
"filter": [{"type": "length", "min":2, "max":4 }],
"text" : "this is a test"
}