ElasticSearch

作者: 今有所思 | 来源:发表于2019-01-12 09:39 被阅读7次

    使用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

    相关文章

      网友评论

        本文标题:ElasticSearch

        本文链接:https://www.haomeiwen.com/subject/ciuqdqtx.html