前端玩转elasticsearch入门

1,406 阅读10分钟

什么是elasticsearch?

一个开源的分布式搜索引擎,底层是开源库Lucene,可以用来实现海量数据的搜索、日志统计、分析、系统监控等功能。对搜索引擎的操作封装成了RESTful的API,通过http请求就能对其进行操作。

ELK技术栈 image.png

准备工作之安装docker

mac商城直接安装desktop, 安装好了直接pull elasticsearch和kibana镜像(注意版本适配,参考官网)
image.png docker相关学习可参考我之前的一篇文章juejin.cn/post/684490…

运行容器

image.png

访问kibana localhost:5601

  • 获取token
    找到elasticsearch容器运行ID docker ps或者通过桌面工具直接复制
    进容器docker exec -it 容器运行ID bash
    生成tokenbin/elasticsearch-create-enrollment-token --scope kibana

  • 获取elasticsearch的账号密码
    进容器 ./bin/elasticsearch-setup-passwords auto 自动生成账号密码

image.png

登陆进去就可以愉快的玩耍devtool image.png

ES数据库基本概念

索引和映射

索引就像数据库里的表,映射就像数据库中定义的表结构 例如:

  • 所有用户文档,就可以组织在一起,称为用户的索引;(ps:文档可以看走表中的一条数据,下文有相关学习)
  • 所有商品的文档,可以组织在一起,称为商品的索引;
  • 所有订单的文档,可以组织在一起,称为订单的索引; image.png

因此,我们可以把索引当做是数据库中的表。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping) ,是索引中文档的字段约束信息,类似表的结构约束。

Mapping映射

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)

      keyword类型只能整体精确搜索,不支持搜索部分内容

    • 数值:long、integer、short、byte、double、float、

    • 布尔:boolean

    • 日期:date

    • 对象:object

  • index:是否创建索引参与搜索,默认为true

  • analyzer:使用哪种分词器

  • properties:该字段的子字段

举个🌰

{    
"age": 21,    
"weight": 52.1,    
"isMarried": false,    
"info": "真相只有一个!", 
"email": "zy@itcast.cn", 
"score": [99.1, 99.5, 98.9],    
}

对应的每个字段映射(mapping):

  • age:类型为 integer;参与搜索,因此需要index为true;无需分词器
  • weight:类型为float;参与搜索,因此需要index为true;无需分词器
  • isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
  • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
  • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
  • score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器

索引库操作

CRUD简单描述:

  • 创建索引库:PUT /索引库名
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 修改索引库(添加字段):PUT /索引库名/_mapping

创建

image.png

查询

image.png

修改(添加新字段)

image.png image.png

文档和字段

一个文档就像数据库里的一条数据,字段就像数据库里的列,所有文档组织在一起可以称之为索引。 elasticsearch是面向文档(Document) 存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:

image.png

而Json文档中往往包含很多的字段(Field) ,类似于mysql数据库中的列

文档操作

文档操作有哪些?

  • 创建文档:POST /{索引库名}/_doc/文档id
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id
    • 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

创建

image.png

查询

image.png

修改

  1. 全量修改是覆盖原来的文档,其本质是:
  • 根据指定的id删除文档
  • 新增一个相同id的文档 image.png

image.png

  1. 增量修改
  • 增量修改是只修改指定id匹配的文档中的部分字段。
POST /{索引库名}/_update/文档id 
{     
    "doc": 
        { 
            "字段名""新的值", 
        } 
}

image.png

查看所有索引

image.png

IK分词

github:github.com/infinilabs/…

进es容器 安装对应版本分词器后需重启es服务:

`bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.13.0`

IK分词器包含两种模式:

  • ik_smart:最少切分
  • ik_max_word:最细切分

测试分词功能: image.png

DSL

elasticsearch的查询依然是基于JSON风格的DSL来实现的。

//查询的语法基本一致
GET /indexName/_search
{   
    "query": {
        "查询类型": {
                "查询条件""条件值"     
         }   
     } 
 }

常见的查询类型

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match: 根据一个字段查询
    • multi_match: 根据多个字段查询
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:

    • ids
    • range:根据数值范围查询,可以是数值、日期的范围
    • term: 根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool
    • function_score:算分函数查询,可以控制文档相关性算分,控制文档排名

全文检索

全文检索查询的基本流程如下:

  • 对用户搜索的内容做分词,得到词条
  • 根据词条去倒排索引库中匹配,得到文档id
  • 根据文档id找到文档,返回给用户

常用场景:如很多网站的输入框搜索

  1. match
//语法
GET /indexName/_search 
{   
    "query": {     
        "match": {       
            "FIELD""TEXT"     
         }  
    } 
}

image.png

  1. mulit_match
