【ElasticSearch笔记】

ElasticSearch

全文搜索原理

计算文档相关性的算法:TF-IDF(词频-逆文档频率)

  • 词频:查找的单词在文档出现次数越高,得分越高
  • 逆文档频率:如果某个单词在所有文档中出现的次数较低,则其权重越高,得分也越高

其他可以定制化的地方:标题关键字的权重比正文更高,点赞数高的权重高,发版时间越新的权重越高

文本索引建立过程

  1. 将文档交给分析器处理,处理过程包括:字符过滤、分词、分词过滤,处理结果文档被分解成一组分词信息的集合,这里分词信息除了分词本身还包括词性、出现的位置、次数等
  2. 将第一步的处理结果建立文档-分词矩阵,表示分词和文档的包含关系
  3. 基于文档-分词矩阵建立基于分词的倒排索引

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUa1akpK-1661087430227)(img/倒排索引.png)]

文本索引查询过程

  1. 将查询的字符串交给分析器处理,处理过程包括:字符过滤、分词、分词过滤
  2. 遍历每个分词,通过倒排索引得到对应的一组文档
  3. 根据文档相关度打分计算,按打分倒序排序并返回文档

分析器简介

分析器都有三部分组成:字符过滤器、分词器、分词过滤器,其中字符过滤器和分词过滤器可以有多个,分词器只有一个

建立索引和查询需要用相同的分析器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oDiO03t-1661087430228)(img/分析器.png)]

使用分析器进行分词测试
POST /_analyze
{
  "analyzer": "standard", # 直接指定分析器
  "text": "The letter tokenizer is not configurable. letter"
}

POST /index/_analyze
{
  "field": "title", # 使用title字段的分析器来分析
  "text": "The letter tokenizer is not configurable. letter"
}

# 自定义分析器
POST /_analyze
{
  "tokenizer": "standard", # 分词器用standard
  "filter": ["lowercase"], #分词过滤器用lowercase
  "text": "The letter tokenizer is not configurable. letter"
}
设置分析器
# setting参数中设置分析器的作用于所有text字段
PUT /index
{
  "setting": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "simple"
        }
      }
    }
  }
}

# mapping中设置分析器作用于单个字段
PUT /index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "whitespace", #索引时使用的分析器
        "search_analyzer" #搜索时使用的分析器,不配置则默认和analyzer一致,一般用于同义词搜索的情况
      }
    }
  }
}
中文分析器之ik

安装地址:/{ES_HOME}/plugins,下载ik解压即可

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

ik提供了ik_max_word和ik_smart两种分析器,区别是ik_max_word切分粒度比较细,ik_smart粒度比较粗

添加新词:创建文件并添加新词,然后在config/IKAnalysis.cfg.xml将新建的字典文件加入ext_dict

中文分析器之HanLP

安装和使用方式ik类似

同义词

synonyms是ES内置的分词过滤器,它是支持用户自定义同义词的分词过滤器;

synonyms_graph它和synonyms用法类似,区别是synonyms用于索引,而synonyms_graph用于搜索

