一、概述
Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。 Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库—无论是开源还是私有。ES使用 Java 编写的,它的内部使用 Lucene 做索引与搜索。
特点
- 一个分布式的实时文档存储,每个字段 可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
非常好的教程推荐:https://www.tizi365.com/archives/590.html
1.存储结构与数据类型
类比MYSQL存储结构
Elasticsearch存储结构 | MYSQL存储结构 |
---|---|
index(索引) | 表 |
Document(文档) | 行,一行数据 |
Field(字段) | 表字段 |
mapping (映射) | 表结构定义 |
index(索引):相当于 mysql 的表
Document(文档):相当于表中的每一条记录,这里都是json数据
Field(文档字段):相当于表中的字段
- 字符串:主要包括: text和keyword两种类型,keyword代表精确值不会参与分词,text类型的字符串会参与分词处理。
- 数值:包括: long, integer, short, byte, double, float
- 布尔值:boolean
- 时间:date
- 数组:数组类型不需要专门定义,只要插入的字段值是json数组就行。
- GEO类型:主要涉及地理信息检索、多边形区域的表达,后面GEO相关的章节单独讲解
mapping (映射):Elasticsearch的mapping (映射)类似mysql中的表结构定义,每个索引都有一个映射规则,我们可以通过定义索引的映射规则,提前定义好文档的json结构和字段类型,如果没有定义索引的映射规则,Elasticsearch会在写入数据的时候,根据我们写入的数据字段推测出对应的字段类型,相当于自动定义索引的映射规则。
2.文档元数据
文档元数据,指的是插入JSON文档的时候,Elasticsearch为这条数据,自动生成的系统字段。
元数据的字段名都是以下划线开头的。
常见的元数据如下:
- _index - 代表当前JSON文档所属的文档名字
- _type - 代表当前JSON文档所属的类型,虽然新版ES废弃了type的用法,但是元数据还是可以看到。
- _id - 文档唯一Id, 如果我们没有为文档指定id,系统会自动生成
- _source - 代表我们插入进去的JSON数据
- _version - 文档的版本号,每修改一次文档数据,字段就会加1, 这个字段新版的ES已经不使用了
_seq_no
- 文档的版本号, 替代老的_version
字段- _
primary_term
- 文档所在主分区,这个可以跟_seq_no
字段搭配实现乐观锁。
二、查询语法
1.查询基本语法结构
GET /{索引名}/_search
{
"from" : 0, // 返回搜索结果的开始位置
"size" : 10, // 分页大小,一次返回多少数据
"_source" :[ ...需要返回的字段数组... ],
"query" : { ...query子句... },
"aggs" : { ..aggs子句.. },
"sort" : { ..sort子句.. }
}
query子句:主要用来编写类似SQL的Where语句,支持布尔查询(and/or)、IN、全文搜索、模糊匹配、范围查询(大于小于)。
aggs子句:主要用来编写统计分析语句,类似SQL的group by语句
sort子句:用来设置排序条件,类似SQL的order by语句
分页:主要通过from和size参数设置,类似MYSQL 的limit和offset语句
_source:用于设置查询结果返回什么字段,类似Select语句后面指定字段
2.Query查询
2.1匹配单个字段-match
通过match实现全文搜索,全文搜索的后面有单独的章节讲解,这里大家只要知道简单的用法就可以。
GET /{索引名}/_search
{
"query": {
"match": {
"{FIELD}": "{TEXT}"
}
}
}
- {FIELD} - 就是我们需要匹配的字段名
- {TEXT} - 就是我们需要匹配的内容
2.2精确匹配单个字段-term
如果我们想要类似SQL语句中的等值匹配,不需要进行分词处理,例如:订单号、手机号、时间字段,不需要分值处理,只要精确匹配。
通过term实现精确匹配语法:
GET /{索引名}/_search
{
"query": {
"term": {
"{FIELD}": "{VALUE}"
}
}
}
- {FIELD} - 就是我们需要匹配的字段名
- {VALUE} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。
2.3实现SQL的in语句-terms
如果我们要实现SQL中的in语句,一个字段包含给定数组中的任意一个值就匹配。
GET /order_v2/_search
{
"query": {
"terms": {
"{FIELD}": [
"{VALUE1}",
"{VALUE2}"
]
}
}
}
- {FIELD} - 就是我们需要匹配的字段名
- {VALUE1}, {VALUE2} … {VALUE N} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。
2.4范围查询
通过range实现范围查询,类似SQL语句中的>, >=, <, <=表达式。
GET /{索引名}/_search
{
"query": {
"range": {
"{FIELD}": {
"gte": 10,
"lte": 20
}
}
}
}
- {FIELD} - 字段名
- gte范围参数 - 等价于>=
- lte范围参数 - 等价于 <=
- 范围参数可以只写一个,例如:仅保留 “gte”: 10, 则代表 FIELD字段 >= 10
范围参数如下:
- gt - 大于 ( > )
- gte - 大于且等于 ( >= )
- lt - 小于 ( < )
- lte - 小于且等于 ( <= )
2.5bool组合查询
2.5.1bool查询基本语法结构
在ES中bool查询就是用来组合布尔查询条件,布尔查询条件,就是类似SQL中的and (且)、or (或)
前面的例子都是设置单个字段的查询条件,如果需要编写类似SQL的Where语句,组合多个字段的查询条件,可以使用bool语句。
GET /{索引名}/_search
{
"query": {
"bool": { // bool查询
"must": [], // must条件,类似SQL中的and, 代表必须匹配条件
"must_not": [], // must_not条件,跟must相反,必须不匹配条件
"should": [] // should条件,类似SQL中or, 代表匹配其中一个条件
}
}
}
可以任意选择must、must_not和should条件的参数都是一个数组,意味着他们都支持设置多个条件
2.5.2must条件
类似SQL的and,代表必须匹配的条件。
GET /{索引名}/_search
{
"query": {
"bool": {
"must": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}
2.5.3must_not条件
跟must的作用相反。
GET /{索引名}/_search
{
"query": {
"bool": {
"must_not": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}
2.5.4should条件
类似SQL中的 or, 只要匹配其中一个条件即可
GET /{索引名}/_search
{
"query": {
"bool": {
"should": [
{匹配条件1},
{匹配条件2},
…可以有N个匹配条件…
]
}
}
}
3.全文搜索
3.1默认全文搜索
默认情况下,使用全文搜索很简单,只要将字段类型定义为text类型,然后用match语句匹配即可
ES对于text类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档,当我们搜索text类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。
ES默认的分词器是standard,对英文比较友好,例如:hello world 会被分解成 hello和world两个关键词,如果是中文会分解成一个一个字,例如:上海大学 会分解成: 上、海、大、学。
3.2中文分词器
ES默认的analyzer(分词器),对英文单词比较友好,对中文分词效果不好。不过ES支持安装分词插件,增加新的分词器。
默认的分词器不满足需要,可以在定义索引映射的时候,指定text字段的分词器(analyzer)。
PUT /article
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "smartcn"
}
}
}
}
只要在定义text字段的时候,增加一个analyzer配置,指定分词器即可,这里指定的分词器是smartcn。
smartcn分词器:smartcn是目前ES官方推荐的中文分词插件,不过目前不支持自定义词库。
ik分词器:ik支持自定义扩展词库,有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容,例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会做为一个整体被切割出来。
4. 排序
ES的默认排序是根据相关性分数排序,如果我们想根据查询结果中的指定字段排序,需要使用sort Processors处理。
GET /{索引名}/_search
{
"query": {
...查询条件....
},
"sort": [
{
"{Field1}": { // 排序字段1
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
},
{
"{Field2}": { // 排序字段2
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
}
....多个排序字段.....
]
}
sort子句支持多个字段排序,类似SQL的order by。
三、聚合分析
ES中的聚合查询,类似SQL的sum/avg/count/GROUP BY分组查询,主要用于统计分析场景。一般统计分析主要分为两个步骤:分组
、组内聚合。
核心概念
-
桶:满足特定条件的文档的集合。桶的就是一组数据的集合,对数据分组后,得到一组组的数据,就是一个个的桶。
提示:桶等同于组,分桶和分组是一个意思,ES使用桶代表一组相同特征的数据。
-
指标:对文档进行统计计算方式,又叫指标聚合。桶内聚合,说的就是先对数据进行分组(分桶),然后对每一个桶内的数据进行指标聚合。说白了就是,前面将数据经过一轮桶聚合,把数据分成一个个的桶之后,我们根据上面计算指标对桶内的数据进行统计。
常用的指标有:SUM、COUNT、MAX等统计函数。
1.指标聚合
ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用。
1.1 value_count 值聚合
主要用于统计文档总数,类似SQL的count函数。
GET /sales/_search?size=0
{
"aggs": {
"types_count": { // 聚合查询的名字,随便取个名字
"value_count": { // 聚合类型为:value_count
"field": "type" // 计算type这个字段值的总数
}
}
}
}
等价SQL:
select count(type) from sales
返回结果:
{
...
"aggregations": {
"types_count": { // 聚合查询的名字
"value": 7 // 统计结果
}
}
}
1.2 cardinality 基数聚合
基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : { // 聚合查询的名字,随便取一个
"cardinality" : { // 聚合查询类型为:cardinality
"field" : "type" // 根据type这个字段统计文档总数
}
}
}
}
等价SQL:
select count(DISTINCT type) from sales
返回结果:
{
...
"aggregations" : {
"type_count" : { // 聚合查询的名字
"value" : 3 // 统计结果
}
}
}
1.3avg/sum/max/min
-
avg - 求平均值
-
sum - 求和
-
max - 求最大值
-
min - 求最小值
这些函数的使用形式和结果形式都和上面是类似的,仅需替换函数就好。
2.分组统计
数据分组在es中叫做桶聚合。桶聚合一般不单独使用,都是配合指标聚合一起使用,对数据分组之后肯定要统计桶内数据,在ES中如果没有明确指定指标聚合,默认使用value_count
指标聚合,统计桶内文档总数。
2.1Terms聚合(唯一值分组)
terms聚合的作用跟SQL中group by作用一样,都是根据字段唯一值对数据进行分组(分桶),字段值相等的文档都分到同一个桶内。
GET /order/_search?size=0
{
"aggs": {
"shop": { // 聚合查询的名字,随便取个名字
"terms": { // 聚合类型为: terms
"field": "shop_id" // 根据shop_id字段值,分桶
}
}
}
}
等价SQL:
select shop_id, count(*) from order group by shop_id
返回结果:
{
...
"aggregations" : {
"shop" : { // 聚合查询名字
"buckets" : [ // 桶聚合结果,下面返回各个桶的聚合结果
{
"key" : "1", // key分桶的标识,在terms聚合中,代表的就是分桶的字段值
"doc_count" : 6 // 默认的指标聚合是统计桶内文档总数
},
{
"key" : "5",
"doc_count" : 3
},
{
"key" : "9",
"doc_count" : 2
}
]
}
}
}
2.2Histogram聚合(数值间隔分组)
histogram(直方图)聚合,主要根据数值间隔分组,使用histogram聚合分桶统计结果,通常用在绘制条形图报表。
POST /sales/_search?size=0
{
"aggs" : {
"prices" : { // 聚合查询名字,随便取一个
"histogram" : { // 聚合类型为:histogram
"field" : "price", // 根据price字段分桶
"interval" : 50 // 分桶的间隔为50,意思就是price字段值按50间隔分组
}
}
}
}
返回结果:
{
...
"aggregations": {
"prices" : { // 聚合查询名字
"buckets": [ // 分桶结果
{
"key": 0.0, // 桶的标识,histogram分桶,这里通常是分组的间隔值
"doc_count": 1 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 100.0,
"doc_count": 0
},
{
"key": 150.0,
"doc_count": 2
}
]
}
}
}
2.3date_histogram聚合(时间间隔分组)
类似histogram聚合,区别是Date histogram可以很好的处理时间类型字段,主要用于根据时间、日期分桶的场景。
POST /sales/_search?size=0
{
"aggs" : {
"sales_over_time" : { // 聚合查询名字,随便取一个
"date_histogram" : { // 聚合类型为: date_histogram
"field" : "date", // 根据date字段分组
"calendar_interval" : "month", // 分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
"format" : "yyyy-MM-dd" // 设置返回结果中桶key的时间格式
}
}
}
}
返回结果:
{
...
"aggregations": {
"sales_over_time": { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key_as_string": "2015-01-01", // 每个桶key的字符串标识,格式由format指定
"key": 1420070400000, // key的具体字段值
"doc_count": 3 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key_as_string": "2015-02-01",
"key": 1422748800000,
"doc_count": 2
},
{
"key_as_string": "2015-03-01",
"key": 1425168000000,
"doc_count": 2
}
]
}
}
}
2.4Range聚合(数值范围分组)
range聚合,按数值范围分桶。
GET /_search
{
"aggs" : {
"price_ranges" : { // 聚合查询名字,随便取一个
"range" : { // 聚合类型为: range
"field" : "price", // 根据price字段分桶
"ranges" : [ // 范围配置
{ "to" : 100.0 }, // 意思就是 price <= 100的文档归类到一个桶
{ "from" : 100.0, "to" : 200.0 }, // price>100 and price<200的文档归类到一个桶
{ "from" : 200.0 } // price>200的文档归类到一个桶
]
}
}
}
}
返回结果:
{
...
"aggregations": {
"price_ranges" : { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key": "*-100.0", // key可以表达分桶的范围
"to": 100.0, // 结束值
"doc_count": 2 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": "100.0-200.0",
"from": 100.0, // 起始值
"to": 200.0, // 结束值
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
}
大家仔细观察的话,发现range分桶,默认key的值不太友好,尤其开发的时候,不知道key长什么样子,处理起来比较麻烦,我们可以为每一个分桶指定一个有意义的名字。
GET /_search
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"keyed" : true,
"ranges" : [
// 通过key参数,配置每一个分桶的名字
{ "key" : "cheap", "to" : 100 },
{ "key" : "average", "from" : 100, "to" : 200 },
{ "key" : "expensive", "from" : 200 }
]
}
}
}
}
2.5综合例子
前面的例子,都是单独使用aggs聚合语句,代表直接统计所有的文档,实际应用中,经常需要配合query语句,先搜索目标文档,然后使用aggs聚合语句对搜索结果进行统计分析。聚合查询支持多层嵌套
GET /cars/_search
{
"size": 0, // size=0代表不需要返回query查询结果,仅仅返回aggs统计结果
"query" : { // 设置查询语句,先赛选文档
"match" : {
"make" : "ford"
}
},
"aggs" : { // 然后对query搜索的结果,进行统计
"colors" : { // 聚合查询名字
"terms" : { // 聚合类型为:terms 先分桶
"field" : "color"
},
"aggs": { // 通过嵌套聚合查询,设置桶内指标聚合条件
"avg_price": { // 聚合查询名字
"avg": { // 聚合类型为: avg指标聚合
"field": "price" // 根据price字段计算平均值
}
},
"sum_price": { // 聚合查询名字
"sum": { // 聚合类型为: sum指标聚合
"field": "price" // 根据price字段求和
}
}
}
}
}
}
3.多桶排序
类似terms、histogram、date_histogram这类桶聚合都会动态生成多个桶,如果生成的桶特别多,我们如何确定这些桶的排序顺序,如何限制返回桶的数量。默认情况,ES会根据doc_count文档总数,降序排序。ES桶聚合支持两种方式排序:内置排序
和 按度量指标排序
。
3.1多桶排序
3.1.1内置排序
- _count - 按文档数排序。对 terms 、 histogram 、 date_histogram 有效
- _term - 按词项的字符串值的字母顺序排序。只在 terms 内使用
- _key - 按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字,随便取一个
"terms" : { // 聚合类型为: terms
"field" : "color",
"order": { // 设置排序参数
"_count" : "asc" // 根据_count排序,asc升序,desc降序
}
}
}
}
}
3.1.2按度量排序
通常情况下,我们根据桶聚合分桶后,都会对桶内进行多个维度的指标聚合,所以我们也可以根据桶内指标聚合的结果进行排序。
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字
"terms" : { // 聚合类型: terms,先分桶
"field" : "color", // 分桶字段为color
"order": { // 设置排序参数
"avg_price" : "asc" // 根据avg_price指标聚合结果,升序排序。
}
},
"aggs": { // 嵌套聚合查询,设置桶内聚合指标
"avg_price": { // 聚合查询名字,前面排序引用的就是这个名字
"avg": {"field": "price"} // 计算price字段平均值
}
}
}
}
}
3.2限制返回桶的数量
如果分桶的数量太多,可以通过给桶聚合增加一个size参数限制返回桶的数量。
GET /_search
{
"aggs" : {
"products" : { // 聚合查询名字
"terms" : { // 聚合类型为: terms
"field" : "product", // 根据product字段分桶
"size" : 5 // 限制最多返回5个桶
}
}
}
}
四、Java快速接入ES
这里使用的es的版本为7.x
1.依赖与配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.4.2</version>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwMiuPOF-1663066410029)(%E8%BD%AF%E4%BB%B6%E7%B3%BB%E7%BB%9F-7.ES.assets/image-20220902144006346.png)]
es的版本差异不同,其结构发生很多变化,这里仅以其中一个版本介绍。
spring:
elasticsearch:
rest:
uris: 127.0.0.1:9200
2.使用
Elasticsearch从6.x
升级到7.x
改动还真不是一般的大,ElasticsearchTemplate不建议使用了,改为使用ElasticsearchRestTemplate,ElasticsearchRepository实现复杂查询的方法也不建议使用了。从此我们简单的数据操作可以使用ElasticsearchRepository,而复杂的数据操作只能使用ElasticsearchRestTemplate了。
public interface EsRepository extends ElasticsearchRepository<UserDocument, Long> {
}
五、Java整合ES的注意
1.@Document
一般使用@Document(indexName = "technical_es_test", useServerConfiguration = true, createIndex = false)
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
//索引库名称
String indexName();
//类型
String type() default "";
//
boolean useServerConfiguration() default false;
//默认分片数5
short shards() default 5;
//默认副本数1
short replicas() default 1;
//刷新间隔
String refreshInterval() default "1s";
//索引文件存储类型
String indexStoreType() default "fs";
//是否创建索引
boolean createIndex() default true;
}
2.@Field 与 枚举类FieldType
作用于document文档的实体类
@Id:作用在成员变量,标记一个字段为id主键;一般id字段或是域不需要存储也不需要分词;
@Field作用在成员变量,标记为文档的字段,并制定映射属性;一般使用@Field(type = FieldType.Long, store = true)
@Field
(1)type:字段的类型,取值是枚举,FieldType;
(2)index:是否索引,布尔值类型,默认是true;
(3)store:是否存储,布尔值类型,默认值是false;
(4)analyzer:分词器名称
FieldType
public enum FieldType {
Text,
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword;
private FieldType() {
}
}
【 @Field(type = FieldType.Keyword)和 @Field(type = FieldType.Text)区别】
在早期elasticsearch5.x之前的版本存储字符串只有string字段;但是在elasticsearch5.x之后的版本存储了Keyword和Text,都是存储字符串的。FieldType.Keyword存储字符串数据时,不会建立索引;而FieldType.Text在存储字符串数据的时候,会自动建立索引,也会占用部分空间资源。
3.布尔查询 BoolQueryBuilder
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
- must子句:文档必须匹配must查询条件;
- should子句:文档应该匹配should子句查询的一个或多个;
- must_not子句:文档不能匹配该查询条件;
- filter子句:过滤器,文档必须匹配该过滤条件,跟must子句的唯一区别是,filter不影响查询的score;
filter子句中可以再传入一个BoolQueryBuilder,通过QueryBuilders的下列方式进行匹配。
termQuery("key", obj); //完全匹配
termsQuery("key", obj1, obj2..); //一次匹配多个值
matchQuery("key", Obj); //单个匹配, field不支持通配符, 前缀具高级特性
multiMatchQuery("text", "field1", "field2"..); //匹配多个字段, field有通配符忒行
matchAllQuery(); //匹配所有文件
在布尔查询中:
各个子句之间的逻辑关系是与(and)。对于单个子句,只要一个文档满足该子句的查询条件,返回的逻辑结果就是true。
对于should子句,它一般包含多个子查询条件,参数 minimum_should_match 控制文档必须满足should子句中的子查询条件的数量,只有当文档满足指定数量的should查询条件时,should子句返回的逻辑结果才是true。
参考:https://www.cnblogs.com/ljhdo/p/5040252.html