// 语法
GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query""TEXT",
      "fields": ["FIELD1"" FIELD12"]
    }
  }
}

image.png

精确检索

因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条,换句话来说,不会搜索具有分词功能的字段。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。

term

// 语法
GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value""VALUE"
      }
    }
  }
}

image.png

如果搜具有分词功能也就是类型是text的字段,则查不出数据

image.png

range

范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。

// 语法
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte"10, // 这里的gte代表大于等于,gt则代表大于
        "lte"20 // lte代表小于等于,lt则代表小于
      }
    }
  }
}

image.png

复合查询

fuction score

1.1 相关性算分
elasticsearch会根据词条和文档的相关度做打分,目前采用BM25算法。
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分query_score(_score),返回结果时按照分值降序排列。

image.png 1.2 算分函数查询
在搜索出来的结果的分数基础上,再手动与指定的数字进行一定运算来改变算分,从而改变结果的排序。(以百度搜索为例,花钱的广告会排在前位)

function score query定义的三要素是什么?

  • 过滤条件:哪些文档要加分
  • 算分函数:如何计算function score
  • 加权方式:function score 与 query score如何运算

1.1中的例子可以看出彭猛排在陈楠后面,如果彭猛花钱了,想排在前面,怎么解决?

GET /biaoxun/_search
{
  "query": {
    "function_score": {           
      "query": { // 原始查询,可以是任意条件,基于BM25算法,得出原始算分query_score
          "match": {
            "jineng": "吃饭"
          }
        },
      "functions":[//算分函数,复合条件的才会加分
        {
          
          "filter":{ //过滤条件,【这里是加分条件】,得出函数算分function_score
            "match":{
              "name":"彭猛"
            }
          },
          "weight":2//算分权重为2
        }
      ],
      "boost_mode": "sum" // 加权模式,求和 = function_score + query_score
    }
  }  
}

运行后会发现彭猛排在前面了
image.png function score 查询中包含四部分内容:

  • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)

  • 过滤条件:filter部分,符合该条件的文档才会重新算分

  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数

    • weight:函数结果是常量
    • field_value_factor:以文档中的某个字段值作为函数结果
    • random_score:以随机数作为函数结果
    • script_score:自定义算分函数算法
  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:

    • multiply:相乘
    • replace:用function score替换query score
    • 其它,例如:sum、avg、max、min

bool查询

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

注意:尽量在筛选的时候多使用不参与算分的must_not和filter,以保证性能良好,搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分
GET /biaoxun/_search
//查询技能含吃饭,年龄大于20小于35的人
{
  "query": {
    "bool": {
      "must": [{
        "match": {
          "jineng": "吃饭"
        }
      }],
      "filter": [
        {
          "range": {
            "age": {
              "gte": 20,
              "lte": 35
            }
          }
        }
      ]
    }
  }
}

image.png

设置搜索结果

可以按照用户的想法设置返回结果,查询的DSL是一个大的JSON对象,包含下列属性:

  • query:查询条件
  • from和size:分页条件
  • sort:排序条件
  • highlight:高亮条件
  • aggs:定义聚合

高亮&&排序

GET /biaoxun/_search
{
  "query":{
    "match":{
      "jineng": "牛皮旅游" // 分词为牛皮和旅游
    }
  },
  "from":0,//从第几个文档开始,默认为0
  "size":10,//期望获取的文档总数
  "sort":[
    {"age":"desc"} //排序字段、排序方式升序ASC、降序DESC
    ],
    "highlight": {
      "fields": {
        "jineng":{ //【要和上面的查询字段FIELD一致】
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
        }
      }
    }
}

image.png

聚合

注意: 参加聚合的字段必须是keyword、日期、数值、布尔类型

可以让我们极其方便的实现对数据的统计、分析、运算;比如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?

聚合常见的有三类

  • 桶(Bucket) 聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • 度量(Metric) 聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline) 聚合:其它聚合的结果为基础做聚合

    • 如:用桶聚合实现种类排序,然后使用度量聚合实现各个桶的最大值、最小值、平均值等

//查询年龄大于30并且小于55的,按照city降序聚合
GET /biaoxun/_search
{
  "query": {
    "range": {
      "age": { // 年龄小于55,大于30
        "gte": 30,
        "lte": 55
      }
    }
  },
  "size": 0,  // 设置size为0,结果中不包含查询结果文档,只包含聚合结果,
  "aggs": {
    "ageGroupTtems": {//给聚合起个名字
      "terms": {
        "field": "city",
        "order": {
          "_count": "desc" // 对聚合结果降序排列
        },
        "size": 20 // 希望获取的聚合结果数量【设置多少就最多只显示多少】
      }
    }
  }
}

image.png

未完待续...