PUT /index
{
  "setting": {
    "analysis": {
      "filter": { # 自定义过滤器
        "ik_synonyms_filter": {
          "type": "synonym",
          "synonyms_path": "synonyms.dict"
        }
      },
      "analyzer": {
        "ik_analyzer_synonyms": { # 自定义分析器
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "ik_synonyms_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_analyzer_synonyms" # 使用自定义分析器
      }
    }
  }
}

# 重新加载自定义分析器,触发加载同义词库
POST /index/_reload_search_analyzers
停用词(无用词)
# standard分析器指定停用词
PUT /index
{
  "setting": {
    "analysis": {
      "analyzer" {
        "my_standard": {
          "type": "standard",
          "stopwords": ["我", "的", "这"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_standard"
      }
    }
  }
}
IK分析器配置停用词

${ES_HOME}/plugins/ik-analysis/config/IKAnalyzer.cfg.xml配置文件里面可以指定停用词

HanLP配置停用词

${ES_HOME}/plugins/hanlp-analysis/data/dictionary/stopwords.txt里面添加停用词

拼音搜索分析器

地址:https://github.com/medcl/elasticsearch-analysis-pinyin,安装方式同其他分析器安装一样

ES和关系型数据库区别

ES中的文档比数据库的表更加灵活,表只能存一层的数据,而ES文档的值可以是数组也可以是更多的键值对,他鼓励你将属于一个实体的数据保存在一个文档中

基本概念

文档对应数据库的行,类型对应数据库的表,索引对应数据库的库

字段类型自动检测可能导致报错,比如,先来个7 es自动检测为数字,如果后面再来个hello world就会报错

新建一个文档不是马上对查询可见的,有个refresh_interval的设置,默认1秒,索引刷新操作很昂贵所以有这样一种设计考虑,因此elasticsearch也被称为准实时的

索引与分片

一个索引会分成多个分片(默认5个),每个分片可以分布在不同的存储节点上,分片的es处理的最小单元,每个分片就是一个Lucene索引:包含倒排索引的文件目录;每个分片默认一个副分片本,副本分片和主分片不在同一节点上面,副本分片可以服务于搜索请求,并且在主分片无法访问时时升级成主分片;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbQ9VWHV-1661087430229)(img/节点与分片.png)]

副本分片可以在运行的时候进行添加和移除,而主分片不可以

可以在任何时候改变每个分片的副本分片的数量,因为副本分片总是可以被创建和移除。这并不适用于索引划分为主分片的数量,在创建索引之前,你必须决定主分片的数量。请记住,过少地分片将限制可扩展性,但是过多的分片会影响性能。默认设置的5份是一个不错的开始。

类型只是逻辑上的分离,在ES中不同类型的文档并没有物理上的分离,在一个ES索引中的所有文档其实都存储在相同分片的同一组文件中。 所以在同一个索引的不同类型中相同的字段必须有相同的数据类型。

建立索引过程

首先根据文档ID计算其hash值,然后确认其应该存储在的节点,接收请求的节点将请求转发到主节点简历主分片,随后在所有备份节点上建立副本分片,主分片和副本分片都建立好后返回成功。

搜索索引过程

搜索索引的过程主要就是接收请求的节点把请求转发到索引所在分片的节点,这个转发过程可以做负载均衡,使用轮训机制选择可用的分片(可能是主分片也可能副本分片),ES从这些分片搜集结果将其聚集成功单一结果返回给客户端

创建索引与搜索文档

ES字段类型

  1. 字符串(keyword, text)
  2. 数值(byte/short/int/long/float/double)
  3. 日期(date),字面量是字符串,ES内部用的long来保存对应的时间戳,定义时使用format配置时间字面量格式
  4. 布尔(true和false)
  5. 数组:不需要定义,每个字段都可以存储成单个值或者数组
  6. 对象类型
  7. 地理类型(geo_point)
  8. 自动提示(completion)
curl - XPUT 'localhost:9200/index/_mapping' -d '{
  "properties": {
      "next_time": {
        "type": "date", 
        "format": "MMM DD YYYY"
      }
  }
}'

文档预定义字段

_ttl: 剩余存活时间

_timestamp: 创建时间

_all:表示所有字段

常用命令

推荐使用kibana的dev tools作为和ES进行交互式的请求工具,本地安装方式地址:http://localhost:5601

ES老板有类型的概念,新版本去除了这个概念,只有一个默认的"_doc"

term/terms(完全匹配):可以用于keyword、数字、boolean、date、数组

GET /index/_search
{
  "query": {
    "term": {
      "city": {
        "value" : "北京"
      }
    }
  }
}

GET /index/_search
{
  "query": {
    "terms": {
      "city": ["北京", "上海"]
    }
  }
}

match(分词匹配):用于text查询

GET /index/_search
{
  "_source": ["title"],
  "query": {
    "match": {
      "title": "金都酒店"
    }
  }
}

或者

GET /index/_search
{
  "_source": ["title"],
  "query": {
    "match": {
      "title": {
        "query": "金都酒店",
        "boost": 2, # 设置该字符串的查询权重,可以影响得分和排序
      }
    }
  }
}

multi_match:在多个字段上使用match

GET /index/_search
{
  "_source": ["title"],
  "query": {
    "multi_match": {
      "query": "金都酒店",
      "fields": ["title", "amenities"]
    }
  }
}

match_all:查询所有

GET /index/_search
{
  "query": {
    "match_all": {
      "boost": 1  # 设置所有文档得分
    }
  }
}

range:一般用于date、数字类型,gt大于,lt小于,gte大于等于,lte小于等于

GET /index/_search
{
  "query": {
    "match": {
      "price": {
        "gte": 300,
        "lte": 500
      }
    }
  }
}

suggest(前缀匹配):搭配completion类型使用

