全文检索elasticsearch入门,看这篇就够了

图片

一、elasticsearch介绍

1、背景

在订单管理系统中,订单查询的调用量都非常大,如果直接查询数据库,那数据库的压力可想而知,而且有时需要执行一些复杂的查询,sql 并不能够友好的支持,需要查询很多张表。再比如用户手误输入的关键词错了或存在错别字,那使用 sql 是无法搜索到。所以打算使用 Elasticsearch 来承载订单查询的主要压力。

总的来说,使用 elasticsearch,以下简称es 的几个原因如下

  • 关系型数据库在进行模糊(%关键字%)搜索的时候,会全表扫描,查询非常慢
  • 关系型数据库在关键字搜索时,并不支持全文分词搜索,比如用户本打算搜索:公众号-臻大虾,却手误:公号-臻大虾,es 可以根据分词的结果搜索出想要的结果。
  • 在数据分析、日志分析上用到 es

2、es 基本概念

Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。

Elasticsearch 是文件存储,Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档,用 JSON 作为文档序列化的格式,比如下面这条用户数据:

{
    "name":"臻大虾",
    "sex":0,
    "age":24
}

3、es 优势

  • 分布式:横向可扩展性,增加服务器可直接配置在集群中
  • 高可用:提供了复制功能,具有容错机制,能自动发现新的或失败的节点,重组和重新平衡节点数据
  • 实时性:数据进入 es,可达到近实时搜索
  • Restful api:json 格式的 RESTful 风格
  • 全文检索:基于 lucene 的强大的全文检索能力

4、使用场景

全文检索

当我们使用百度搜索、谷歌搜索时,输入关键字,就能搜索到最相关的文章,这就是利用了 es 强大的全文检索的能力。

用户行为

平时淘宝买东西时,你是否发现推荐的商品跟你最近搜索的关键词很享受,这就是通过收集用户的行为日志,分析并建立用户模型,保存在 es 中,并利用 es 强大的深入搜索和聚合的能力,可以更好的分析和展示用户的行为数据。例如推荐系统,就是利用用户模型的用户数据,对用户数据交叉查询,分析出用户细粒度的喜好。

监控系统

利用 es 高性能查询的特性,收集系统的监控数据,近实时展现监控数据,同时也方便用户对监控数据进行关键字排查。

日志系统

常用的方案是 ELK(elasticsearch+logstash+kibana),利用 logstash 去收集 logback 的日志信息,再通过 es 做存储,最后可以再 kibana 去利用 es api 查看和分析日志的相关信息。

5、es 的核心概念

索引(index)

索引是 es 最大的数据单元,类似于关系型数据库中的库,是多个相似文档的集合。每个索引有一个或多个分片,每个分片有多个副片。

文档(document)

一条数据就是一个文档,类似于数据库表中的一条记录,比如:

{
    "name":"臻大虾",
    "sex":0,
    "age":24
}

字段(field)

文档的属性,类似表中的字段,比如如下 json 的健:name

{
    "name":"臻大虾"
}

映射(mapping)

映射是对文档中每个字段类型进行定义,类似表结构,包含数据类型,长度之类的,比如:

"mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "long"
            }
        }
    }

分片(Shards)

在创建索引时,可以设置主分片个数和副本个数,类似数据库的分表,将单个索引文件分成多份存储,当请求过来时,通过路由计算找到主分片(hash(字段,比如 id)%分主片数量)。

好处:

  • 如果一个索引数据量很大,会造成硬盘和搜索速度的瓶颈,分片能分担压力
  • 分片允许我们进行水平切分和扩展容量
  • 可以在多个分片上进行分布式的、并行的操作,提高系统吞吐量
注意:主分片在创建之后是无法修改的,而副本可以随时修改。那想修改主分片的数量怎么办呢,删除重新建。
"settings": {
    "number_of_shards": 2,//主分片
    "number_of_replicas": 1//副本
}

副本(Replicas)

由主分片复制来的,提供高可用

好处:

  • 高可用,当一个主分片挂了,副本可以代替工作
  • 副本也可以执行搜索操作,分摊了主分片的压力

集群(Cluster)

一个集群就是由一个或多个节点组织在一起,具有相同集群名的节点才能组成一个集群。它们共同持有整个的数据,并一起提供索引和搜索功能。

注意:主分片和副本处于不同节点,这样当主分片的机器挂了,副本由于在不同机器上,不会受到影响,副本变为主分片继续工作。所以 es 最小的高可用配置为两台服务器

节点(node)

单个 es 实例称为一个节点(node),一个节点是集群中的一个服务器,作为集群的一部分,存储数据。

类型(type)

7.x 移除了 type,8.x 将彻底移出

图片

image-20210920183804825

二、索引原理

