欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ElasticSearch

程序员文章站 2022-07-01 09:54:26
...

使用elasticsearch

启动 Elasticsearch:

cd elasticsearch-<version>
./bin/elasticsearch

Apache Lucene

全文检索(Full-text Search)

我们生活中的数据总体分为两种:结构化数据和非结构化数据。

  • 结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
  • 非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等。
    当然有的地方还会提到第三种,半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。

非结构化数据又一种叫法叫全文数据。

按照数据的分类,搜索也分为两种:

  • 对结构化数据的搜索:如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。
  • 对非结构化数据的搜索:如利用windows的搜索也可以搜索文件内容,Linux下的grep命令,再如用Google和百度可以搜索大量内容数据。
  1. 顺序扫描法(Serial Scanning):所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。

  2. 全文检索大体分两个过程,索引创建(Indexing)和搜索索引(Search)。

    1. 索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
    2. 搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。

基本概念

  • 文档(document):索引和搜索时使用的主要数据载体,包含一个或多个存有数据的字段。
  • 字段(field):文档的一部分,包含名称和值两部分。
  • 词(term):一个搜索单元,表示文本中的一个词。
  • 标记(token):表示在字段文本中出现的词,由这个词的文本、开始和结束偏移量以及类
    型组成。

倒排索引

倒排索引建立索引中词和文档之间的映射,数据是面向词而不是面向文档。

ElasticSearch

  • 一个分布式的实时文档存储,每个字段可以被索引与搜索
  • 一个分布式实时分析搜索引擎
  • 能胜任上百个服务节点的扩展,并支持PB级别的结构化或者非结构化数据

为何elasticsearch是准实时的

MySql与ES概念对比

ElasticSearch MySQL
index(索引,名词) database
doc type(文档类型) table
document(文档) row
field(字段) column
mapping(映射) schema
query DSL(查询语言) SQL

基本概念

1)Cluster:集群

ES可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。

2)Node:节点

形成集群的每个服务器称为节点。

3)Shard:分片

当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较小的分片。每个分片放到不同的服务器上。
当你查询的索引分布在多个分片上时,ES会把查询发送给每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。

4)Replia:副本

为提高查询吞吐量或实现高可用性,可以使用分片副本。
副本是一个分片的精确复制,每个分片可以有零个或多个副本。ES中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。
当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。

创建

Elasticsearch数据类型

Elasticsearch自带的数据类型数Lucene索引的依据,也是我们做手动映射调整到依据。

映射中主要就是针对字段设置类型以及类型相关参数。

JSON基础类型如下:

  • 字符串:string
  • 数字:byte、short、integer、long、float、double、
  • 时间:date
  • 布尔值: true、false
  • 数组: array
  • 对象: object

Elasticsearch独有的类型:

  • 多重: multi
  • 经纬度: geo_point
  • 网络地址: ip
  • 堆叠对象: nested object
  • 二进制: binary
  • 附件: attachment

注意点:

Elasticsearch映射虽然有idnex和type两层关系,但是实际索引时是以index为基础的。如果同一个index下不同type的字段出现mapping不一致的情况,虽然数据依然可以成功写入并生成并生成各自的mapping,但实际上fielddata中的索引结果却依然是以index内第一个mapping类型来生成的。

精确索引:
字段都有几个基本的映射选项,类型(type)和索引方式(index)。以字符串类型为例,index有三个选项:

  • analyzed:默认选项,以标准的全文索引方式,分析字符串,完成索引。

  • not_analyzed:精确索引,不对字符串做分析,直接索引字段数据的精确内容。

  • no:不索引该字段。

mapping

mapping是类似于数据库中的表结构定义,主要作用如下:

  • 定义index下的字段名
  • 定义字段类型,比如数值型、浮点型、布尔型等
  • 定义倒排索引相关的设置,比如是否索引、记录position等

Text vs Keyword

数据类型被用来索引长文本,比如说电子邮件的主体部分或者一款产品的介绍。这些文本会被分析,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许 ES来检索这些词语。text 数据类型不能用来排序和聚合。
Keyword 数据类型用来建立电子邮箱地址、姓名、邮政编码和标签等数据,不需要进行分词。可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索。

查询

Apache Lucene评分机制

默认评分机制

TF/IDF(词频/逆文档频率)算法

基本原则

  • 匹配到的关键词越稀有,文档的得分就越高。
  • 文档的域越小(包含比较少的Term),文档的得分就越高。
  • 设置的权重(索引和搜索时设置的都可以)越大,文档得分越高。

分页和结果集大小

  • from:该属性指定我们希望在结果中返回的起始文档。它的默认值是0,表示想要得到从
    第一个文档开始的结果。

  • size:该属性指定了一次查询中返回的最大文档数,默认值为10。如果只对切面结果感兴趣,并不关心文档本身,可以把这个参数设置成0。

基本查询

过滤 is not null

"exists": {
  "field": "gmtCreate"
}

返回版本值

"version": true

query_string和排序

GET /course_audit_aliased/course_audit_aliased/_search
{
  "query": {
    "query_string": {
      "default_field": "courseName",
      "query": "1"
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "lastSubmitReviewTime": {
        "order": "asc"
      }
    }
  ]
}

结构化查询 Query DSL (Domain Specific Language)

term vs match

  • term是精确查询,搜索前不会再对搜索词进行分词
  • match是模糊查询

terms类似mysql的in

"terms" : {
    "price" : [20, 30]
}

multi-match