GET /index/_search
{
  "suggestion": {
    "hotel_zh_sug": { # 定义搜索建议名称
      "prefix": "如家",
      "completion": {
        "field": "field_name"
      }
    }
  }
}

exists(查询存在某个字段的文档)

GET /index/_search
{
  "query": {
    "exists": {
      "field": "tag" # 查询存在tag字段的文档
    }
  }
}

geo_distance:基于地理位置查询

GET /index/_search
{
  "_source": ["title", "city", "location"],
  "query": {
    "geo_distance": {
      "distance": "5km",     # 搜索距离不超过5km的酒店
      "location": {          # 搜索字段
        "lat": "39.915143",  # 维度
        "lon": "116.4039"    # 经度
      }
    }
  }
}

geo_bounding_box:搜索指定地区矩形区域内的文档

GET /index/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {  # 左上角坐标
          "lat": "39.922921",
          "lon": "116.457044"
        },
        "right_bottom": {
          "lat": "39.907104",
          "lon": "116.479466"
        }
      }
    }
  }
}

geo_polygon:搜索指定多边形区域内的文档

GET /index/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [{
          "lat": "111",
          "lon": "222"
        },{
          "lat": "333",
          "lon": "444"
        },{
          "lat": "555",
          "lon": "666"
        }]
      }
    }
  }
}
创建一个新的映射
PUT /index
{
  "settings": {
    "number_of_shards": 15,
    "number_of_replicas": 2
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "city": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      },
      "create_time": {
        "type": "date"
      },
      "full_room": {
        "type": "boolean"
      },
      "location": {
        "type": "geo_point"
      },
      "tags": {
        "type": "keyword"
      },
      "comment_info": {
        "properties": {
          "favourable_comment": {
            "type": "integer"
          },
          "negative_comment": {
            "type": "integer"
          }
        }
      }
    }
  }
}
更新索引setting
PUT /index/_setting
{
  "analysis": {
    "filter": { # 自定义过滤器
      "ik_synonyms_filter": {
        "type": "synonym",
        "synonyms_path": "synonyms.dict"
      }
    },
    "analyzer": {
      "ik_analyzer_synonyms": { # 自定义分析器
        "tokenizer": "ik_max_word",
        "filter": ["lowercase", "ik_synonyms_filter"]
      }
    }
  }
}
删除索引
DELETE /index
关闭索引
POST /index/_close
打开索引
POST /index/_open
定义新的映射
curl -XPOST 'localhost:9200/index' -H 'content-type:application/json' -d 
{ 
  "mappings:" {
    "properties": {
      "name": {
        "type": "text", 
        "index": true
      },
      "city": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      }
    }
  }
}

可以多次定义映射,并且新的映射会和老的映射自动合并;但是无法改变已有的字段类型和索引方式

获取映射
curl 'localhost:9200/index/_mapping/?pretty'
添加一个文档
# id可以指定也可以不指定
POST /hotel/_doc/{id}
{
  "title": "weiyela",
  "city": "chengdu",
  "price": 298.99
}
更新一个文档
POST /index/_update/id
{
  "doc": {
    "title": "好再来酒店",
    "city": "北京",
    "price": 659.45
  }
  "upsert": {  # 可有可没有,表示没有的时候执行插入
    "title": "好再来酒店",
    "city": "北京",
    "price": 659.45
  }
}
通过ID获取分档
curl 'localhost:9200/index/id?pretty'

通过ID查询是实时的,而通过搜索查询只是近实时的,保存后可能有1秒左右延时才能搜索