es 使用的是倒排索引也叫反向索引,既然有倒排索引,那是不是有正排索引,有的,我们先介绍下正排索引。

1、正排索引

正排索引是以文档的 ID 为关键字,文档中每个字段的值为 value,主要场景是通过 id 获取文档信息,平时用的 msyql 关系型数据库就是以这种方式查询的。

举个例子

id 内容
1 my name is zhendaxia
2 my name is jack

通过 id 可以很快查询到内容,但是当查询比如 name 的时候,需要使用 like,再加上数据量大的时候,查询的时间是很久的,无法满足查询快速的要求。

2、倒排索引

倒排索引是以字或词为关键字进行索引,记录出现这个关键词的文档的 ID

比如上面的例子使用倒排索引如下:

content docid
my 1,2
name 1,2
is 1,2
zhendaxia 1
jack 2

倒排索引,通过字或词快速的找到所有文档的 id,在根据文档 id 能快速找到内容。由于人类的词汇数量是相对有限且固定的,所以效率并不会由于日后关键词的增长而受到很大的影响。

三、集群扩容

1、集群健康

图片

image-20210920221154778

集群的健康状态有三种:绿色 green、黄色 yellow、红色 red

绿色(健康):所有的主分片和副分片都正常运行

黄色(亚健康):所有主分片正常运行,但有副分片没正常运行

红色(不健康):有主分片没正常运行

2、扩容

扩容一般分为两种,垂直和水平

1)、垂直扩容

升级服务器,买性能更好的服务器替换原有的服务器,不过这种扩容不推荐,毕竟单台机器的性能总是有瓶颈的

2)、水平扩容

水平扩容也叫横向扩容,就是增加服务器数量,多台普通的服务器组织在一起形成强大的计算能力。俗话说:团结就是力量。

四、浏览器插件

head 插件是 ES 的一个可视化插件,类似于 navicat 和 mysql 的关系。head 插件是一个用来浏览、与 ES 数据进行交互的 web 前端展示插件,是一个用来监视 ES 状态的客户端插件。

以下是插件的一些简单介绍

图片

image-20210920225224119

五、常用 api

1、创建索引

PUT /index
{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "text_name": {
                "type": "text"
            },
            "keyword_name": {
                "type": "keyword"
            },
            "english_name": {
                "type": "text",
                "fields": {
                    "keyword": {
                        "type": "keyword"
                    }
                }
            },
            "age": {
                "type": "long"
            },
            "classId": {
                "type": "long"
            },
            "score": {
                "type": "long"
            },
            "createTime": {
                "type": "long"
            }
        }
    }
}

当看到请求体时,细心的你可能会发现 text\_name、keyword\_name、english\_name 这三个字段都是字符串,但类型好像有些不同,区别是什么呢?是的,这几个类型往往是刚接触 es 的新手经常弄错的地方。

首先,看下 text 和 keyword 的区别

text:可以分词,用户全文搜索,可以模糊匹配搜索

keyword:不能分词,关键词搜索,只能对某个值进行整体搜索

type 是 text,但有 fields-keyword:这种类型,一种是自己加入的,另一种是在往 es 插入数据的时候,字段 english\_name 还没有创建。

这时 es 会根据数据类型,自动帮你创建一个字段,如果是字符串类型,由于无法判断你的这个字符串你是用来精确查询还是模糊查询,所以 es 会创建类型是 text,支持模糊查询,同时会创建 fields,type 是 keyword,支持精确查询,所以当你要精确查询的时候,字段名就不是原来的 english\_name,而是要使用 english\_name.keyword

举个例子来说明下,首先插入了以下数据,关键字 zhen

{
    "text_name": "zhen daxia",
    "keyword_name": "zhen daxia",
    "english_name": "zhen daxia",
    "age": 18,
    "classId": 2,
    "score": 90,
    "createTime": 1629353892784
}
  • 查询 text\_name,由于 text\_name 类型是 text,会讲 zhen daxia 分词为 zhen、daxia,所以当使用 zhen 查询时,能匹配到 zhen,所以会有结果返回.
如何查看 zhen daxia 被分为哪些词语,可以使用 GET 你的索引/\_doc/数据 id/\_termvectors?fields=字段名,比如我的索引是 test-user,那语句就是:GET test-user/\_doc/1/\_termvectors?fields=text\_name
GET test-user/_search
{
  "query": {
   "term": {
     "text_name": {
       "value": "zhen"
     }
   }
  }
}

图片

image-20210920232241959

  • 查询 keyword\_name,由于 keyword\_name 类型是 keyword,不会分词,所以 zhen 无法搜索到数据

图片

image-20210920232950219

  • 查询 english\_name,同 text\_name,可以搜到

图片

image-20210920233814208

  • 查询 english\_name.keyword,同 keyword\_name,无法搜索到结果