{
  "dis_max": {
    "queries":  [
      {
        "match": {
          "title": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
      {
        "match": {
          "body": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
    ],
    "tie_breaker": 0.3
  }
}
{
    "multi_match": {
        "query":                "Quick brown fox",
        "type":                 "best_fields", 
        "fields":               [ "title", "body" ],
        "tie_breaker":          0.3,
        "minimum_should_match": "30%" 
    }
}

bool

Bool查询包括四种子句,must,filter,should, must_not。

filter快在两个方面:

  1. 对结果进行缓存
  2. 避免计算分值
{
    "bool" : {
        "must" : {
            "term" : { "user" : "kimchy" }
        },
        "filter": {
            "term" : { "tag" : "tech" }
        },
        "must_not" : {
            "range" : {
                "age" : { "from" : 10, "to" : 20 }
            }
        },
        "should" : [
            {
                "term" : { "tag" : "wow" }
            },
            {
                "term" : { "tag" : "elasticsearch" }
            }
        ]
    }
}

range

  • gt 大于
  • gte 大于等于
  • lt 小于
  • lte 小于等于
"query": {
    "range": {
      "status": {
        "gte": 3,
        "lte": 20
      }
    }
}
"query": {
    "bool": {
      "must": [
        {
          "term": {
            "courseName": {
              "value": "1"
            }
          }
        },
        {
          "range": {
            "status": {
              "lte": 1
            }
          }
        }
      ]
    }
}

高亮

高亮器

    public Page<CourseAuditESDoc> pageByStatus(Integer status, Integer liveFlag, String courseName, String ownerName, PaginationBaseQuery paginationBaseQuery, Sort sort) {
        int pageIndex = paginationBaseQuery.getPageIndex();
        int pageSize = paginationBaseQuery.getPageSize();
        if (pageIndex <= 0) {
            pageIndex = 1;
        }
        if (pageSize <= 0) {
            pageSize = 20;
        }
        // 高亮
        String preTag = "<font color='#dd4b39'>";
        String postTag = "</font>";
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (Objects.nonNull(status)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("status", status));
        }
        if (Objects.nonNull(liveFlag)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("liveFlag", liveFlag));
        }
        nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
        int count = 0;
        boolean hasCourseName = StringUtils.isNotBlank(courseName);
        boolean hasOwnerName = StringUtils.isNotBlank(ownerName);
        if (hasCourseName) {
            count++;
        }
        if (hasOwnerName) {
            count++;
        }
        if (count > 0) {
            sort.and(new Sort(Sort.Direction.DESC, "_score"));
            HighlightBuilder.Field[] fields = new HighlightBuilder.Field[count];
            BoolQueryBuilder queryBuilders = QueryBuilders.boolQuery();
            int index = 0;
            if (hasCourseName) {
                queryBuilders.must(QueryBuilders.matchQuery("courseName", courseName));
                fields[index] = new HighlightBuilder.Field("courseName").preTags(preTag).postTags(postTag).highlighterType("unified");
                index++;
            }
            if (hasOwnerName) {
                queryBuilders.must(QueryBuilders.matchQuery("ownerName", ownerName));
                fields[index] = new HighlightBuilder.Field("ownerName").preTags(preTag).postTags(postTag).highlighterType("unified");
                index++;
            }
            nativeSearchQueryBuilder.withQuery(queryBuilders);
            nativeSearchQueryBuilder.withHighlightFields(fields);
        }

        // 设置分页。Spring data的pageIndex和我们内部的差1
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageIndex - 1, pageSize, sort));
        Page<CourseAuditESDoc> result = elasticsearchOperations.queryForPage(nativeSearchQueryBuilder.build(),
                CourseAuditESDoc.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                        List<CourseAuditESDoc> docList = new ArrayList<>();
                        for (SearchHit searchHit : response.getHits()) {
                            Map<String, Object> result = searchHit.getSource();
                            Gson gson = new Gson();
                            JsonElement jsonElement = gson.toJsonTree(result);
                            CourseAuditESDoc doc = gson.fromJson(jsonElement, CourseAuditESDoc.class);
                            doc.setId(searchHit.getId());
                            Map<String, HighlightField> map = searchHit.getHighlightFields();
                            if (MapUtils.isNotEmpty(map)) {
                                HighlightField courseNameFiled = map.get("courseName");
                                if (Objects.nonNull(courseNameFiled)) {
                                    doc.setCourseName(courseNameFiled.fragments()[0].toString());
                                }
                                HighlightField ownerNameFiled = map.get("ownerName");
                                if (Objects.nonNull(ownerNameFiled)) {
                                    doc.setOwnerName(ownerNameFiled.fragments()[0].toString());
                                }
                            }
                            docList.add(doc);
                        }
                        if (docList.size() > 0) {
                            return new AggregatedPageImpl<>((List<T>) docList, pageable, response.getHits().totalHits);
                        }
                        return null;
                    }

        });
        return result;
    }
{
  "query": {
    "match": {
      "courseName": "1"
    }
  },
  "highlight": {
    "pre_tags": [
      "<b>"
    ],
    "post_tags": [
      "</b>"
    ],
    "fields": {
      "courseName": {}
    }
  }
}

删除文档

POST /course_audit_aliased/course_audit_aliased/_delete_by_query
{
  "query": {
    "match_all": {}
  }
}

分词

分词器比较

ik分词器

基于_version进行乐观锁并发控制

准实时

要把数据写到磁盘,需要调用 fsync,但是fsync十分耗资源,无法频繁的调用,在这种情况下,Elasticsearch 利用了filesystem cache,新文档先写到in-memory buffer,然后写入到 filesystem cache,过一段时间后,再将segment写到磁盘。在这个过程中,只要文档写到filesystem cache,就可以被搜索到了。

ELK