批量操作文档
POST /_bulk
{"index": {"_index": "index_name"}} # 写入索引
{...}                                   # 写入文档内容
{"index": {"_index": "index_name", "_id": "id"}} # 指定id
{...}
{"update": {"_index": "index_name", "_id": "id"}} # 更新指定id文档
{"doc": {"title": "文雅豪情酒店", "city": "北京"}} # 更新内容
{"delete": {"_index": "index_name", "_id": "id"}} # 更新指定id文档
POST /_bulk
{"index": {"_index": "hotel", "_id": "001"}}
{"title": "文雅酒假日酒店", "city": "北京", "price": 556.00, "create_time": "20200418120000", "full_room": true, "location": {"lat": 39.938838, "lon": 116.448112}, "tags": ["wifi", "小型电影院"], "comment_info": {"favourable_comment": 20, "negative_comment": 10}}
{"index": {"_index": "hotel", "_id": "002"}}
{"title": "金都嘉怡假日酒店", "city": "北京", "create_time": "20200315200000", "full_room": false, "location": {"lat": 39.915153, "lon": 116.4030}, "tags": ["wifi", "免费早餐"], "comment_info": {"favourable_comment": 20, "negative_comment": 10}}
{"index": {"_index": "hotel", "_id": "003"}}
{"title": "金都假日酒店", "city": "北京", "price": 200.00, "create_time": "20210509160000", "full_room": true, "location": {"lat": 40.002096, "lon": 116.3766773}, "comment_info": {"favourable_comment": 20, "negative_comment": 10}}
{"index": {"_index": "hotel", "_id": "004"}}
{"title": "金都假日酒店", "city": "天津", "price": 500.00, "create_time": "20210218080000", "full_room": false, "location": {"lat": 39.155004, "lon": 117.203976}, "tags": ["wifi", "免费车位"]}
{"index": {"_index": "hotel", "_id": "005"}}
{"title": "文雅精选酒店", "city": "天津", "price": 800.00, "create_time": "202101010800", "full_room": true, "location": {"lat": 39.178447, "lon": 117.219999}, "tags": ["wifi", "充电车位"], "comment_info": {"favourable_comment": 20, "negative_comment": 10}}
根据条件更新文档
POST /index/_update_by_query
{
  "query": {  # 如果不要则是更新所有文档
    "term": {
      "city": {
        "value": "北京"
      }
    }
  },
  "script": {
    "source": "ctx._source['city']='上海'",
    "lang": "painless"
  }
}
删除单个文档
DELETE /index/_doc/id
根据条件删除文档
POST /index/_delete_by_query
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
简单查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JJ4gWmOc-1661087430229)(img/搜索语法.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3HgtKHJ-1661087430230)(img/搜索结果.png)]

# 搜索多个索引
curl 'localhost:9200/index,index2/_search?q=es&pretty'

# 搜索所有索引
curl 'localhost:9200/_search?q=es&pretty'
复合查询
GET /index/_search
{
  "explain": true, # 是否返回查询分析
  "_source": ["title", "city"],
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  },
  "from": 0, # 分页查询起势偏移量,从零开始
  "size": 20, # 分页大小,最大10000,超过则报错
  "profile": "true", #开启性能剖析开关
  "sort": [{  # 有sort后,查询返回将没有得分score(null)
    "price": {
      "order": "desc"
    }
  }],
  "sort": [{
    "_geo_distance": {
      "location": { # 设置排序的中心点坐标
        "lat": "39.915143",
        "lon": "116.4039"
      },
      "order": "asc", # 由近及远排序
      "unit": "km",
      "distance_type": " plane " # 排序所使用的距离计算算法
    }
  }],
  "highlight": { # 高亮显示
    "fields": {
      "title": {
        "type": "plain" # plain高精确度,需要载入内存重新查询分析,适用于短字段;unified是默认值;fvh速度快,可使用于大字段(超过1MB)
        "pre_tags": "<high>", # 默认是<em>
        "post_tags": "</high>"
      }
    }
  }
}

分页查询中,每个分片节点都是构建一个from+size长度的有序队列,然后协调节点将个分片节点数据汇总,则需要N * (from+size)长度的队列用于全局排序

聚合查询-count
GET /index/_count
{
  "query": {...}
}
评分分析
GET /index/_explain/id
{
  "query": {...}
}

boosting查询

虽然boost值可以对查询的权重进行调整,但是仅限于term查询和match查询,如果是其他查询类型却不行,这时就可以用到boosting查询

GET /index/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "title": {
            "query": "金都"
          }
        }
      },
      "negative": { # 设置负面查询
        "range": {
          "price": {
            "lte": 200
          }
        }
      },
      "negative_boost": 0.2 # 设置降低的权重值
    }
  }
}

聚合查询

