一、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)

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

节点(node)

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

类型(type)

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

image-20210920183804825

二、索引原理

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

1、正排索引

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

举个例子

1my name is zhendaxia
2my name is jack

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

2、倒排索引

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

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

my1,2
name1,2
is1,2
zhendaxia1
jack2

倒排索引,通过字或词快速的找到所有文档的 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,所以会有结果返回.
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后端干货

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

03-05 16:02