图片

image-20210920233927960

2、增加映射字段

PUT /index/_mapping
{
    "properties":{
        "keyword-name":{
            "type":"keyword"
        }
    }
}

3、查询

GET test-user/_search

3.1 match(全文检索)

全文检索,会分词,模糊查询,比如关键字 zhen daxia,会被拆为 zhen、daxia

{
  "query": {
    "match": {
      "text_name": "zhen daxia"
    }
  }
}

spring boot 方法

boolQueryBuilder.filter(QueryBuilders.matchQuery("text_name", "zhen daxia"));

3.2 term(精确查询)

精确查询,不会拆词,比如关键字 zhen daxia,会直接使用 zhen daxia 搜索

{
  "query": {
    "term": {
      "keyword_name": {
        "value": "zhen daxia"
      }
    }
  }
}

spring boot 方法

QueryBuilders.termQuery("keyword_name", "zhen daxia");

3.3 terms(多值匹配)

和 term 查询一样,但它允许你指定多值进行匹配,如果这个字段包含了指定值中的任何一个值,那么这个文档就算是满足条件。类似 mysql 的 in

{
  "query": {
   "terms": {
     "keyword_name": [
       "zhen",
       "daxia"
     ]
   }
  }
}

spring boot 方法

QueryBuilders.termsQuery("keyword_name", Lists.newArrayList("zhen","daxia"));

3.4 range(范围查询)

范围查询,比如搜索大于等于 20 且小于等于 30 的数据

{
  "query": {
    "range": {
      "age": {
        "gte": 20,   # 大于等于  大于用 gt
        "lte": 30    # 小于等于  小于用 lt
      }
    }
  }
}

spring boot 方法

RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
rangeQueryBuilder.gte(20);
rangeQueryBuilder.lte(30);

3.5 prefix(前缀查询)

前缀查询,比如搜索 zhen,则前缀是 zhen 的都会被搜索出来

{
  "query": {
    "prefix": {
      "keyword_name": {
        "value": "zhen"
      }
    }
  }
}

spring boot 方法

QueryBuilders.prefixQuery("keyword_name","zhen");

3.6 wildcard(通配符模糊查询)

通配符模糊查询,类似 mysql 的 like,?匹配一个字符,*匹配 0~n 个字符

{
  "query": {
    "wildcard": {
      "keyword_name": {
        "value": "*大虾"
      }
    }
  }
}

spring boot 方法

QueryBuilders.wildcardQuery("keyword_name","*大虾")

3.7 fuzzy(模糊查询,不精确查询)

不同于 mysql 的 like,它可以错误一些字,比如搜索 mock,可以搜索出 mick

{
  "query": {
    "fuzzy": {
      "keyword_name": "mock"
    }
  }
}

spring boot 方法

QueryBuilders.fuzzyQuery("keyword_name","mock");

3.8 must、must not、should

//must:必须
boolQueryBuilder.must(QueryBuilders.termQuery("keyword_name","mick"));

//must not:非
boolQueryBuilder.mustNot(QueryBuilders.termQuery("keyword_name","mick"));

//should:类似mysql的或
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","jack"));
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","mick"));

3.9 match all(查询全部)

查询全部,默认 10 条

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(matchAllQueryBuilder);
sourceBuilder.size(10);

3.10 match\_phrase

  • 分词后,待查询的字段同时匹配分词后的所有关键词
  • 顺序也是一样

比如有以下数据:

1. keyword_name:zhen daxia
2. keyword_name:daxia zhen
3. keyword_name:I am zhen daxia
4. keyword_name:daxia haha

查询 zhen daxia,则返回 1 和 3,2:顺序不对,4:没有匹配到全部分词

可通过 slp 调节因子,比如 1,少匹配一个也满足

{
  "query": {
    "match_phrase": {
     "keyword_name": {
       "query": "zhen daxia",
       "slop": 1
     }
    }
  }
}

3.11 multi\_match(多字段匹配)

多字段匹配,有一个字段匹配,就满足,keyword\_name=jack,或 english\_name=jack,就算满足

{
  "query": {
   "multi_match": {
     "query": "jack",
     "fields": ["keyword_name","english_name"]
   }
  }
}j

3.12 filter 和 must(过滤)

filter 与 must 是属于同一个级别的查询方式,都可以作为 query->bool 的属性 filter:不计算评分, 查询效率高;有缓存(推荐) must:要计算评分,查询效率低;无缓存

3.13 聚合查询(聚合)

根据名字分组

builder.aggregation(AggregationBuilders.terms("agg").field("keyword_name").size(10));

关注公众号:臻大虾,分享更多java后端干货

你的支持是对我不断创作的极大鼓励,咱们下期见。

你可能感兴趣的:(全文检索elasticsearch入门,看这篇就够了)