GET /index/_search
{
  "size": 0, # 表示不查询
  "aggs": { # 聚合查询
    "my_agg": { # 聚合名称
      "avg": { # 求平均值
        "field": "price"
      }
    }
  }
}
GET /index/_search
{
  "size": 0,
  "aggs": { # 聚合查询
    "my_agg": { # 聚合名称
      "stats": { # 文档数量、最大值、最小值、平均值、加和值一并返回
        "field": "price"
      }
    }
  }
}
GET /index/_search
{
  "size": 0,
  "aggs": { # 聚合查询
    "my_agg": { # 聚合名称
      "value_count": { # 类似count,但是不统计空值的文档,如果用于数组则所有文档数量中非空的总和
        "field": "price"
      }
    }
  }
}
GET /index/_search
{
  "size": 0,
  "aggs": { # 聚合查询
    "my_agg": { # 聚合名称
      "sum": { # 类似count,但是不统计空值的文档,如果用于数组则所有文档数量中非空的总和
        "field": "price",
        "missing": 0 # 空值当做0处理
      }
    }
  }
}
桶聚合(类似分组聚合)
GET /hotel/_search
{
  "size": 0,
  "aggs": { 
    "my_agg": { 
      "terms": { # 按城市分组,只适用于keyword、bool、keyword数组字段
        "field": "city"
      }
    }
  }
}
GET /hotel/_search
{
  "size": 0,
  "aggs": { 
    "my_agg": { 
      "range": { # 按数组进行分组
        "field": "city",
        "ranges": [{
          "to": 200  # 不指定from,默认0
        }, {
          "from": 200,
          "to": 500
        }, {
          "from": 500 # 不指定to,默认为该字段最大值
        }]
      }
    }
  }
}
GET /hotel/_search
{
  "size": 0,
  "aggs": { 
    "my_agg": { 
      "terms": { # 按城市分组,只适用于keyword、bool、keyword数组字段
        "field": "city"
      },
      "aggs": { # 子分组,可以无线嵌套
        "my_sum": {
          "sum": {
            "field": "price",
            "missing": 0
          }
        }
      }
    }
  }
}
地理距离聚合
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "my_agg": {
      "geo_distance": {
        "field": "location",
        "origin": {
          "lat": 39.915143,
          "lon": 116.4039
        },
        "unit": "km", # mi米, km千米
        "ranges": [{
          "to": 3
        }, {
          "from": 3,
          "to": 10
        }, {
          "from": 10
        }]
      }
    }
  }
}
先查询再聚合
GET /hotel/_search
{
  "size": 0,
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  },
  "aggs": {
    "my_agg": {
      "avg": {
        "field": "price"
      }
    }
  }
}
前过滤器
GET /hotel/_search
{
  "size": 0,
  "query": {
    "term": {
      "city": {
        "value": "天津"
      }
    }
  },
  "aggs": {
    "my_agg": {
      "filter": { # 指定前过滤器
        "term": {
          "full_room": false
        }
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}
后过滤器
GET /hotel/_search
{
  "size": 0,
  "query": {
    "match": {
      "title": "假日"
    }
  },
  "post_filter": { #后过滤器,在聚合完成后对查询起作用,所以不影响聚合,只影响查询
    "term": {
      "city": "北京"
    }
  },
  "aggs": {
    "my_agg": {
      "avg": {
        "field": "price",
        "missing": 200
      }
    }
  }
}
聚合排序
按统计数量排序
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "query_city": {
      "terms": {
        "field": "city",
        "order": {
          "_count": "asc" # 按count的升序排序
        }
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "price",
            "missing": 200
          }
        }
      }
    }
  }
}
按子聚合排序
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "group_city": {
      "terms": {
        "field": "city",
        "order": {
          "my_avg": "asc"
        }
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "price",
            "missing": 200
          }
        }
      }
    }
  }
}
按key排序
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "query_city": {
      "terms": {
        "field": "city",
        "order": {
          "_key": "asc" # 按key升序排序
        }
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "price",
            "missing": 200
          }
        }
      }
    }
  }
}
聚合分页

ES支持同时返回查询结果和聚合结果

Top Hits聚合
GET /hotel/_search
{
  "size": 0,
  "query": {
    "match": {
      "title": "金都"
    }
  },
  "aggs": {
    "group_city": {
      "terms": {
        "field": "city"
      },
      "aggs": {
        "my_avg": {
          "top_hits": { # 只返回每个父分组的前N个
            "size": 3,
            "sort": [{
                "uuid": {
                  "order": "desc"
                }
              }
            ]
          }
        }
      }
    }
  }
}
Collapse聚合
GET /hotel/_search
{
  "from": 0, # 当有collapse时,作用于每个分组内
  "size": 5, # 当有collapse时,作用于每个分组内
  "query": {
    "match": {
      "title": "金都"
    }
  },
  "collapse": { # collapse方法只支持对某一个字段去重
    "field": "city"
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值