elasticsearch_elasticsearchresttemplate - CSDN
elasticsearch 订阅
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。 展开全文
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。
信息
技术支持
通过HTTP使用JSON进行数据索引
简    称
ES
主要目的
解决人们对于搜索的众多要求
用    途
用于分布式全文检索
外文名
Elasticsearch
elasticsearch简介
Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。Elasticsearch是与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发的。这三个产品被设计成一个集成解决方案,称为“Elastic Stack”(以前称为“ELK stack”)。Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。”Elasticsearch是分布式的,这意味着索引可以被分成分片,每个分片可以有0个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。“相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。一旦创建了索引,就不能更改主分片的数量。Elasticsearch使用Lucene,并试图通过JSON和Java API提供其所有特性。它支持facetting和percolating,如果新文档与注册查询匹配,这对于通知非常有用。另一个特性称为“网关”,处理索引的长期持久性;例如,在服务器崩溃的情况下,可以从网关恢复索引。Elasticsearch支持实时GET请求,适合作为NoSQL数据存储,但缺少分布式事务。 [1] 
收起全文
精华内容
参与话题
  • 本课程为ElasticSearch6.6视频搜索项目实战课程,项目基于Java开发,通过视频搜索项目案例的方式讲解Elasticsearch分布式搜索引擎中的核心技术,学员学习本课程以后可以初级实现类似优酷视频搜索项目。
  • Elasticsearch从入门到实战

    千人学习 2020-03-11 14:33:08
    Elasticsearch是业界领先的海量搜索引擎,开箱即用的特性让其拥有最多的装机量。当前采用和使用Elasticsearch的企业越来越多。 本课程不仅会涉及Elasticsearch的基本安装和配置,更会对其基本操作进行详细的...
  • Elasticsearch学习,请先看这一篇!

    万次阅读 多人点赞 2018-05-25 22:49:55
    题记:Elasticsearch研究有一段时间了,现特将Elasticsearch相关核心知识、原理从初学者认知、学习的角度,从以下9个方面进行详细梳理。欢迎讨论……0. 带着问题上路——ES是如何产生的?(1)思考:大规模数据如何...

    题记:

    Elasticsearch研究有一段时间了,现特将Elasticsearch相关核心知识、原理从初学者认知、学习的角度,从以下9个方面进行详细梳理。欢迎讨论……

    0. 带着问题上路——ES是如何产生的?

    (1)思考:大规模数据如何检索?

    如:当系统数据量上了10亿、100亿条的时候,我们在做系统架构的时候通常会从以下角度去考虑问题:
    1)用什么数据库好?(mysql、sybase、oracle、达梦、神通、mongodb、hbase…)
    2)如何解决单点故障;(lvs、F5、A10、Zookeep、MQ)
    3)如何保证数据安全性;(热备、冷备、异地多活)
    4)如何解决检索难题;(数据库代理中间件:mysql-proxy、Cobar、MaxScale等;)
    5)如何解决统计分析问题;(离线、近实时)

    (2)传统数据库的应对解决方案

    对于关系型数据,我们通常采用以下或类似架构去解决查询瓶颈和写入瓶颈:
    解决要点:
    1)通过主从备份解决数据安全性问题;
    2)通过数据库代理中间件心跳监测,解决单点故障问题;
    3)通过代理中间件将查询语句分发到各个slave节点进行查询,并汇总结果
    这里写图片描述

    (3)非关系型数据库的解决方案

    对于Nosql数据库,以mongodb为例,其它原理类似:
    解决要点:
    1)通过副本备份保证数据安全性;
    2)通过节点竞选机制解决单点问题;
    3)先从配置库检索分片信息,然后将请求分发到各个节点,最后由路由节点合并汇总结果
    这里写图片描述

    另辟蹊径——完全把数据放入内存怎么样?

    我们知道,完全把数据放在内存中是不可靠的,实际上也不太现实,当我们的数据达到PB级别时,按照每个节点96G内存计算,在内存完全装满的数据情况下,我们需要的机器是:1PB=1024T=1048576G
    节点数=1048576/96=10922个
    实际上,考虑到数据备份,节点数往往在2.5万台左右。成本巨大决定了其不现实!

    从前面讨论我们了解到,把数据放在内存也好,不放在内存也好,都不能完完全全解决问题。
    全部放在内存速度问题是解决了,但成本问题上来了。
    为解决以上问题,从源头着手分析,通常会从以下方式来寻找方法:
    1、存储数据时按有序存储;
    2、将数据和索引分离;
    3、压缩数据;
    这就引出了Elasticsearch。

    1. ES 基础一网打尽

    1.1 ES定义

    ES=elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
    Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

    1.2 Lucene与ES关系?

    1)Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

    2)Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

    1.3 ES主要解决问题:

    1)检索相关数据;
    2)返回统计结果;
    3)速度要快。

    1.4 ES工作原理

    当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。这个过程如下图所示:
    这里写图片描述

    1.5 ES核心概念

    1)Cluster:集群。

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

    2)Node:节点。

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

    3)Shard:分片。

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

    4)Replia:副本。

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

    5)全文检索。

    全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
    全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们的激情是因为什么事情来的” 可能会被分词成:“你们“,”激情“,“什么事情“,”来“ 等token,这样当你搜索“你们” 或者 “激情” 都会把这句搜出来。

    1.6 ES数据架构的主要概念(与关系数据库Mysql对比)

    这里写图片描述
    (1)关系型数据库中的数据库(DataBase),等价于ES中的索引(Index)
    (2)一个数据库下面有N张表(Table),等价于1个索引Index下面有N多类型(Type),
    (3)一个数据库表(Table)下的数据由多行(ROW)多列(column,属性)组成,等价于1个Type由多个文档(Document)和多Field组成。
    (4)在一个关系型数据库里面,schema定义了表、每个表的字段,还有表和字段之间的关系。 与之对应的,在ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立、索引类型、是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处理等。
    (5)在数据库中的增insert、删delete、改update、查search操作等价于ES中的增PUT/POST、删Delete、改_update、查GET.

    1.7 ELK是什么?

    ELK=elasticsearch+Logstash+kibana
    elasticsearch:后台分布式存储以及全文检索
    logstash: 日志加工、“搬运工”
    kibana:数据可视化展示。
    ELK架构为数据分布式存储、可视化查询和日志解析创建了一个功能强大的管理链。 三者相互配合,取长补短,共同完成分布式大数据处理工作。

    2. ES特点和优势

    1)分布式实时文件存储,可将每一个字段存入索引,使其可以被检索到。
    2)实时分析的分布式搜索引擎。
    分布式:索引分拆成多个分片,每个分片可有零个或多个副本。集群中的每个数据节点都可承载一个或多个分片,并且协调和处理各种操作;
    负载再平衡和路由在大多数情况下自动完成。
    3)可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。也可以运行在单台PC上(已测试)
    4)支持插件机制,分词插件、同步插件、Hadoop插件、可视化插件等。

    3、ES性能

    3.1 性能结果展示

    (1)硬件配置:
    CPU 16核 AuthenticAMD
    内存 总量:32GB
    硬盘 总量:500GB 非SSD

    (2)在上述硬件指标的基础上测试性能如下:
    1)平均索引吞吐量: 12307docs/s(每个文档大小:40B/docs)
    2)平均CPU使用率: 887.7%(16核,平均每核:55.48%)
    3)构建索引大小: 3.30111 GB
    4)总写入量: 20.2123 GB
    5)测试总耗时: 28m 54s.

    3.2 性能esrally工具(推荐)

    使用参考:http://blog.csdn.net/laoyang360/article/details/52155481

    4、为什么要用ES?

    4.1 ES国内外使用优秀案例

    1) 2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。 “GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”。

    2)维基百科:启动以elasticsearch为基础的核心搜索架构。
    3)SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”。
    4)百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据。

    4.2 我们也需要

    实际项目开发实战中,几乎每个系统都会有一个搜索的功能,当搜索做到一定程度时,维护和扩展起来难度就会慢慢变大,所以很多公司都会把搜索单独独立出一个模块,用ElasticSearch等来实现。

    近年ElasticSearch发展迅猛,已经超越了其最初的纯搜索引擎的角色,现在已经增加了数据聚合分析(aggregation)和可视化的特性,如果你有数百万的文档需要通过关键词进行定位时,ElasticSearch肯定是最佳选择。当然,如果你的文档是JSON的,你也可以把ElasticSearch当作一种“NoSQL数据库”, 应用ElasticSearch数据聚合分析(aggregation)的特性,针对数据进行多维度的分析。

    【知乎:热酷架构师潘飞】ES在某些场景下替代传统DB
    个人以为Elasticsearch作为内部存储来说还是不错的,效率也基本能够满足,在某些方面替代传统DB也是可以的,前提是你的业务不对操作的事性务有特殊要求;而权限管理也不用那么细,因为ES的权限这块还不完善。
    由于我们对ES的应用场景仅仅是在于对某段时间内的数据聚合操作,没有大量的单文档请求(比如通过userid来找到一个用户的文档,类似于NoSQL的应用场景),所以能否替代NoSQL还需要各位自己的测试。
    如果让我选择的话,我会尝试使用ES来替代传统的NoSQL,因为它的横向扩展机制太方便了。

    5. ES的应用场景是怎样的?

    通常我们面临问题有两个:

    1)新系统开发尝试使用ES作为存储和检索服务器;
    2)现有系统升级需要支持全文检索服务,需要使用ES。
    以上两种架构的使用,以下链接进行详细阐述。
    http://blog.csdn.net/laoyang360/article/details/52227541

    一线公司ES使用场景:

    1)新浪ES 如何分析处理32亿条实时日志 http://dockone.io/article/505
    2)阿里ES 构建挖财自己的日志采集和分析体系 http://afoo.me/columns/tec/logging-platform-spec.html
    3)有赞ES 业务日志处理 http://tech.youzan.com/you-zan-tong-ri-zhi-ping-tai-chu-tan/
    4)ES实现站内搜索 http://www.wtoutiao.com/p/13bkqiZ.html

    6. 如何部署ES?

    6.1 ES部署(无需安装)

    1)零配置,开箱即用
    2)没有繁琐的安装配置
    3)java版本要求:最低1.7
    我使用的1.8
    [root@laoyang config_lhy]# echo $JAVA_HOME
    /opt/jdk1.8.0_91
    4)下载地址:
    https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/zip/elasticsearch/2.3.5/elasticsearch-2.3.5.zip
    5)启动
    cd /usr/local/elasticsearch-2.3.5
    ./bin/elasticsearch
    bin/elasticsearch -d(后台运行)

    6.2 ES必要的插件

    必要的Head、kibana、IK(中文分词)、graph等插件的详细安装和使用。
    http://blog.csdn.net/column/details/deep-elasticsearch.html

    6.3 ES windows下一键安装

    自写bat脚本实现windows下一键安装。
    1)一键安装ES及必要插件(head、kibana、IK、logstash等)
    2)安装后以服务形式运行ES。
    3)比自己摸索安装节省至少2小时时间,效率非常高。
    脚本说明:
    http://blog.csdn.net/laoyang360/article/details/51900235

    7. ES对外接口(开发人员关注)

    1)JAVA API接口

    http://www.ibm.com/developerworks/library/j-use-elasticsearch-java-apps/index.html

    2)RESTful API接口

    常见的增、删、改、查操作实现:
    http://blog.csdn.net/laoyang360/article/details/51931981

    8.ES遇到问题怎么办?

    1)国外:https://discuss.elastic.co/
    2)国内:http://elasticsearch.cn/

    参考:

    [1] http://www.tuicool.com/articles/7fueUbb
    [2] http://zhaoyanblog.com/archives/495.html
    [3]《Elasticsearch服务器开发》
    [4]《实战Elasticsearch、Logstash、Kibana》
    [5]《Elasticsearch In Action》
    [6]《某ES大牛PPT》

    9、还有吗?

    《死磕 Elasticsearch 方法论》:普通程序员高效精进的 10 大狠招!(免费完整版)
    https://blog.csdn.net/laoyang360/article/details/79293493
    ——————————————————————————————————
    更多ES相关实战干货经验分享,请扫描下方【铭毅天下】微信公众号二维码关注。
    (每周至少更新一篇!)

    这里写图片描述
    和你一起,死磕Elasticsearch
    ——————————————————————————————————

    2016-08-18 21:10 思于家中床前

    作者:铭毅天下
    转载请标明出处,原文地址:
    http://blog.csdn.net/laoyang360/article/details/52244917
    如果感觉本文对您有帮助,请点击‘顶’支持一下,您的支持是我坚持写作最大的动力,谢谢!

    展开全文
  • ElasticSearch简介

    万次阅读 多人点赞 2020-09-14 11:07:07
    摘自Elasticsearch-基础介绍及索引原理分析 Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单...

    摘自Elasticsearch-基础介绍及索引原理分析

    Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:

    • 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
    • 实时分析的分布式搜索引擎。
    • 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。

     

    一、基本概念

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

    {
        "name" :     "John",
        "sex" :      "Male",
        "age" :      25,
        "birthDate": "1990/05/01",
        "about" :    "I love to go rock climbing",
        "interests": [ "sports", "music" ]
    }

     用Mysql这样的数据库存储就会容易想到建立一张User表,有balabala的字段等,在Elasticsearch里这就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。

    这里有一份简易的将Elasticsearch和关系型数据术语对照表:

    关系数据库      ⇒ 数据库        ⇒  表         ⇒ 行              ⇒ 列(Columns)
    
    Elasticsearch  ⇒ 索引(Index)   ⇒ 类型(type)  ⇒ 文档(Docments)  ⇒ 字段(Fields)  

     一个 Elasticsearch 集群可以包含多个索引(数据库),也就是说其中包含了很多类型(表)。这些类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互,可以使用Java API,也可以直接使用HTTP的Restful API方式,比如我们打算插入一条记录,可以简单发送一个HTTP的请求:

    PUT /megacorp/employee/1  
    {
        "name" :     "John",
        "sex" :      "Male",
        "age" :      25,
        "about" :    "I love to go rock climbing",
        "interests": [ "sports", "music" ]
    }

    更新,查询也是类似这样的操作。

     

    二、 索引

    Elasticsearch最关键的就是提供强大的索引能力。

    Elasticsearch索引的精髓:

    一切设计都是为了提高搜索的性能

    另一层意思:为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新,否则其他数据库不用混了。前面看到往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,比如上面例子中的name, sex, age, about, interests,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还默默1的为这些字段建立索引--倒排索引,因为Elasticsearch最核心功能是搜索。

     

    三、Elasticsearch是如何做到快速索引的

    Elasticsearch使用的倒排索引比关系型数据库的B-Tree索引快,为什么呢?

    什么是B-Tree索引?

    上大学读书时老师教过我们,二叉树查找效率是logN,同时插入新的节点不必移动全部节点,所以用树型结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,再结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这样的数据结构:

    为了提高查询的效率,减少磁盘寻道次数,将多个值作为一个数组通过连续区间存放,一次寻道读取多个数据,同时也降低树的高度。

    什么是倒排索引?

    继续上面的例子,假设有这么几条数据(为了简单,去掉about, interests这两个field):

    | ID | Name | Age  |  Sex     |
    | -- |:------------:| -----:| -----:| 
    | 1  | Kate         | 24 | Female
    | 2  | John         | 24 | Male
    | 3  | Bill         | 29 | Male

    ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下:

    Name:

    | Term | Posting List |
    | -- |:----:|
    | Kate | 1 |
    | John | 2 |
    | Bill | 3 |

    Age:

    | Term | Posting List |
    | -- |:----:|
    | 24 | [1,2] |
    | 29 | 3 |

    Sex:

    | Term | Posting List |
    | -- |:----:|
    | Female | 1 |
    | Male | [2,3] |

    Posting List

    Elasticsearch分别为每个field都建立了一个倒排索引,Kate, John, 24, Female这些叫term,而[1,2]就是Posting List。Posting list就是一个int的数组,存储了所有符合某个term的文档id。

    看到这里,不要认为就结束了,精彩的部分才刚开始...

    通过posting list这种索引方式似乎可以很快进行查找,比如要找age=24的同学,爱回答问题的小明马上就举手回答:我知道,id是1,2的同学。但是,如果这里有上千万的记录呢?如果是想通过name来查找呢?

    Term Dictionary

    Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似啊,为什么说比B-Tree的查询快呢?

    Term Index

    B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树:

    所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。

    这时候爱提问的小明又举手了:"那个FST是神马东东啊?"

    一看就知道小明是一个上大学读书的时候跟我一样不认真听课的孩子,数据结构老师一定讲过什么是FST。但没办法,我也忘了,这里再补下课:

    FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output.

    假设我们现在要将mop, moth, pop, star, stop and top(term index里的term前缀)映射到序号:0,1,2,3,4,5(term dictionary的block位置)。最简单的做法就是定义个Map<string, integer="">,大家找到自己的位置对应入座就好了,但从内存占用少的角度想想,有没有更优的办法呢?答案就是:FST。

     

    FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output.

    FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。

    后面的更精彩,看累了的同学可以喝杯咖啡……

    压缩技巧

    Elasticsearch里除了上面说到用FST压缩term index外,对posting list也有压缩技巧。 
    小明喝完咖啡又举手了:"posting list不是已经只存储文档id了吗?还需要压缩?"

    嗯,我们再看回最开始的例子,如果Elasticsearch需要对同学的性别进行索引(这时传统关系型数据库已经哭晕在厕所……),会怎样?如果有上千万个同学,而世界上只有男/女这样两个性别,每个posting list都会有至少百万个文档id。 Elasticsearch是如何有效的对这些文档id压缩的呢?

    Frame Of Reference

    增量编码压缩,将大数变小数,按字节存储

    首先,Elasticsearch要求posting list是有序的(为了提高搜索的性能,再任性的要求也得满足),这样做的一个好处是方便压缩,看下面这个图例: 

    如果数学不是体育老师教的话,还是比较容易看出来这种压缩技巧的。

    原理就是通过增量,将原来的大数变成小数仅存储增量值,再精打细算按bit排好队,最后通过字节存储,而不是大大咧咧的尽管是2也是用int(4个字节)来存储。

    Roaring bitmaps

    说到Roaring bitmaps,就必须先从bitmap说起。Bitmap是一种数据结构,假设有某个posting list:

    [1,3,4,7,10]

    对应的bitmap就是:

    [1,0,1,1,0,0,1,0,0,1]

    非常直观,用0/1表示某个值是否存在,比如10这个值就对应第10位,对应的bit值是1,这样用一个字节就可以代表8个文档id,旧版本(5.0之前)的Lucene就是用这样的方式来压缩的,但这样的压缩方式仍然不够高效,如果有1亿个文档,那么需要12.5MB的存储空间,这仅仅是对应一个索引字段(我们往往会有很多个索引字段)。于是有人想出了Roaring bitmaps这样更高效的数据结构。

    Bitmap的缺点是存储空间随着文档个数线性增长,Roaring bitmaps需要打破这个魔咒就一定要用到某些指数特性:

    将posting list按照65535为界限分块,比如第一块所包含的文档id范围在0~65535之间,第二块的id范围是65536~131071,以此类推。再用<商,余数>的组合表示每一组id,这样每组里的id范围都在0~65535内了,剩下的就好办了,既然每组id不会变得无限大,那么我们就可以通过最有效的方式对这里的id存储。

    联合索引

    上面说了半天都是单field索引,如果多个field索引的联合查询,倒排索引如何满足快速查询的要求呢?

    • 利用跳表(Skip list)的数据结构快速做“与”运算,或者
    • 利用上面提到的bitset按位“与”

    先看看跳表的数据结构:

    将一个有序链表level0,挑出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找,比如55,先找到level2的31,再找到level1的47,最后找到55,一共3次查找,查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的。

    假设有下面三个posting list需要联合索引:

    如果使用跳表,对最短的posting list中的每个id,逐个在另外两个posting list中查找看是否存在,最后得到交集的结果。

    如果使用bitset,就很直观了,直接按位与,得到的结果就是最后的交集。

     

    四、总结和思考

    Elasticsearch的索引思路:

    将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种奇技淫巧的压缩算法,用及其苛刻的态度使用内存。 

    所以,对于使用Elasticsearch进行索引时需要注意:

    • 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
    • 同样的道理,对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的
    • 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询

    关于最后一点,个人认为有多个因素:

    其中一个(也许不是最重要的)因素: 上面看到的压缩算法,都是对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高;

    另外一个因素: 可能是最影响查询性能的,应该是最后通过Posting list里的ID到磁盘中查找Document信息的那步,因为Elasticsearch是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率直接影响了最后查询的性能,如果ID是有规律的,可以快速跳过不包含该ID的Segment,从而减少不必要的磁盘读次数。

    展开全文
  • 2小时学会ElasticSearch

    千人学习 2019-08-22 10:16:18
    网的高并发中间件redis,ElasticSearch,RabbitMQ,MongoDB数据库,springSecurity安全框架,还会涉及到一些后期运维的相关技术如jenkins,influxdb,ranchar等技术,还会涉及到代码生成器,不需要写简单的代码了,可以...
  • 本课程深入浅出剖析了Elasticsearch的核心基础知识,带着大家一步一步,从快速入门,到理解Elasticsearch的工作原理、内核级原理,再到动手实战操作Elasticsearch的各种核心功能,到最后可以基于Java语言开发基本的...
  • ElasticSearch知识点记录

    2020-04-08 09:10:55
    ElasticSearchElasticSearch1.1 全文检索,倒排索引1.2 Lucene1.3 什么是ElasticSearch1.4 Elasticsearch的功能1.5 elasticsearch的适用场景1.6 elasticsearch的特点二 Elasticsearch的核心概念2.1 Lucene和...

    ElasticSearch

    一 ElasticSearch

    1.1 全文检索,倒排索引

    倒排索引:给每一个数据都设定一个id,倒排索引看起来类似下图,是将每一个词条都拆开来,将所有的拆出来的词全部排列,建立倒排索引,搜索的时候也会将搜索词进行拆分,然后将拆分后的词放入倒排索引进行查询
    倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。通俗地来讲,正向索引是通过key找value,反向索引则是通过value找key。
    在这里插入图片描述
    利用倒排索引进行全文索引,假设100万条数据,拆分出来的词语,假设有100万个词语,那么在倒排索引中,就有1000万行,优化搜索算法,有可能快速的查询到想要查询的词 。

    1.2 Lucene

    就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。一般就用Java开发时,引用Lucene jar,基于Lucene的api进行开发就可以了。我们就可以去将已有的数据建立索引,Lucene会在本地磁盘上面,给我们组织索引的数据结构。另外的话,我们也可以用Lucene提供的一些功能和api来针对磁盘上的索引数据,进行搜索。

    1.3 什么是ElasticSearch

    Lucene封装了搜索引擎的功能
    ElasticSearch 底层会封装Lucene,elasticSearch是多个节点
    es的特点和优点:
    1.自动维护数据的冗余副本,保证数据不丢失,由于宕机
    2.封装了Lucene,提升高级api,能快速开发复杂的功能
    3.基于地理位置的搜索,搜索距离我当前一公里的烤肉店
    4.自动维护数据的分布到多个节点的索引的建立,还有搜索请求分布到多个节点的执行

    Elasticsearch 分布式,高可用,可伸缩,高性能

    1.4 Elasticsearch的功能

    1)分布式的搜索引擎和数据分析引擎
    搜索:百度,网站的站内搜索,IT系统的检索
    数据分析:电商网站,最近七天牙膏这种商品销量排名前十的商家有哪些

    分布式,搜索,数据分析

    2)全文检索,结构化检索,数据分析
    全文检索:我想搜索商品名称包含牙膏的商品 like
    结构化搜索:我想搜索商品分类为日化用品的商品有哪些 =
    部分匹配,自动完成,搜索纠错,搜索推荐
    数据分析:我想分析每一个商品分类下有多少个商品 group by

    3)对海量数据进行近实时的处理
    分布式:ES自动可以将海量数据分散到多台服务器上存储和检索
    海量数据的处理:分布式以后,就可以采用大量的服务器和检索数据,自然而然就可以实现海量数据的处理了
    近实时:在秒级别对数据进行搜索和分析
    跟分布式/海量数据相反的:lucene,单机应用,只能在单台机器上使用,最多只能处理单台服务器的数据量

    1.5 elasticsearch的适用场景

    维基百科
    国外新闻网站,类似搜狐新闻
    Stack Overflow
    GitHub
    电商网站
    日志数据分析,Logstash采集日志,es进行复杂的数据分析
    商品价格监控网站,用户设定某商品的价格阈值的时候,发送通知消息给用户
    BI系统,ES执行数据分析和挖掘,Kibana进行数据可视化

    1.6 elasticsearch的特点

    1. 可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据
    2. Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并成了独一无二的ES
    3. 对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分支部署一下es,就可以作为生产环境的系统来使用,数据量不大,操作不是太复杂
    4. 数据库的功能面对很多领域是不够用的(事务,还有各种联机事务型操作);特殊的功能,比如,相关度排名,复杂数据分析,海量数据的近实时处理;Elasticsearch作为传统数据库的一个补充,提供了数据库所不能提供的很多功能

    二 Elasticsearch的核心概念

    2.1 Lucene和ElasticSearch的前世今生

    Lucene:最先进、功能最强大的搜索库,直接基于Lucene开发,非常复杂,api复杂,需要深入理解原理
    elasticsearch,基于Lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他语言的api接口)

    1. 分布式文档存储引擎
    2. 分布式的搜索引擎和分析引擎
    3. 分布式,支持PB级数据

    开箱即用,优秀的默认参数,不需要任何额外设置,完全开源

    2.2 elasticsearch的核心概念

    Near Realtime(NRT):近实时,两个意思,从写入数据到数据可以被搜索到有一个延迟(大概1秒);基于es执行搜索和分析可以达到秒级
    cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常
    Node:节点,集群中的一个节点,节点也有一个名称(默认随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群
    Document:文档,es中最小数据单元,一个document可以是一条客户数据,通常用JSON数据结构表示,每个Index下的type中,都可以存储多个document,一个document里面有多个field,每个field就是一个数据字段
    Index:索引,包含一堆有相似的文档数据,比如可以有一个客户索引,商品分类索引,订单索引。
    Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field。
    shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。
    replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。

    2.3 full-text search(全文检索)

    get /ecommerce/product/_search
    {
        "query": {
            "match": {
                "producer": "yagao producer"
            }
        }
    }
    

    producer 这个字段,会先被拆解,建立倒排索引
    special 4
    yago 4
    producer 1,2,3,4
    gaolujie 1
    zhonghua 3
    jiajieshi 2

    2.4 phrase search (短语搜索)

    match_phrase
    跟全文检索相对应,相反,全文检索会将输入的搜索串拆解开来,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回
    phrase search 要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配,才能作为结果返回

    2.5 elasticsearch 聚合分析

    聚合分析的需求:先分组,再算每组的平均值,计算每个tag下的商品的平均价格

    GET /ecommerce/product/_search
    {
        "size": 0,
        "aggs" : {
            "group_by_tags" : {
                "terms" : { "field" : "tags" },
                "aggs" : {
                    "avg_price" : {
                        "avg" : { "field" : "price" }
                    }
                }
            }
        }
    }
    

    数据分析需求:计算每个tag下的商品的平均价格,并且按照平均价格降序排序

    GET /ecommerce/product/_search
    {
        "size": 0,
        "aggs" : {
            "all_tags" : {
                "terms" : { "field" : "tags", "order": { "avg_price": "desc" } },
                "aggs" : {
                    "avg_price" : {
                        "avg" : { "field" : "price" }
                    }
                }
            }
        }
    }
    

    数据分析需求:按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格

    GET /ecommerce/product/_search
    {
      "size": 0,
      "aggs": {
        "group_by_price": {
          "range": {
            "field": "price",
            "ranges": [
              {
                "from": 0,
                "to": 20
              },
              {
                "from": 20,
                "to": 40
              },
              {
                "from": 40,
                "to": 50
              }
            ]
          },
          "aggs": {
            "group_by_tags": {
              "terms": {
                "field": "tags"
              },
              "aggs": {
                "average_price": {
                  "avg": {
                    "field": "price"
                  }
                }
              }
            }
          }
        }
      }
    }
    

    2.6 ES的基础分布式结构

    在这里插入图片描述
    1.ElasticSearch对复杂分布式机制的透明隐藏特性
    2.ElasticSearch的垂直扩容与水平扩容
    3.增减或减少节点的数据rebalance
    4.master节点
    5.节点对等的分布式架构

    2.7 primary&replica shard

    1)index包含多个shard
    2)每个shard都是一个最小工作单元,承载部分数据,Lucene实例,完整的建立索引和处理请求的能力
    3)增减节点时,shard 会自动在nodes中负载均衡
    4)primary shard和replica shard,每个document肯定只存在某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
    5)replica shard是primary shard的副本,负责容错,以及承担读请求负载
    6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
    7)primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
    8)primary shard不能和自己的replica shard放在同一个节点上,但是可以和其他primary shard的replica shard放在同一个节点上
    在这里插入图片描述

    2.8 ES横向扩容

    图解横向扩容过程,如何超出扩容极限,以及如何提升容错性
    1)primary&replica自动负载均衡,6个shard,3 primary,3 replica
    2)每个node有更少的shard,IO/CPU/Memory资源给每个shard分配更多,每个shard性能更好
    3)扩容的极限,6个shard(3 primary,3 replica),最多扩容到6台机器,每个shard可以占用单台服务器的所有资源,性能最好
    4)超出扩容极限,动态修改replica数量,9个shard(3 primary,6 replica),扩容到9台机器,比3台机器时,拥有3倍的读吞吐量
    5)3台机器下,9个shard(3 primary ,6 replica),资源更少,但是容错性更好,做多容纳2台机器宕机,6个shard只能容纳1台机器宕机
    6)这里的这些知识点,你综合起来看,就是说,一方面告诉你扩容的原理,怎么扩容,怎么提升系统整体吞吐量:另一方面要考虑到系统的容错性,怎么保证提高容错性,让尽可能多的服务器宕机,保证数据不丢失
    扩容过程分析
    容错纠正

    2.9 ES容错机制

    ElasticSearch容错机制:master选举,replica容错,数据恢复
    1)9 shard, 3 node
    2)master node宕机,自动master选举,red
    3)replica容错:新master将replica提升为primary shard,yellow
    4)重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green
    在这里插入图片描述

    2.10 ES _index/_type/_id 元数据介绍

    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "1",
      "_version": 1,
      "found": true,
      "_source": {																												
        "test_content": "test test"
      }
    }
    

    1、_index元数据
    (1)代表一个document存放在哪个index中
    (2)类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。
    (3)index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。
    (4)索引名称必须是小写的,不能用下划线开头,不能包含逗号:product,website,blog

    2、_type元数据
    (1)代表document属于index中的哪个类别(type)
    (2)一个索引通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等。
    (3)type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号

    3、_id元数据
    (1)代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
    (2)我们可以手动指定document的id(put /index/type/id),也可以不指定,由es自动为我们创建一个id

    2.11 ES id生成的两种方式

    一般来说,是从某些其他的系统中,导入一些数据到es时,会采取这种方式,就是使用系统中已有数据的唯一标识,作为es中document的id。举个例子,比如说,我们现在在开发一个电商网站,做搜索功能,或者是OA系统,做员工检索功能。这个时候,数据首先会在网站系统或者IT系统内部的数据库中,会先有一份,此时就肯定会有一个数据库的primary key(自增长,UUID,或者是业务编号)。如果将数据导入到es中,此时就比较适合采用数据在数据库中已有的primary key。

    如果说,我们是在做一个系统,这个系统主要的数据存储就是es一种,也就是说,数据产生出来以后,可能就没有id,直接就放es一个存储,那么这个时候,可能就不太适合说手动指定document id的形式了,因为你也不知道id应该是什么,此时可以采取下面要讲解的让es自动生成id的方式。

    1、手动指定document id

    put /index/type/id
    
    PUT /test_index/test_type/2
    {
      "test_content": "my test"
    }
    

    2、自动生成document id

    POST /index/type
    
    POST /test_index/test_type
    {
      "test_content": "my test"
    }
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "AVp4RN0bhjxldOOnBxaE",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
    

    自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突

    GUID算法不冲突解释:
    GUID算法不冲突解释

    2.12 _source元数据

    1、_source元数据

    put /test_index/test_type/1
    {
      "test_field1": "test field1",
      "test_field2": "test field2"
    }
    
    get /test_index/test_type/1
    result:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "1",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field1": "test field1",
        "test_field2": "test field2"
      }
    }
    

    _source元数据:就是说,我们在创建一个document的时候,使用的那个放在request body中的json串,默认情况下,在get的时候,会原封不动的给我们返回回来。

    2、定制返回结果
    定制返回的结果,指定_source中,返回哪些field

    GET /test_index/test_type/1?_source=test_field1,test_field2
    result:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "1",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field2": "test field2"
      }
    }
    

    2.13 document的全量替换、强制创建、删除

    1、document的全量替换
    (1)语法与创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容
    (2)document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容
    (3)es会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document

    2、document的强制创建
    (1)创建文档与全量替换的语法是一样的,有时我们只是想新建文档,不想替换文档,如果强制进行创建呢?
    (2)PUT /index/type/id?op_type=create,PUT /index/type/id/_create

    3、document的删除
    (1)DELETE /index/type/id
    (2)不会理解物理删除,只会将其标记为deleted,当数据越来越多的时候,在后台自动删除

    2.13 ES并发冲突

    在这里插入图片描述

    2.14 ES内部如何基于_version进行乐观锁并发控制

    悲观锁与乐观锁两种并发控制方案
    在这里插入图片描述
    mysql 采用悲观锁技术
    ES 采用乐观锁技术,乐观锁引入 version

    悲观锁与乐观锁
    1.悲观锁的优点是:方便,直接加锁,对应用程序来说,透明,不需要做额外的操作;缺点,并发能力很低,同一时间只能有一条线程操作数据
    2.乐观锁的优点是:并发能力很高,不给数据加锁,大量线程并发操作;缺点,麻烦,每次更新的时候,都要先对比版本号,然后可能需要重新加载数据,再次修改,再写;这个过程可能要重复好几次。

    _version
    每一次创建一个document的时候,它的_version内部版本号就是1;以后,每次对这个document执行修改或者删除操作,都会对这个_version版本号自动加1;哪怕是删除,也会对这条数据的版本号加1

    PUT /test_index/test_type/6
    {
      "test_field": "test test"
    }
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "6",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
    
    {
      "found": true,
      "_index": "test_index",
      "_type": "test_type",
      "_id": "6",
      "_version": 4,
      "result": "deleted",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      }
    }
    

    我们会发现,在删除一个document之后,可以从侧面证明,它不是立即物理删除的,因为它的一些版本号信息还是保留着的。先删除一条document,再重新创建这条document,其实会在delete version基础之上,再把version号加1。

    es 中的数据的版本号,跟客户端中的数据的版本号是相同才能修改
    基于最新的数据和版本号,去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下

    external version
    es 提供了一个feature,就是说,你可以不用它提供的内部_version 版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。举个例子,加入你的数据在mysql里也有一份,然后你的应用系统本身就维护了一个版本号,无论是自己生成的,程序控制的。这个时候,你进行乐观锁并发控制的时候,可能并不是想要的es 内部的_version 来进行控制,而是用你自己维护的那个version 来进行控制。
    ?version=1
    ?version=1&version_type=external

    version_type=external,唯一的区别在于,_version,只有当你提供的version 与 es 中的_version一模一样的时候,才可以进行修改,只要不一样,就报错;当version_type=external的时候,只有当你提供的version比es中的_version大的时候,才能完成修改

    es,_version=1,?version=1,才能更新成功
    es,_version=1,?version>1&version_type=external,才能成功,比如说?version=2&version_type=external

    2.15 partial update实现原理

    1. 什么是 partial update?
    PUT /index/type/id,创建文档&替换文档,就是一样的语法

    一般对应到应用程序中,每次的执行流程基本是这样的:
    (1)应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改
    (2)用户在前台界面修改数据,发送到后台
    (3)后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据
    (4)然后发送PUT请求,到es中,进行全量替换
    (5)es将老的document标记为deleted,然后重新创建一个新的document

    partial update

    post /index/type/id/_update 
    {
       "doc": {
          "要修改的少数几个field即可,不需要全量的数据"
       }
    }
    

    看起来,好像就比较方便了,每次就传递少数几个发生修改的field即可,不需要将全量的document数据发送过去

    PUT /test_index/test_type/10
    {
      "test_field1": "test1",
      "test_field2": "test2"
    }
    
    POST /test_index/test_type/10/_update
    {
      "doc": {
        "test_field2": "updated test2"
      }
    }
    

    partial update 相较于全量替换的优点
    1.所有的查询、修改和写回操作,都发生在es中的一个shard 内部,避免了所有的网络数据传输的开销(减少2次网络请求),大大提升了性能(全量替换的查询、修改是发生在java或别的程序中的)
    2.减少了查询和修改中的时间间隔,可以有效减少并发冲突的情况

    groovy 脚本进行 partial update
    es,有个内置脚本支持,可以基于groovy实现各种各样复杂操作
    (1)内置脚本

    POST /test_index/test_type/11/_update
    {
       "script" : "ctx._source.num+=1"
    }
    
    result:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "11",
      "_version": 2,
      "found": true,
      "_source": {
        "num": 1,
        "tags": []
      }
    }
    

    (2)外部脚本
    脚本文件test-add-tags.groovy
    ctx._source.tags+=new_tag

    “file”: “test-add-tags”

    POST /test_index/test_type/11/_update
    {
      "script": {
        "lang": "groovy", 
        "file": "test-add-tags",
        "params": {
          "new_tag": "tag1"
        }
      }
    }
    

    (3)用脚本删除文档
    脚本文件:test-delete-document.groovy
    ctx.op = ctx._source.num == count ? ‘delete’ : ‘none’

    POST /test_index/test_type/11/_update
    {
      "script": {
        "lang": "groovy",
        "file": "test-delete-document",
        "params": {
          "count": 1
        }
      }
    }
    

    (4)upsert操作

    POST /test_index/test_type/11/_update
    {
      "doc": {
        "num": 1
      }
    }
    
    
    {
      "error": {
        "root_cause": [
          {
            "type": "document_missing_exception",
            "reason": "[test_type][11]: document missing",
            "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
            "shard": "4",
            "index": "test_index"
          }
        ],
        "type": "document_missing_exception",
        "reason": "[test_type][11]: document missing",
        "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
        "shard": "4",
        "index": "test_index"
      },
      "status": 404
    }
    

    如果指定的document不存在,就执行upsert中的初始化操作;如果指定的document存在,就执行doc或者script指定的partial update操作

    POST /test_index/test_type/11/_update
    {
       "script" : "ctx._source.num+=1",
       "upsert": {
           "num": 0,
           "tags": []
       }
    }
    

    partial update 内置乐观锁并发控制
    partial update 内置乐观锁并发控制
    retry_on_conflict
    _version

    post /index/type/id/_update?retry_on_conflict=5&version=6
    

    2.16 mget 批量查询

    1、批量查询的好处
    就是一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的
    如果进行批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的性能开销缩减100倍

    2、mget的语法
    (1)一条一条的查询

    GET /test_index/test_type/1
    GET /test_index/test_type/2
    

    (2)mget批量查询

    GET /_mget
    {
       "docs" : [
          {
             "_index" : "test_index",
             "_type" :  "test_type",
             "_id" :    1
          },
          {
             "_index" : "test_index",
             "_type" :  "test_type",
             "_id" :    2
          }
       ]
    }
    
    result:
    {
      "docs": [
        {
          "_index": "test_index",
          "_type": "test_type",
          "_id": "1",
          "_version": 2,
          "found": true,
          "_source": {
            "test_field1": "test field1",
            "test_field2": "test field2"
          }
        },
        {
          "_index": "test_index",
          "_type": "test_type",
          "_id": "2",
          "_version": 1,
          "found": true,
          "_source": {
            "test_content": "my test"
          }
        }
      ]
    }
    

    (3)如果查询的document是一个index下的不同type种的话

    GET /test_index/_mget
    {
       "docs" : [
          {
             "_type" :  "test_type",
             "_id" :    1
          },
          {
             "_type" :  "test_type",
             "_id" :    2
          }
       ]
    }
    

    (4)如果查询的数据都在同一个index下的同一个type下,最简单了

    GET /test_index/test_type/_mget
    {
       "ids": [1, 2]
    }
    

    3、mget的重要性
    可以说mget是很重要的,一般来说,在进行查询的时候,如果一次性要查询多条数据的话,那么一定要用batch批量操作的api
    尽可能减少网络开销次数,可能可以将性能提升数倍,甚至数十倍,非常非常之重要

    2.17 bulk 批量增删改

    1、bulk语法

    POST /_bulk
    { "delete": { "_index": "test_index", "_type": "test_type", "_id": "3" }} 
    { "create": { "_index": "test_index", "_type": "test_type", "_id": "12" }}
    { "test_field":    "test12" }
    { "index":  { "_index": "test_index", "_type": "test_type", "_id": "2" }}
    { "test_field":    "replaced test2" }
    { "update": { "_index": "test_index", "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
    { "doc" : {"test_field2" : "bulk test1"} }
    

    每一个操作要两个json串,语法如下:

    {"action": {"metadata"}}
    {"data"}
    

    举例,比如你现在要创建一个文档,放bulk里面,看起来会是这样子的:

    {"index": {"_index": "test_index", "_type", "test_type", "_id": "1"}}
    {"test_field1": "test1", "test_field2": "test2"}
    

    有哪些类型的操作可以执行呢?
    (1)delete:删除一个文档,只要1个json串就可以了
    (2)create:PUT /index/type/id/_create,强制创建
    (3)index:普通的put操作,可以是创建文档,也可以是全量替换文档
    (4)update:执行的partial update操作

    bulk api对json的语法,有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行

    {
      "took": 41,
      "errors": true,
      "items": [
        {
          "delete": {
            "found": true,
            "_index": "test_index",
            "_type": "test_type",
            "_id": "10",
            "_version": 3,
            "result": "deleted",
            "_shards": {
              "total": 2,
              "successful": 1,
              "failed": 0
            },
            "status": 200
          }
        },
        {
          "create": {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "3",
            "_version": 1,
            "result": "created",
            "_shards": {
              "total": 2,
              "successful": 1,
              "failed": 0
            },
            "created": true,
            "status": 201
          }
        },
        {
          "create": {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "2",
            "status": 409,
            "error": {
              "type": "version_conflict_engine_exception",
              "reason": "[test_type][2]: version conflict, document already exists (current version [1])",
              "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
              "shard": "2",
              "index": "test_index"
            }
          }
        },
        {
          "index": {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "4",
            "_version": 1,
            "result": "created",
            "_shards": {
              "total": 2,
              "successful": 1,
              "failed": 0
            },
            "created": true,
            "status": 201
          }
        },
        {
          "index": {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "2",
            "_version": 2,
            "result": "updated",
            "_shards": {
              "total": 2,
              "successful": 1,
              "failed": 0
            },
            "created": false,
            "status": 200
          }
        },
        {
          "update": {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "1",
            "_version": 3,
            "result": "updated",
            "_shards": {
              "total": 2,
              "successful": 1,
              "failed": 0
            },
            "status": 200
          }
        }
      ]
    }
    

    bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志

    POST /test_index/_bulk
    { "delete": { "_type": "test_type", "_id": "3" }} 
    { "create": { "_type": "test_type", "_id": "12" }}
    { "test_field":    "test12" }
    { "index":  { "_type": "test_type" }}
    { "test_field":    "auto-generate id test" }
    { "index":  { "_type": "test_type", "_id": "2" }}
    { "test_field":    "replaced test2" }
    { "update": { "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
    { "doc" : {"test_field2" : "bulk test1"} }
    
    POST /test_index/test_type/_bulk
    { "delete": { "_id": "3" }} 
    { "create": { "_id": "12" }}
    { "test_field":    "test12" }
    { "index":  { }}
    { "test_field":    "auto-generate id test" }
    { "index":  { "_id": "2" }}
    { "test_field":    "replaced test2" }
    { "update": { "_id": "1", "_retry_on_conflict" : 3} }
    { "doc" : {"test_field2" : "bulk test1"} }
    

    2、bulk size最佳大小
    bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从1000~ 5000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在5~15MB之间。

    2.18 distributed document system

    distributed document store
    ElasticSearch在跑起来以后,其实起到的第一个最核心的功能,就是一个分布式的文档数据存储系统。ES是分布式的。文档数据存储系统。文档数据,存储系统。
    文档数据:es可以存储和操作json文档类型的数据,而且这也是es的核心数据结构
    存储系统:es可以对json文档类型的数据进行存储,查询,创建,更新,删除,等等操作。其实已经起到了一个什么样的效果呢?其实ES满足了这些功能,就可以说已经是一个NoSQL的存储系统了。

    围绕着document在操作,其实就是把es当成了一个NoSQL存储引擎,一个可以存储文档类型数据的系统,再操作里面的document。

    es可以作为一个分布式的文档存储系统,所以说,我们的应用系统,是不是就可以基于这个概念,去进行相关的应用程序的开发了。

    什么类型的应用程序呢?
    (1)数据量较大,es的分布式本质,可以帮助你快速进行扩容,承载大量数据
    (2)数据结构灵活多变,随时可能会变化,数据结构之间的关系非常复杂
    (3)对数据的相关操作比较简单
    (4)NoSQL数据库,适用的也是类似上面的这种场景

    举个例子,比如说像一些网站系统,或者是普通的电商系统,博客系统,面向对象概念比较复杂,但是作为终端网站来说,没什么太复杂的功能,就是一些简单的CRUD操作,而且数据量可能还比较大。这个时候选用ES这种NoSQL型的数据存储,比传统的复杂的功能务必强大的支持SQL的关系型数据库,更加合适一些。无论是性能,还是吞吐量,可能都会更好。

    document数据路由原理
    我们知道,一个index的数据会被分为多片,每片都在一个shard中。所以说,一个document,只能存在于一个shard中。当客户端创建document的时候,es此时就需要决定说,这个document是放在这个index的哪个shard上。这个过程就称之为document routing,数据路由。

    (1)document路由到shard上是什么意思?
    (2)路由算法:shard = hash(routing) % number_of_primary_shards

    举个例子,一个index有3个primary shard,P0,P1,P2

    每次增删改查一个document的时候,都会带过来一个routing number,默认就是这个document的_id(可能是手动指定,也可能是自动生成)
    routing = _id,假设_id=1

    会将这个routing值,传入一个hash函数中,产出一个rout ing值的hash值,hash(routing) = 21
    然后将hash函数产出的值对这个index的primary shard的数量求余数,21 % 3 = 0
    就决定了,这个document就放在P0上。

    决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的

    无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

    (3)_id or custom routing value
    默认的routing就是_id
    也可以在发送请求的时候,手动指定一个routing value,比如说put /index/type/id?routing=user_id

    手动指定routing value是很有用的,可以保证说,某一类document一定被路由到一个shard上去,那么在后续进行应用级别的负载均衡,以及提升批量读取的性能的时候,是很有帮助的

    (4)primary shard数量不可变的谜底

    document增删改内部原理
    (1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)
    (2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
    (3)实际的node上的primary shard处理请求,然后将数据同步到replica node
    (4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端

    写一致性原理以及quorum机制
    (1)consistency,one(primary shard),all(all shard),quorum(default)
    我们在发送任何一个增删改操作的时候,比如说put /index/type/id,都可以带上一个consistency参数,指明我们想要的写一致性是什么?
    put /index/type/id?consistency=quorum

    one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
    all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
    quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

    (2)quorum机制,写之前必须确保大多数shard都可用,int( (primary + number_of_replicas) / 2 ) + 1,当number_of_replicas>1时才生效
    quroum = int( (primary + number_of_replicas) / 2 ) + 1
    举个例子,3个primary shard,number_of_replicas=1,总共有3 + 3 * 1 = 6个shard
    quorum = int( (3 + 1) / 2 ) + 1 = 3
    所以,要求6个shard中至少有3个shard是active状态的,才可以执行这个写操作

    (3)如果节点数少于quorum数量,可能导致quorum不齐全,进而导致无法执行任何写操作
    3个primary shard,replica=1,要求至少3个shard是active,3个shard按照之前学习的shard&replica机制,必须在不同的节点上,如果说只有2台机器的话,是不是有可能出现说,3个shard都没法分配齐全,此时就可能会出现写操作无法执行的情况

    es提供了一种特殊的处理场景,就是说当number_of_replicas>1时才生效,因为假如说,你就一个primary shard,replica=1,此时就2个shard

    (1 + 1 / 2) + 1 = 2,要求必须有2个shard是活跃的,但是可能就1个node,此时就1个shard是活跃的,如果你不特殊处理的话,导致我们的单节点集群就无法工作

    (4)quorum不齐全时,wait,默认1分钟,timeout,100,30s
    等待期间,期望活跃的shard数量可以增加,最后实在不行,就会timeout
    我们其实可以在写操作的时候,加一个timeout参数,比如说put /index/type/id?timeout=30,这个就是说自己去设定quorum不齐全的时候,es的timeout时长,可以缩短,也可以增长

    document 读请求内部原理

    1、客户端发送请求到任意一个node,成为coordinate node
    2、coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
    3、接收请求的node返回document给coordinate node
    4、coordinate node返回document给客户端
    5、特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了

    bulk api 的奇特json格式与底层性能优化关系大揭秘
    // bulk api奇特的json格式

    {“action”: {“meta”}}\n
    {“data”}\n
    {“action”: {“meta”}}\n
    {“data”}\n

    为什么不能使用下面这种方式或其他方式:
    [{
    “action”: {

    },
    “data”: {

    }
    }]

    1、bulk中的每个操作都可能要转发到不同的node的shard去执行
    2、如果采用比较良好的json数组格式
    允许任意的换行,整个可读性非常棒,读起来很爽,es拿到那种标准格式的json串以后,要按照下述流程去进行处理
    (1)将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是json文本,一份数据是JSONArray对象
    (2)解析json数组里的每个json,对每个请求中的document进行路由
    (3)为路由到同一个shard上的多个请求,创建一个请求数组
    (4)将这个请求数组序列化
    (5)将序列化后的请求数组发送到对应的节点上去

    3、耗费更多内存,更多的jvm gc开销
    我们之前提到过bulk size最佳大小的那个问题,一般建议说在几千条那样,然后大小在10MB左右,所以说,可怕的事情来了。假设说现在100个bulk请求发送到了一个节点上去,然后每个请求是10MB,100个请求,就是1000MB = 1GB,然后每个请求的json都copy一份为jsonarray对象,此时内存中的占用就会翻倍,就会占用2GB的内存,甚至还不止。因为弄成jsonarray之后,还可能会多搞一些其他的数据结构,2GB+的内存占用。

    占用更多的内存可能就会积压其他请求的内存使用量,比如说最重要的搜索请求,分析请求,等等,此时就可能会导致其他请求的性能急速下降
    另外的话,占用内存更多,就会导致java虚拟机的垃圾回收次数更多,更频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致es的java虚拟机停止工作线程的时间更多

    4、现在的奇特格式
    {“action”: {“meta”}}\n
    {“data”}\n
    {“action”: {“meta”}}\n
    {“data”}\n

    (1)不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json
    (2)对每两个一组的json,读取meta,进行document路由
    (3)直接将对应的json发送到node上去

    5、最大的优势在于,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能

    2.19 初识搜索引擎

    search 结果深入解析

    GET /_search
    
    {
      "took": 6,
      "timed_out": false,
      "_shards": {
        "total": 6,
        "successful": 6,
        "failed": 0
      },
      "hits": {
        "total": 10,
        "max_score": 1,
        "hits": [
          {
            "_index": ".kibana",
            "_type": "config",
            "_id": "5.2.0",
            "_score": 1,
            "_source": {
              "buildNum": 14695
            }
          }
        ]
      }
    }
    

    took:整个搜索请求花费了多少毫秒

    hits.total:本次搜索,返回了几条结果
    hits.max_score:本次搜索的所有结果中,最大的相关度分数是多少,每一条document对于search的相关度,越相关,_score分数越大,排位越靠前
    hits.hits:默认查询前10条数据,完整数据,_score降序排序

    shards:shards fail的条件(primary和replica全部挂掉),不影响其他shard。默认情况下来说,一个搜索请求,会打到一个index的所有primary shard上去,当然了,每个primary shard都可能会有一个或多个replic shard,所以请求也可以到primary shard的其中一个replica shard上去。

    timeout:默认无timeout,latency平衡completeness,手动指定timeout,timeout查询执行机制

    timeout=10ms,timeout=1s,timeout=1m
    GET /_search?timeout=10m

    timeout机制详解:
    在这里插入图片描述
    multi-index & multi-type搜索模式解析
    1、multi-index和multi-type搜索模式
    告诉你如何一次性搜索多个index和多个type下的数据

    /_search:所有索引,所有type下的所有数据都搜索出来
    /index1/_search:指定一个index,搜索其下所有type的数据
    /index1,index2/_search:同时搜索两个index下的数据
    /*1,*2/_search:按照通配符去匹配多个索引
    /index1/type1/_search:搜索一个index下指定的type的数据
    /index1/type1,type2/_search:可以搜索一个index下多个type的数据
    /index1,index2/type1,type2/_search:搜索多个index下的多个type的数据
    /_all/type1,type2/_search:_all,可以代表搜索所有index下的指定type的数据

    2、初步图解一下简单的搜索原理
    在这里插入图片描述
    client发送一个搜索请求,会把请求打到所有的primary shard 上去执行,因为每个shard都包含部分数据,所以每个shard上都可能会包含搜索请求的结果

    但是如果primary shard有replica shard,那么请求也可以打到replica shard 上去

    分页搜索与deep paging性能问题

    1、讲解如何使用es进行分页搜索的语法

    // size,from
    
    GET /_search?size=10
    GET /_search?size=10&from=0
    GET /_search?size=10&from=20
    
    GET /test_index/test_type/_search
    
    "hits": {
        "total": 9,
        "max_score": 1,
    

    我们假设将这9条数据分成3页,每一页是3条数据,来实验一下这个分页搜索的效果

    GET /test_index/test_type/_search?from=0&size=3
    
    {
      "took": 2,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 9,
        "max_score": 1,
        "hits": [
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "8",
            "_score": 1,
            "_source": {
              "test_field": "test client 2"
            }
          },
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "6",
            "_score": 1,
            "_source": {
              "test_field": "tes test"
            }
          },
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "4",
            "_score": 1,
            "_source": {
              "test_field": "test4"
            }
          }
        ]
      }
    }
    

    第一页:id=8,6,4

    GET /test_index/test_type/_search?from=3&size=3
    

    第二页:id=2,自动生成,7

    GET /test_index/test_type/_search?from=6&size=3
    

    第三页:id=1,11,3

    2、什么是deep paging问题?为什么会产生这个问题,它的底层原理是什么?
    deep paging:简单来说,就是搜索的特别深,比如总共有60000条数据,每个shard上分了20000条数据。每页是10条数据,这个时候,你要搜索到第1000页,实际上拿到的是10001~10010大家自己思考一下,是第几条到第几条?

    每个shard,其实都要返回的是最后10条,不是这样理解。。。错误!!!

    你的请求首先可能是打到一个不包含这个index的shard的node上去,这个node就是一个coordinator node,那么这个coordinator node就会将搜索请求转发到index的三个shard所在的node上去。
    在这里插入图片描述

    快速掌握query string search

    1、query string基础语法

    GET /test_index/test_type/_search?q=test_field:test
    GET /test_index/test_type/_search?q=+test_field:test # + field必须要有
    GET /test_index/test_type/_search?q=-test_field:test # - field不包含
    

    一个是掌握q=field:search content的语法,还有一个是掌握+和-的含义

    2、_all metadata的原理和作用

    GET /test_index/test_type/_search?q=test
    

    直接可以搜索所有的field,任意一个field包含指定的关键字就可以搜索出来。我们在进行中搜索的时候,难道是对document中的每一个field都进行一次搜索吗?不是的

    es中的_all元数据,在建立索引的时候,我们插入一条document,它里面包含了多个field,此时,es会自动将多个field的值,全部用字符串的方式串联起来,变成一个长的字符串,作为_all field的值,同时建立索引

    后面如果在搜索的时候,没有对某个field指定搜索,就默认搜索_all field,其中是包含了所有field的值的

    举个例子

    {
      "name": "jack",
      "age": 26,
      "email": "jack@sina.com",
      "address": "guamgzhou"
    }
    

    “jack 26 jack@sina.com guangzhou”,作为这一条document的_all field的值,同时进行分词后建立对应的倒排索引

    生产环境不使用

    mapping

    插入几条数据,让es自动为我们建立一个索引

    PUT /website/article/1
    {
      "post_date": "2017-01-01",
      "title": "my first article",
      "content": "this is my first article in this website",
      "author_id": 11400
    }
    
    PUT /website/article/2
    {
      "post_date": "2017-01-02",
      "title": "my second article",
      "content": "this is my second article in this website",
      "author_id": 11400
    }
    
    PUT /website/article/3
    {
      "post_date": "2017-01-03",
      "title": "my third article",
      "content": "this is my third article in this website",
      "author_id": 11400
    }
    

    尝试各种搜索

    GET /website/article/_search?q=2017			3条结果             
    GET /website/article/_search?q=2017-01-01        	3条结果
    GET /website/article/_search?q=post_date:2017-01-01   	1条结果
    GET /website/article/_search?q=post_date:2017         	1条结果
    

    查看es自动建立的mapping,带出什么是mapping的知识点
    自动或手动为index中的type建立的一种数据结构和相关配置,简称为mapping
    dynamic mapping,自动为我们建立index,创建type,以及type对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置
    我们当然,后面会讲解,也可以手动在创建数据之前,先创建index和type,以及type对应的mapping

    GET /website/_mapping/article
    
    {
      "website": {
        "mappings": {
          "article": {
            "properties": {
              "author_id": {
                "type": "long"
              },
              "content": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "post_date": {
                "type": "date"
              },
              "title": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        }
      }
    }
    

    搜索结果为什么不一致,因为es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。所以出现了_all field和post_date field的搜索表现完全不一样。

    精确匹配与全文搜索的对比分析

    1、exact value
    2017-01-01,exact value,搜索的时候,必须输入2017-01-01,才能搜索出来
    如果你输入一个01,是搜索不出来的

    2、full text
    (1)缩写 vs. 全程:cn vs. china
    (2)格式转化:like liked likes
    (3)大小写:Tom vs tom
    (4)同义词:like vs love

    2017-01-01,2017 01 01,搜索2017,或者01,都可以搜索出来
    china,搜索cn,也可以将china搜索出来
    likes,搜索like,也可以将likes搜索出来
    Tom,搜索tom,也可以将Tom搜索出来
    like,搜索love,同义词,也可以将like搜索出来

    就不是说单纯的只是匹配完整的一个值,而是可以对值进行拆分词语后(分词)进行匹配,也可以通过缩写、时态、大小写、同义词等进行匹配

    倒排索引

    doc1:I really liked my small dogs, and I think my mom also liked them.
    doc2:He never liked any dogs, so I hope that my mom will not expect me to liked him.

    在这里插入图片描述

    演示了一下倒排索引最简单的建立的一个过程

    搜索
    mother like little dog,不可能有任何结果

    mother
    like
    little
    dog

    这个是不是我们想要的搜索结果???绝对不是,因为在我们看来,mother和mom有区别吗?同义词,都是妈妈的意思。like和liked有区别吗?没有,都是喜欢的意思,只不过一个是现在时,一个是过去时。little和small有区别吗?同义词,都是小小的。dog和dogs有区别吗?狗,只不过一个是单数,一个是复数。

    normalization,建立倒排索引的时候,会执行一个操作,也就是说对拆分出的各个单词进行相应的处理,以提升后面搜索的时候能够搜索到相关联的文档的概率
    时态的转换,单复数的转换,同义词的转换,大小写的转换
    mom —> mother
    liked —> like
    small —> little
    dogs —> dog

    重新建立倒排索引,加入normalization,再次用mother liked little dog搜索,就可以搜索到了
    在这里插入图片描述

    mother like little dog,分词,normalization
    mother --> mom
    like --> like
    little --> little
    dog --> dog

    doc1和doc2都会搜索出来

    doc1:I really liked my small dogs, and I think my mom also liked them.
    doc2:He never liked any dogs, so I hope that my mom will not expect me to liked him.

    分词器的内部组成

    1、什么是分词器
    切分词语,normalization(提升recall召回率)

    给你一段句子,然后将这段句子拆分成一个一个的单个的单词,同时对每个单词进行normalization(时态转换,单复数转换),分词器
    recall,召回率:搜索的时候,增加能够搜索到的结果的数量

    character filter:在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(hello --> hello),& --> and(I&you --> I and you)
    tokenizer:分词,hello you and me --> hello, you, and, me
    token filter:lowercase,stop word,synonymom,dogs --> dog,liked --> like,Tom --> tom,a/the/an --> 干掉,mother --> mom,small --> little

    一个分词器,很重要,将一段文本进行各种处理,最后处理好的结果才会拿去建立倒排索引

    2、内置分词器的介绍
    Set the shape to semi-transparent by calling set_trans(5)

    standard analyzer:set, the, shape, to, semi, transparent, by, calling, set_trans, 5(默认的是standard)
    simple analyzer:set, the, shape, to, semi, transparent, by, calling, set, trans
    whitespace analyzer:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
    language analyzer(特定的语言的分词器,比如说,english,英语分词器):set, shape, semi, transpar, call, set_tran, 5

    query string的分词以及mapping

    1、query string分词
    query string必须以和index建立时相同的analyzer进行分词
    query string对exact value和full text的区别对待

    date:exact value
    _all:full text

    比如我们有一个document,其中有一个field,包含的value是:hello you and me,建立倒排索引
    我们要搜索这个document对应的index,搜索文本是hell me,这个搜索文本就是query string
    query string,默认情况下,es会使用它对应的field建立倒排索引时相同的分词器去进行分词,分词和normalization,只有这样,才能实现正确的搜索

    我们建立倒排索引的时候,将dogs --> dog,结果你搜索的时候,还是一个dogs,那不就搜索不到了吗?所以搜索的时候,那个dogs也必须变成dog才行。才能搜索到。

    知识点:不同类型的field,可能有的就是full text,有的就是exact value

    post_date,date:exact value
    _all:full text,分词,normalization

    2、mapping引入案例遗留问题大揭秘

    GET /_search?q=2017
    

    搜索的是_all field,document所有的field都会拼接成一个大串,进行分词

    2017-01-02 my second article this is my second article in this website 11400

    在这里插入图片描述

    _all,2017,自然会搜索到3个docuemnt

    GET /_search?q=2017-01-01
    

    _all,2017-01-01,query string会用跟建立倒排索引一样的分词器去进行分词

    2017
    01
    01

    GET /_search?q=post_date:2017-01-01
    

    date,会作为exact value去建立索引

    在这里插入图片描述

    post_date:2017-01-01,2017-01-01,doc1一条document

    GET /_search?q=post_date:2017,这个在这里不讲解,因为是es 5.2以后做的一个优化

    3、测试分词器

    GET /_analyze
    {
      "analyzer": "standard",
      "text": "Text to analyze"
    }
    

    mapping

    (1)往es里面直接插入数据,es会自动建立索引,同时建立type以及对应的mapping
    (2)mapping中就自动定义了每个field的数据类型
    (3)不同的数据类型(比如说text和date),可能有的是exact value,有的是full text
    (4)exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;full text,会经历各种各样的处理,分词,normaliztion(时态转换,同义词转换,大小写转换),才会建立到倒排索引中
    (5)同时呢,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索
    (6)可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型;也可以提前手动创建index和type的mapping,自己对各个field进行设置,包括数据类型,包括索引行为,包括分词器,等等

    mapping,就是index的type的元数据,每个type都有一个自己的mapping,决定了数据类型,建立倒排索引的行为,还有进行搜索的行为

    dynamic mapping

    核心的数据类型

    string
    byte,short,integer,long
    float,double
    boolean
    date
    

    dynamic mapping

    true or false	-->	boolean
    123		-->	long
    123.45		-->	double
    2017-01-01	-->	date
    "hello world"	-->	string/text
    

    查看mapping

    GET /index/_mapping/type
    

    手动建立mapping以及定制string类型数据是否分词

    如何建立索引

    analyzed
    not_analyzed
    no
    

    修改mapping
    只能创建index时手动建立mapping,或者新增field mapping,但是不能update field mapping

    PUT /website
    {
      "mappings": {
        "article": {
          "properties": {
            "author_id": {
              "type": "long"
            },
            "title": {
              "type": "text",
              "analyzer": "english"
            },
            "content": {
              "type": "text"
            },
            "post_date": {
              "type": "date"
            },
            "publisher_id": {
              "type": "text",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
    
    PUT /website
    {
      "mappings": {
        "article": {
          "properties": {
            "author_id": {
              "type": "text"
            }
          }
        }
      }
    }
    

    result:

    {
      "error": {
        "root_cause": [
          {
            "type": "index_already_exists_exception",
            "reason": "index [website/co1dgJ-uTYGBEEOOL8GsQQ] already exists",
            "index_uuid": "co1dgJ-uTYGBEEOOL8GsQQ",
            "index": "website"
          }
        ],
        "type": "index_already_exists_exception",
        "reason": "index [website/co1dgJ-uTYGBEEOOL8GsQQ] already exists",
        "index_uuid": "co1dgJ-uTYGBEEOOL8GsQQ",
        "index": "website"
      },
      "status": 400
    }
    
    PUT /website/_mapping/article
    {
      "properties" : {
        "new_field" : {
          "type" :    "string",
          "index":    "not_analyzed"
        }
      }
    }
    

    测试mapping

    GET /website/_analyze
    {
      "field": "content",
      "text": "my-dogs" 
    }
    
    GET website/_analyze
    {
      "field": "new_field",
      "text": "my dogs"
    }
    

    result:

    {
      "error": {
        "root_cause": [
          {
            "type": "remote_transport_exception",
            "reason": "[4onsTYV][127.0.0.1:9300][indices:admin/analyze[s]]"
          }
        ],
        "type": "illegal_argument_exception",
        "reason": "Can't process field [new_field], Analysis requests are only supported on tokenized fields"
      },
      "status": 400
    }
    

    mapping复杂数据类型以及object类型数据底层结构

    1、multivalue field

    { "tags": [ "tag1", "tag2" ]}
    

    建立索引时与string是一样的,数据类型不能混

    2、empty field

    null[][null]
    

    3、object field

    PUT /company/employee/1
    {
      "address": {
        "country": "china",
        "province": "guangdong",
        "city": "guangzhou"
      },
      "name": "jack",
      "age": 27,
      "join_date": "2017-01-01"
    }
    

    address:object类型

    {
      "company": {
        "mappings": {
          "employee": {
            "properties": {
              "address": {
                "properties": {
                  "city": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  },
                  "country": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  },
                  "province": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  }
                }
              },
              "age": {
                "type": "long"
              },
              "join_date": {
                "type": "date"
              },
              "name": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        }
      }
    }
    
    {
      "address": {
        "country": "china",
        "province": "guangdong",
        "city": "guangzhou"
      },
      "name": "jack",
      "age": 27,
      "join_date": "2017-01-01"
    }
    

    底层结构(行式存储转为列式存储):

    {
        "name":            [jack],
        "age":          [27],
        "join_date":      [2017-01-01],
        "address.country":         [china],
        "address.province":   [guangdong],
        "address.city":  [guangzhou]
    }
    
    {
        "authors": [
            { "age": 26, "name": "Jack White"},
            { "age": 55, "name": "Tom Jones"},
            { "age": 39, "name": "Kitty Smith"}
        ]
    }
    

    底层结构(行式存储转为列式存储):

    {
        "authors.age":    [26, 55, 39],
        "authors.name":   [jack, white, tom, jones, kitty, smith]
    }
    

    search api的基础语法

    1、search api的基本语法

    GET /search
    {}
    
    GET /index1,index2/type1,type2/search
    {}
    
    GET /_search
    {
      "from": 0,
      "size": 10
    }
    

    2、http协议中get是否可以带上request body
    HTTP协议,一般不允许get请求带上request body,但是因为get更加适合描述查询数据的操作,因此还是这么用了

    GET /_search?from=0&size=10
    
    POST /_search
    {
      "from":0,
      "size":10
    }
    

    碰巧,很多浏览器,或者是服务器,也都支持GET+request body模式

    如果遇到不支持的场景,也可以用POST /_search

    Query DSL搜索语法

    1、一个例子让你明白什么是Query DSL

    GET /_search
    {
        "query": {
            "match_all": {}
        }
    }
    

    2、Query DSL的基本语法

    {
        QUERY_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
    
    {
        QUERY_NAME: {
            FIELD_NAME: {
                ARGUMENT: VALUE,
                ARGUMENT: VALUE,...
            }
        }
    }
    

    示例:

    GET /test_index/test_type/_search 
    {
      "query": {
        "match": {
          "test_field": "test"
        }
      }
    }
    

    3、如何组合多个搜索条件
    搜索需求:title必须包含elasticsearch,content可以包含elasticsearch也可以不包含,author_id必须不为111

    GET /website/article/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "title": "elasticsearch"
              }
            }
          ],
          "should": [
            {
              "match": {
                "content": "elasticsearch"
              }
            }
          ],
          "must_not": [
            {
              "match": {
                "author_id": 111
              }
            }
          ]
        }
      }
    }
    
    GET /test_index/_search
    {
        "query": {
                "bool": {
                    "must": { "match":   { "name": "tom" }},
                    "should": [
                        { "match":       { "hired": true }},
                        { "bool": {
                            "must":      { "match": { "personality": "good" }},
                            "must_not":  { "match": { "rude": true }}
                        }}
                    ],
                    "minimum_should_match": 1
                }
        }
    }
    

    query与filter深入对比解密:相关度、性能

    1、filter与query示例

    PUT /company/employee/2
    {
      "address": {
        "country": "china",
        "province": "jiangsu",
        "city": "nanjing"
      },
      "name": "tom",
      "age": 30,
      "join_date": "2016-01-01"
    }
    
    PUT /company/employee/3
    {
      "address": {
        "country": "china",
        "province": "shanxi",
        "city": "xian"
      },
      "name": "marry",
      "age": 35,
      "join_date": "2015-01-01"
    }
    

    搜索请求:年龄必须大于等于30,同时join_date必须是2016-01-01

    GET /company/employee/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "join_date": "2016-01-01"
              }
            }
          ],
          "filter": {
            "range": {
              "age": {
                "gte": 30
              }
            }
          }
        }
      }
    }
    

    2、filter与query对比大解密
    filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响
    query,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序

    一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter
    除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可

    3、filter与query性能
    filter,不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据
    query,相反,要计算相关度分数,按照分数进行排序,而且无法cache结果

    query常用搜索语法

    1、match all

    GET /_search
    {
        "query": {
            "match_all": {}
        }
    }
    

    2、match

    GET /_search
    {
        "query": { "match": { "title": "my elasticsearch article" }}
    }
    

    3、multi match

    GET /test_index/test_type/_search
    {
      "query": {
        "multi_match": {
          "query": "test",
          "fields": ["test_field", "test_field1"]
        }
      }
    }
    

    4、range query

    GET /company/employee/_search 
    {
      "query": {
        "range": {
          "age": {
            "gte": 30
          }
        }
      }
    }
    

    5、term query

    GET /test_index/test_type/_search 
    {
      "query": {
        "term": {
          "test_field": "test hello"
        }
      }
    }
    

    6、terms query

    GET /_search
    {
        "query": { "terms": { "tag": [ "search", "full_text", "nosql" ] }}
    }
    

    7、exist query(2.x中的查询,现在已经不提供了)

    多搜索条件组合查询

    GET /website/article/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "title": "elasticsearch"
              }
            }
          ],
          "should": [
            {
              "match": {
                "content": "elasticsearch"
              }
            }
          ],
          "must_not": [
            {
              "match": {
                "author_id": 111
              }
            }
          ]
        }
      }
    }
    
    {
        "bool": {
            "must":     { "match": { "title": "how to make millions" }},
            "must_not": { "match": { "tag":   "spam" }},
            "should": [
                { "match": { "tag": "starred" }}
            ],
            "filter": {
              "range": { "date": { "gte": "2014-01-01" }} 
            }
        }
    }
    

    bool
    must,must_not,should,filter

    每个子查询都会计算一个document针对它的相关度分数,然后bool综合所有分数,合并为一个分数,当然filter是不会计算分数的

    {
        "bool": {
            "must":     { "match": { "title": "how to make millions" }},
            "must_not": { "match": { "tag":   "spam" }},
            "should": [
                { "match": { "tag": "starred" }}
            ],
            "filter": {
              "bool": { 
                  "must": [
                      { "range": { "date": { "gte": "2014-01-01" }}},
                      { "range": { "price": { "lte": 29.99 }}}
                  ],
                  "must_not": [
                      { "term": { "category": "ebooks" }}
                  ]
              }
            }
        }
    }
    
    GET /company/employee/_search 
    {
      "query": {
        "constant_score": {
          "filter": {
            "range": {
              "age": {
                "gte": 30
              }
            }
          }
        }
      }
    }
    

    如何定位不合法的搜索及其原因

    GET /test_index/test_type/_validate/query?explain
    {
      "query": {
        "math": {
          "test_field": "test"
        }
      }
    }
    
    {
      "valid": false,
      "error": "org.elasticsearch.common.ParsingException: no [query] registered for [math]"
    }
    
    GET /test_index/test_type/_validate/query?explain
    {
      "query": {
        "match": {
          "test_field": "test"
        }
      }
    }
    
    {
      "valid": true,
      "_shards": {
        "total": 1,
        "successful": 1,
        "failed": 0
      },
      "explanations": [
        {
          "index": "test_index",
          "valid": true,
          "explanation": "+test_field:test #(#_type:test_type)"
        }
      ]
    }
    

    一般用在那种特别复杂庞大的搜索下,比如你一下子写了上百行的搜索,这个时候可以先用validate api去验证一下,搜索是否合法

    定制搜索结果的排序规则

    1、默认排序规则
    默认情况下,是按照_score降序排序的

    然而,某些情况下,可能没有有用的_score,比如说filter

    GET /_search
    {
        "query" : {
            "bool" : {
                "filter" : {
                    "term" : {
                        "author_id" : 1
                    }
                }
            }
        }
    }
    

    当然,也可以是constant_score

    GET /_search
    {
        "query" : {
            "constant_score" : {
                "filter" : {
                    "term" : {
                        "author_id" : 1
                    }
                }
            }
        }
    }
    

    2、定制排序规则

    GET /company/employee/_search 
    {
      "query": {
        "constant_score": {
          "filter": {
            "range": {
              "age": {
                "gte": 30
              }
            }
          }
        }
      },
      "sort": [
        {
          "join_date": {
            "order": "asc"
          }
        }
      ]
    }
    

    如何将一个field索引两次来解决字符串排序问题

    如果对一个string field进行排序,结果往往不准确,因为分词后是多个单词,再排序就不是我们想要的结果了
    通常解决方案是,将一个string field建立两次索引,一个分词,用来进行搜索;一个不分词,用来进行排序

    PUT /website 
    {
      "mappings": {
        "article": {
          "properties": {
            "title": {
              "type": "text",
              "fields": {
                "raw": {
                  "type": "string",
                  "index": "not_analyzed"
                }
              },
              "fielddata": true
            },
            "content": {
              "type": "text"
            },
            "post_date": {
              "type": "date"
            },
            "author_id": {
              "type": "long"
            }
          }
        }
      }
    }
    
    PUT /website/article/1
    {
      "title": "first article",
      "content": "this is my second article",
      "post_date": "2017-01-01",
      "author_id": 110
    }
    
    {
      "took": 2,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
          {
            "_index": "website",
            "_type": "article",
            "_id": "2",
            "_score": 1,
            "_source": {
              "title": "first article",
              "content": "this is my first article",
              "post_date": "2017-02-01",
              "author_id": 110
            }
          },
          {
            "_index": "website",
            "_type": "article",
            "_id": "1",
            "_score": 1,
            "_source": {
              "title": "second article",
              "content": "this is my second article",
              "post_date": "2017-01-01",
              "author_id": 110
            }
          },
          {
            "_index": "website",
            "_type": "article",
            "_id": "3",
            "_score": 1,
            "_source": {
              "title": "third article",
              "content": "this is my third article",
              "post_date": "2017-03-01",
              "author_id": 110
            }
          }
        ]
      }
    }
    
    GET /website/article/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "title.raw": {
            "order": "desc"
          }
        }
      ]
    }
    

    相关度评分TF&IDF算法

    1、算法介绍
    relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度

    Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为TF/IDF算法

    Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关
    搜索请求:hello world
    doc1:hello you, and world is very good
    doc2:hello, how are you

    Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关
    搜索请求:hello world
    doc1:hello, today is very good
    doc2:hi world, how are you

    比如说,在index中有1万条document,hello这个单词在所有的document中,一共出现了1000次;world这个单词在所有的document中,一共出现了100次
    doc2更相关

    Field-length norm:field长度,field越长,相关度越弱
    搜索请求:hello world

    doc1:{ “title”: “hello article”, “content”: “babaaba 1万个单词” }
    doc2:{ “title”: “my article”, “content”: “blablabala 1万个单词,hi world” }

    hello world在整个index中出现的次数是一样多
    doc1更相关,title field更短

    2、_score是如何被计算出来的

    GET /test_index/test_type/_search?explain
    {
      "query": {
        "match": {
          "test_field": "test hello"
        }
      }
    }
    
    {
      "took": 6,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 4,
        "max_score": 1.595089,
        "hits": [
          {
            "_shard": "[test_index][2]",
            "_node": "4onsTYVZTjGvIj9_spWz2w",
            "_index": "test_index",
            "_type": "test_type",
            "_id": "20",
            "_score": 1.595089,
            "_source": {
              "test_field": "test hello"
            },
            "_explanation": {
              "value": 1.595089,
              "description": "sum of:",
              "details": [
                {
                  "value": 1.595089,
                  "description": "sum of:",
                  "details": [
                    {
                      "value": 0.58279467,
                      "description": "weight(test_field:test in 0) [PerFieldSimilarity], result of:",
                      "details": [
                        {
                          "value": 0.58279467,
                          "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                          "details": [
                            {
                              "value": 0.6931472,
                              "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                              "details": [
                                {
                                  "value": 2,
                                  "description": "docFreq",
                                  "details": []
                                },
                                {
                                  "value": 4,
                                  "description": "docCount",
                                  "details": []
                                }
                              ]
                            },
                            {
                              "value": 0.840795,
                              "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "termFreq=1.0",
                                  "details": []
                                },
                                {
                                  "value": 1.2,
                                  "description": "parameter k1",
                                  "details": []
                                },
                                {
                                  "value": 0.75,
                                  "description": "parameter b",
                                  "details": []
                                },
                                {
                                  "value": 1.75,
                                  "description": "avgFieldLength",
                                  "details": []
                                },
                                {
                                  "value": 2.56,
                                  "description": "fieldLength",
                                  "details": []
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "value": 1.0122943,
                      "description": "weight(test_field:hello in 0) [PerFieldSimilarity], result of:",
                      "details": [
                        {
                          "value": 1.0122943,
                          "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                          "details": [
                            {
                              "value": 1.2039728,
                              "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "docFreq",
                                  "details": []
                                },
                                {
                                  "value": 4,
                                  "description": "docCount",
                                  "details": []
                                }
                              ]
                            },
                            {
                              "value": 0.840795,
                              "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "termFreq=1.0",
                                  "details": []
                                },
                                {
                                  "value": 1.2,
                                  "description": "parameter k1",
                                  "details": []
                                },
                                {
                                  "value": 0.75,
                                  "description": "parameter b",
                                  "details": []
                                },
                                {
                                  "value": 1.75,
                                  "description": "avgFieldLength",
                                  "details": []
                                },
                                {
                                  "value": 2.56,
                                  "description": "fieldLength",
                                  "details": []
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "value": 0,
                  "description": "match on required clause, product of:",
                  "details": [
                    {
                      "value": 0,
                      "description": "# clause",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "*:*, product of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "boost",
                          "details": []
                        },
                        {
                          "value": 1,
                          "description": "queryNorm",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          },
          {
            "_shard": "[test_index][2]",
            "_node": "4onsTYVZTjGvIj9_spWz2w",
            "_index": "test_index",
            "_type": "test_type",
            "_id": "6",
            "_score": 0.58279467,
            "_source": {
              "test_field": "tes test"
            },
            "_explanation": {
              "value": 0.58279467,
              "description": "sum of:",
              "details": [
                {
                  "value": 0.58279467,
                  "description": "sum of:",
                  "details": [
                    {
                      "value": 0.58279467,
                      "description": "weight(test_field:test in 0) [PerFieldSimilarity], result of:",
                      "details": [
                        {
                          "value": 0.58279467,
                          "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                          "details": [
                            {
                              "value": 0.6931472,
                              "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                              "details": [
                                {
                                  "value": 2,
                                  "description": "docFreq",
                                  "details": []
                                },
                                {
                                  "value": 4,
                                  "description": "docCount",
                                  "details": []
                                }
                              ]
                            },
                            {
                              "value": 0.840795,
                              "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "termFreq=1.0",
                                  "details": []
                                },
                                {
                                  "value": 1.2,
                                  "description": "parameter k1",
                                  "details": []
                                },
                                {
                                  "value": 0.75,
                                  "description": "parameter b",
                                  "details": []
                                },
                                {
                                  "value": 1.75,
                                  "description": "avgFieldLength",
                                  "details": []
                                },
                                {
                                  "value": 2.56,
                                  "description": "fieldLength",
                                  "details": []
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "value": 0,
                  "description": "match on required clause, product of:",
                  "details": [
                    {
                      "value": 0,
                      "description": "# clause",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "*:*, product of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "boost",
                          "details": []
                        },
                        {
                          "value": 1,
                          "description": "queryNorm",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          },
          {
            "_shard": "[test_index][3]",
            "_node": "4onsTYVZTjGvIj9_spWz2w",
            "_index": "test_index",
            "_type": "test_type",
            "_id": "7",
            "_score": 0.5565415,
            "_source": {
              "test_field": "test client 2"
            },
            "_explanation": {
              "value": 0.5565415,
              "description": "sum of:",
              "details": [
                {
                  "value": 0.5565415,
                  "description": "sum of:",
                  "details": [
                    {
                      "value": 0.5565415,
                      "description": "weight(test_field:test in 0) [PerFieldSimilarity], result of:",
                      "details": [
                        {
                          "value": 0.5565415,
                          "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                          "details": [
                            {
                              "value": 0.6931472,
                              "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "docFreq",
                                  "details": []
                                },
                                {
                                  "value": 2,
                                  "description": "docCount",
                                  "details": []
                                }
                              ]
                            },
                            {
                              "value": 0.8029196,
                              "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "termFreq=1.0",
                                  "details": []
                                },
                                {
                                  "value": 1.2,
                                  "description": "parameter k1",
                                  "details": []
                                },
                                {
                                  "value": 0.75,
                                  "description": "parameter b",
                                  "details": []
                                },
                                {
                                  "value": 2.5,
                                  "description": "avgFieldLength",
                                  "details": []
                                },
                                {
                                  "value": 4,
                                  "description": "fieldLength",
                                  "details": []
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "value": 0,
                  "description": "match on required clause, product of:",
                  "details": [
                    {
                      "value": 0,
                      "description": "# clause",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "_type:test_type, product of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "boost",
                          "details": []
                        },
                        {
                          "value": 1,
                          "description": "queryNorm",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          },
          {
            "_shard": "[test_index][1]",
            "_node": "4onsTYVZTjGvIj9_spWz2w",
            "_index": "test_index",
            "_type": "test_type",
            "_id": "8",
            "_score": 0.25316024,
            "_source": {
              "test_field": "test client 2"
            },
            "_explanation": {
              "value": 0.25316024,
              "description": "sum of:",
              "details": [
                {
                  "value": 0.25316024,
                  "description": "sum of:",
                  "details": [
                    {
                      "value": 0.25316024,
                      "description": "weight(test_field:test in 0) [PerFieldSimilarity], result of:",
                      "details": [
                        {
                          "value": 0.25316024,
                          "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                          "details": [
                            {
                              "value": 0.2876821,
                              "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "docFreq",
                                  "details": []
                                },
                                {
                                  "value": 1,
                                  "description": "docCount",
                                  "details": []
                                }
                              ]
                            },
                            {
                              "value": 0.88,
                              "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                              "details": [
                                {
                                  "value": 1,
                                  "description": "termFreq=1.0",
                                  "details": []
                                },
                                {
                                  "value": 1.2,
                                  "description": "parameter k1",
                                  "details": []
                                },
                                {
                                  "value": 0.75,
                                  "description": "parameter b",
                                  "details": []
                                },
                                {
                                  "value": 3,
                                  "description": "avgFieldLength",
                                  "details": []
                                },
                                {
                                  "value": 4,
                                  "description": "fieldLength",
                                  "details": []
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "value": 0,
                  "description": "match on required clause, product of:",
                  "details": [
                    {
                      "value": 0,
                      "description": "# clause",
                      "details": []
                    },
                    {
                      "value": 1,
                      "description": "*:*, product of:",
                      "details": [
                        {
                          "value": 1,
                          "description": "boost",
                          "details": []
                        },
                        {
                          "value": 1,
                          "description": "queryNorm",
                          "details": []
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          }
        ]
      }
    }
    

    3、分析一个document是如何被匹配上的

    GET /test_index/test_type/6/_explain
    {
      "query": {
        "match": {
          "test_field": "test hello"
        }
      }
    }
    

    内核级知识点之doc _value

    搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values

    在建立索引的时候,一方面会建立倒排索引,以供搜索用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用

    doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上

    doc1: hello world you and me
    doc2: hi, world, how are you

    倒排索引
    在这里插入图片描述

    hello you --> hello, you

    hello --> doc1
    you --> doc1,doc2

    sort by age
    doc1: { “name”: “jack”, “age”: 27 }
    doc2: { “name”: “tom”, “age”: 30 }

    正排索引
    在这里插入图片描述

    内核级知识点之query phase

    1、query phase
    (1)搜索请求发送到某一个coordinate node,构构建一个priority queue,长度以paging操作from和size为准,默认为10
    (2)coordinate node将请求转发到所有shard,每个shard本地搜索,并构建一个本地的priority queue
    (3)各个shard将自己的priority queue返回给coordinate node,并构建一个全局的priority queue

    2、replica shard如何提升搜索吞吐量
    一次请求要打到所有shard的一个replica/primary上去,如果每个shard都有多个replica,那么同时并发过来的搜索请求可以同时打到其他的replica上去

    图解:
    在这里插入图片描述

    内核级知识点之fetch phase

    1、fetch phbase工作流
    (1)coordinate node构建完priority queue之后,就发送mget请求去所有shard上获取对应的document
    (2)各个shard将document返回给coordinate node
    (3)coordinate node将合并后的document结果返回给client客户端

    2、一般搜索,如果不加from和size,就默认搜索前10条,按照_score排序

    图解:
    在这里插入图片描述

    搜索相关参数梳理以及bouncing results问题解决方案

    1、preference
    决定了哪些shard会被用来执行搜索操作
    _primary, _primary_first, _local,
    _only_node:xyz, _prefer_node:xyz, _shards:2,3

    bouncing results问题,两个document排序,field值相同;不同的shard上,可能排序不同;每次请求轮询打到不同的replica shard上;每次页面上看到的搜索结果的排序都不一样。这就是bouncing result,也就是跳跃的结果。

    搜索的时候,是轮询将搜索请求发送到每一个replica shard(primary shard),但是在不同的shard上,可能document的排序不同

    解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个replica shard去执行,就不会看到bouncing results了

    2、timeout,已经讲解过原理了,主要就是限定在一定时间内,将部分获取到的数据直接返回,避免查询耗时过长

    3、routing,document文档路由,_id路由,routing=user_id,这样的话可以让同一个user对应的数据到一个shard上去

    4、search_type
    default:query_then_fetch
    dfs_query_then_fetch,可以提升revelance sort精准度

    scoll技术滚动搜索大量数据

    如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完处理完

    使用scoll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来
    scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的
    采用基于_doc进行排序的方式,性能较高
    每次发送scroll请求,我们还需要指定一个scoll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了

    GET /test_index/test_type/_search?scroll=1m
    {
      "query": {
        "match_all": {}
      },
      "sort": [ "_doc" ],
      "size": 3
    }
    
    {
      "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACxeFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAALF8WNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACxhFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYhY0b25zVFlWWlRqR3ZJajlfc3BXejJ3",
      "took": 5,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 10,
        "max_score": null,
        "hits": [
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "8",
            "_score": null,
            "_source": {
              "test_field": "test client 2"
            },
            "sort": [
              0
            ]
          },
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "6",
            "_score": null,
            "_source": {
              "test_field": "tes test"
            },
            "sort": [
              0
            ]
          },
          {
            "_index": "test_index",
            "_type": "test_type",
            "_id": "AVp4RN0bhjxldOOnBxaE",
            "_score": null,
            "_source": {
              "test_content": "my test"
            },
            "sort": [
              0
            ]
          }
        ]
      }
    }
    

    获得的结果会有一个scoll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id

    GET /_search/scroll
    {
        "scroll": "1m", 
        "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACxeFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAALF8WNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACxhFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYhY0b25zVFlWWlRqR3ZJajlfc3BXejJ3"
    }
    

    11,4,7
    3,2,1
    20

    scoll,看起来挺像分页的,但是其实使用场景不一样。分页主要是用来一页一页搜索,给用户看的;scoll主要是用来一批一批检索数据,让系统进行处理的

    2.20 索引管理

    创建、修改以及删除索引

    1、为什么我们要手动创建索引?

    2、创建索引
    创建索引的语法
    PUT /my_index
    {
    “settings”: { … any settings … },
    “mappings”: {
    “type_one”: { … any mappings … },
    “type_two”: { … any mappings … },

    }
    }

    创建索引的示例

    PUT /my_index
    {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 0
      },
      "mappings": {
        "my_type": {
          "properties": {
            "my_field": {
              "type": "text"
            }
          }
        }
      }
    }
    

    3、修改索引

    PUT /my_index/_settings
    {
        "number_of_replicas": 1
    }
    

    4、删除索引

    DELETE /my_index
    DELETE /index_one,index_two
    DELETE /index_*
    DELETE /_all
    

    elasticsearch.yml
    action.destructive_requires_name: true

    修改分词器以及定制自己的分词器

    1、默认的分词器

    standard
    
    standard tokenizer:以单词边界进行切分
    standard token filter:什么都不做
    lowercase token filter:将所有字母转换为小写
    stop token filer(默认被禁用):移除停用词,比如a the it等等
    

    2、修改分词器的设置
    启用english停用词token filter

    PUT /my_index
    {
      "settings": {
        "analysis": {
          "analyzer": {
            "es_std": {
              "type": "standard",
              "stopwords": "_english_"
            }
          }
        }
      }
    }
    
    GET /my_index/_analyze
    {
      "analyzer": "standard", 
      "text": "a dog is in the house"
    }
    result:a dog is in the house
    
    GET /my_index/_analyze
    {
      "analyzer": "es_std",
      "text":"a dog is in the house"
    }
    result:dog house
    

    3、定制化自己的分词器

    PUT /my_index
    {
      "settings": {
        "analysis": {
          "char_filter": {
            "&_to_and": {
              "type": "mapping",
              "mappings": ["&=> and"]
            }
          },
          "filter": {
            "my_stopwords": {
              "type": "stop",
              "stopwords": ["the", "a"]
            }
          },
          "analyzer": {
            "my_analyzer": {
              "type": "custom",
              "char_filter": ["html_strip", "&_to_and"],
              "tokenizer": "standard",
              "filter": ["lowercase", "my_stopwords"]
            }
          }
        }
      }
    }
    
    GET /my_index/_analyze
    {
      "text": "tom&jerry are a friend in the house, <a>, HAHA!!",
      "analyzer": "my_analyzer"
    }
    
    PUT /my_index/_mapping/my_type
    {
      "properties": {
        "content": {
          "type": "text",
          "analyzer": "my_analyzer"
        }
      }
    }
    

    type底层数据结构

    type,是一个index中用来区分类似的数据的,类似的数据,但是可能有不同的fields,而且有不同的属性来控制索引建立、分词器
    field的value,在底层的lucene中建立索引的时候,全部是opaque bytes类型,不区分类型的
    lucene是没有type的概念的,在document中,实际上将type作为一个document的field来存储,即_type,es通过_type来进行type的过滤和筛选
    一个index中的多个type,实际上是放在一起存储的,因此一个index下,不能有多个type重名,而类型或者其他设置不同的,因为那样是无法处理的

    {
       "ecommerce": {
          "mappings": {
             "elactronic_goods": {
                "properties": {
                   "name": {
                      "type": "string",
                   },
                   "price": {
                      "type": "double"
                   },
    	       "service_period": {
    		  "type": "string"
    	       }			
                }
             },
             "fresh_goods": {
                "properties": {
                   "name": {
                      "type": "string",
                   },
                   "price": {
                      "type": "double"
                   },
    	       "eat_period": {
    		  "type": "string"
    	       }
                }
             }
          }
       }
    }
    
    {
      "name": "geli kongtiao",
      "price": 1999.0,
      "service_period": "one year"
    }
    
    {
      "name": "aozhou dalongxia",
      "price": 199.0,
      "eat_period": "one week"
    }
    

    在底层的存储是这样子的。。。。

    {
       "ecommerce": {
          "mappings": {
            "_type": {
              "type": "string",
              "index": "not_analyzed"
            },
            "name": {
              "type": "string"
            }
            "price": {
              "type": "double"
            }
            "service_period": {
              "type": "string"
            }
            "eat_period": {
              "type": "string"
            }
          }
       }
    }
    
    {
      "_type": "elactronic_goods",
      "name": "geli kongtiao",
      "price": 1999.0,
      "service_period": "one year",
      "eat_period": ""
    }
    
    {
      "_type": "fresh_goods",
      "name": "aozhou dalongxia",
      "price": 199.0,
      "service_period": "",
      "eat_period": "one week"
    }
    

    最佳实践,将类似结构的type放在一个index下,这些type应该有多个field是相同的
    假如说,你将两个type的field完全不同,放在一个index下,那么就每条数据都至少有一半的field在底层的lucene中是空值,会有严重的性能问题

    mapping root object

    1、root object
    就是某个type对应的mapping json,包括了properties,metadata(_id,_source,_type),settings(analyzer),其他settings(比如include_in_all)

    PUT /my_index
    {
      "mappings": {
        "my_type": {
          "properties": {}
        }
      }
    }
    

    2、properties
    type,index,analyzer

    PUT /my_index/_mapping/my_type
    {
      "properties": {
        "title": {
          "type": "text"
        }
      }
    }
    

    3、_source
    好处
    (1)查询的时候,直接可以拿到完整的document,不需要先拿document id,再发送一次请求拿document
    (2)partial update基于_source实现
    (3)reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
    (4)可以基于_source定制返回field
    (5)debug query更容易,因为可以直接看到_source

    如果不需要上述好处,可以禁用_source

    PUT /my_index/_mapping/my_type2
    {
      "_source": {"enabled": false}
    }
    

    4、_all
    将所有field打包在一起,作为一个_all field,建立索引。没指定任何field进行搜索时,就是使用_all field在搜索。

    PUT /my_index/_mapping/my_type3
    {
      "_all": {"enabled": false}
    }
    

    也可以在field级别设置include_in_all field,设置是否要将field的值包含在_all field中

    PUT /my_index/_mapping/my_type4
    {
      "properties": {
        "my_field": {
          "type": "text",
          "include_in_all": false
        }
      }
    }
    

    5、标识性metadata
    _index,_type,_id

    定制化dynamic mapping策略

    1、定制dynamic策略
    true:遇到陌生字段,就进行dynamic mapping
    false:遇到陌生字段,就忽略
    strict:遇到陌生字段,就报错

    PUT /my_index
    {
      "mappings": {
        "my_type": {
          "dynamic": "strict",
          "properties": {
            "title": {
              "type": "text"
            },
            "address": {
              "type": "object",
              "dynamic": "true"
            }
          }
        }
      }
    }
    
    PUT /my_index/my_type/1
    {
      "title": "my article",
      "content": "this is my article",
      "address": {
        "province": "guangdong",
        "city": "guangzhou"
      }
    }
    

    result:

    {
      "error": {
        "root_cause": [
          {
            "type": "strict_dynamic_mapping_exception",
            "reason": "mapping set to strict, dynamic introduction of [content] within [my_type] is not allowed"
          }
        ],
        "type": "strict_dynamic_mapping_exception",
        "reason": "mapping set to strict, dynamic introduction of [content] within [my_type] is not allowed"
      },
      "status": 400
    }
    
    PUT /my_index/my_type/1
    {
      "title": "my article",
      "address": {
        "province": "guangdong",
        "city": "guangzhou"
      }
    }
    
    GET /my_index/_mapping/my_type
    
    {
      "my_index": {
        "mappings": {
          "my_type": {
            "dynamic": "strict",
            "properties": {
              "address": {
                "dynamic": "true",
                "properties": {
                  "city": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  },
                  "province": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  }
                }
              },
              "title": {
                "type": "text"
              }
            }
          }
        }
      }
    }
    

    2、定制dynamic mapping策略

    (1)date_detection

    默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mapping成date,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型。

    PUT /my_index/_mapping/my_type
    {
        "date_detection": false
    }
    

    (2)定制自己的dynamic mapping template(type level)

    PUT /my_index
    {
        "mappings": {
            "my_type": {
                "dynamic_templates": [
                    { "en": {
                          "match":              "*_en", 
                          "match_mapping_type": "string",
                          "mapping": {
                              "type":           "string",
                              "analyzer":       "english"
                          }
                    }}
                ]
    }}}
    
    PUT /my_index/my_type/1
    {
      "title": "this is my first article"
    }
    
    PUT /my_index/my_type/2
    {
      "title_en": "this is my first article"
    }
    

    title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的
    title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到了

    (3)定制自己的default mapping template(index level)

    PUT /my_index
    {
        "mappings": {
            "_default_": {
                "_all": { "enabled":  false }
            },
            "blog": {
                "_all": { "enabled":  true  }
            }
        }
    }
    

    基于scoll+bulk+索引别名实现零停机重建索引

    1、重建索引

    一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中

    批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scoll就查询指定日期的一段数据,交给一个线程即可

    (1)一开始,依靠dynamic mapping,插入数据,但是不小心有些数据是2017-01-01这种日期格式的,所以title这种field被自动映射为了date类型,实际上它应该是string类型的

    PUT /my_index/my_type/3
    {
      "title": "2017-01-03"
    }
    
    {
      "my_index": {
        "mappings": {
          "my_type": {
            "properties": {
              "title": {
                "type": "date"
              }
            }
          }
        }
      }
    }
    

    (2)当后期向索引中加入string类型的title值的时候,就会报错

    PUT /my_index/my_type/4
    {
      "title": "my first article"
    }
    

    result:

    {
      "error": {
        "root_cause": [
          {
            "type": "mapper_parsing_exception",
            "reason": "failed to parse [title]"
          }
        ],
        "type": "mapper_parsing_exception",
        "reason": "failed to parse [title]",
        "caused_by": {
          "type": "illegal_argument_exception",
          "reason": "Invalid format: \"my first article\""
        }
      },
      "status": 400
    }
    

    (3)如果此时想修改title的类型,是不可能的

    PUT /my_index/_mapping/my_type
    {
      "properties": {
        "title": {
          "type": "text"
        }
      }
    }
    result:
    {
      "error": {
        "root_cause": [
          {
            "type": "illegal_argument_exception",
            "reason": "mapper [title] of different type, current_type [date], merged_type [text]"
          }
        ],
        "type": "illegal_argument_exception",
        "reason": "mapper [title] of different type, current_type [date], merged_type [text]"
      },
      "status": 400
    }
    

    (4)此时,唯一的办法,就是进行reindex,也就是说,重新建立一个索引,将旧索引的数据查询出来,再导入新索引

    (5)如果说旧索引的名字,是old_index,新索引的名字是new_index,终端java应用,已经在使用old_index在操作了,难道还要去停止java应用,修改使用的index为new_index,才重新启动java应用吗?这个过程中,就会导致java应用停机,可用性降低

    (6)所以说,给java应用一个别名,这个别名是指向旧索引的,java应用先用着,java应用先用goods_index alias来操作,此时实际指向的是旧的my_index
    PUT /my_index/_alias/goods_index

    (7)新建一个index,调整其title的类型为string

    PUT /my_index_new
    {
      "mappings": {
        "my_type": {
          "properties": {
            "title": {
              "type": "text"
            }
          }
        }
      }
    }
    

    (8)使用scroll api将数据批量查询出来

    GET /my_index/_search?scroll=1m
    {
        "query": {
            "match_all": {}
        },
        "sort": ["_doc"],
        "size":  1
    }
    
    {
      "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAADpAFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAA6QRY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAAOkIWNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAADpDFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAA6RBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3",
      "took": 1,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": null,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "2",
            "_score": null,
            "_source": {
              "title": "2017-01-02"
            },
            "sort": [
              0
            ]
          }
        ]
      }
    }
    

    (9)采用bulk api将scoll查出来的一批数据,批量写入新索引

    POST /_bulk
    { "index":  { "_index": "my_index_new", "_type": "my_type", "_id": "2" }}
    { "title":    "2017-01-02" }
    

    (10)反复循环8~9,查询一批又一批的数据出来,采取bulk api将每一批数据批量写入新索引

    (11)将goods_index alias切换到my_index_new上去,java应用会直接通过index别名使用新的索引中的数据,java应用程序不需要停机,零提交,高可用

    POST /_aliases
    {
        "actions": [
            { "remove": { "index": "my_index", "alias": "goods_index" }},
            { "add":    { "index": "my_index_new", "alias": "goods_index" }}
        ]
    }
    

    (12)直接通过goods_index别名来查询,是否ok

    GET /goods_index/my_type/_search
    

    2、基于alias对client透明切换index

    PUT /my_index_v1/_alias/my_index
    

    client对my_index进行操作

    reindex操作,完成之后,切换v1到v2

    POST /_aliases
    {
        "actions": [
            { "remove": { "index": "my_index_v1", "alias": "my_index" }},
            { "add":    { "index": "my_index_v2", "alias": "my_index" }}
        ]
    }
    

    2.21 内核原理

    倒排索引

    倒排索引,是适合用于进行搜索的

    倒排索引的结构
    (1)包含这个关键词的document list
    (2)包含这个关键词的所有document的数量:IDF(inverse document frequency)
    (3)这个关键词在每个document中出现的次数:TF(term frequency)
    (4)这个关键词在这个document中的次序
    (5)每个document的长度:length norm
    (6)包含这个关键词的所有document的平均长度

    在这里插入图片描述

    倒排索引不可变的好处
    (1)不需要锁,提升并发能力,避免锁的问题
    (2)数据不变,一直保存在os cache中,只要cache内存足够
    (3)filter cache一直驻留在内存,因为数据不变
    (4)可以压缩,节省cpu和io开销

    倒排索引不可变的坏处:每次都要重新构建整个索引

    document写入原理

    (1)数据写入buffer
    (2)commit point
    (3)buffer中的数据写入新的index segment
    (4)等待在os cache中的index segment被fsync强制刷到磁盘上
    (5)新的index sgement被打开,供search使用
    (6)buffer被清空

    每次commit point时,会有一个.del文件,标记了哪些segment中的哪些document被标记为deleted了
    搜索的时候,会依次查询所有的segment,从旧的到新的,比如被修改过的document,在旧的segment中,会标记为deleted,在新的segment中会有其新的数据
    在这里插入图片描述

    优化写入流程实现NRT近实时

    现有流程的问题,每次都必须等待fsync将segment刷入磁盘,才能将segment打开供search使用,这样的话,从一个document写入,到它可以被搜索,可能会超过1分钟!!!这就不是近实时的搜索了!!!主要瓶颈在于fsync实际发生磁盘IO写数据进磁盘,是很耗时的。

    写入流程别改进如下:
    (1)数据写入buffer
    (2)每隔一定时间,buffer中的数据被写入segment文件,但是先写入os cache
    (3)只要segment写入os cache,那就直接打开供search使用,不立即执行commit

    数据写入os cache,并被打开供搜索的过程,叫做refresh,默认是每隔1秒refresh一次。也就是说,每隔一秒就会将buffer中的数据写入一个新的index segment file,先写入os cache中。所以,es是近实时的,数据写入到可以被搜索,默认是1秒。

    POST /my_index/_refresh,可以手动refresh,一般不需要手动执行,没必要,让es自己搞就可以了

    比如说,我们现在的时效性要求,比较低,只要求一条数据写入es,一分钟以后才让我们搜索到就可以了,那么就可以调整refresh interval

    PUT /my_index
    {
      "settings": {
        "refresh_interval": "30s" 
      }
    }
    

    commit。。。稍后就会讲。。。
    在这里插入图片描述

    优化写入流程实现durability可靠存储

    再次优化的写入流程
    (1)数据写入buffer缓冲和translog日志文件
    (2)每隔一秒钟,buffer中的数据被写入新的segment file,并进入os cache,此时segment被打开并供search使用
    (3)buffer被清空
    (4)重复1~3,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加
    (5)当translog长度达到一定程度的时候,commit操作发生
    (5-1)buffer中的所有数据写入一个新的segment,并写入os cache,打开供使用
    (5-2)buffer被清空
    (5-3)一个commit ponit被写入磁盘,标明了所有的index segment
    (5-4)filesystem cache中的所有index segment file缓存数据,被fsync强行刷到磁盘上
    (5-5)现有的translog被清空,创建一个新的translog

    基于translog和commit point,如何进行数据恢复
    fsync+清空translog,就是flush,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush

    POST /my_index/_flush,一般来说别手动flush,让它自动执行就可以了

    translog,每隔5秒被fsync一次到磁盘上。在一次增删改操作之后,当fsync在primary shard和replica shard都成功之后,那次增删改操作才会成功

    但是这种在一次增删改时强行fsync translog可能会导致部分操作比较耗时,也可以允许部分数据丢失,设置异步fsync translog

    PUT /my_index/_settings
    {
        "index.translog.durability": "async",
        "index.translog.sync_interval": "5s"
    }
    

    在这里插入图片描述
    终极版本的es数据写入流程
    在这里插入图片描述

    最后优化写入流程实现海量磁盘文件合并

    每秒一个segment file,文件过多,而且每次search都要搜索所有的segment,很耗时

    默认会在后台执行segment merge操作,在merge的时候,被标记为deleted的document也会被彻底物理删除

    每次merge操作的执行流程
    (1)选择一些有相似大小的segment,merge成一个大的segment
    (2)将新的segment flush到磁盘上去
    (3)写一个新的commit point,包括了新的segment,并且排除旧的那些segment
    (4)将新的segment打开供搜索
    (5)将旧的segment删除

    POST /my_index/_optimize?max_num_segments=1,尽量不要手动执行,让它自动默认执行就可以了

    在这里插入图片描述

    Elasticsearch顶尖高手系列-快速入门篇原视频

    视频分享给大家看
    链接:https://pan.baidu.com/s/1ma6Qcezr5_Zu0V8RaCLU-A
    提取码:9cyw
    复制这段内容后打开百度网盘手机App,操作更方便哦

    展开全文
  • centos安装elasticsearch

    2020-09-27 14:31:51
    elasticsearch,地址: https://www.elastic.co/downloads/elasticsearch 下载完,后解压,到bin目录下运行./elasticsearch 因为我下载的是7.9.2版本的,但是系统安装的是jdk8,而这个版本的es要求的jdk版本最低是...

    下elasticsearch,地址:
    https://www.elastic.co/downloads/elasticsearch

    下载完,后解压,到bin目录下运行./elasticsearch
    因为我下载的是7.9.2版本的,但是系统安装的是jdk8,而这个版本的es要求的jdk版本最低是11,否则会提示如下错误,

    future versions of Elasticsearch will require Java 11; your Java version from [/home/parallels/program/jdk1.8.0_261/jre] does not meet this requirement
    

    只好重新安装jdk11
    https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

    安装好了后,重新运行es

    [1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
    [2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
    [3]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
    

    又有问题。一一解决
    显示每个进程打开文件数太小,查看系统环境。

    [parallels@10 bin]$ ulimit -Hn
    4096
    [parallels@10 bin]$ ulimit -Sn
    1024
    

    解决【1】修改文件 /etc/security/limits.conf 在文件末尾增加两行

    *  soft nofile 65535
    *  hard nofile 65537
    

    解决【2】修改配置文件vim /etc/sysctl.conf ,在文件末尾增加一行

    vm.max_map_count=655360
    

    执行命令生效

    [parallels@10 bin]$ sudo sysctl -p
    

    修改配置文件后重新登录
    解决【3】修改config/elasticsearch.yml配置文件
    一并修改其他配置

    cluster.initial_master_nodes: ["node-1"]
    network.host: 10.211.55.14
    http.port: 9200
    path.logs: /path/to/logs
    path.data: /path/to/data
    node.name: node-1
    cluster.name: my-elasticsearch
    

    创建/path/to/logs|data目录,并复制权限

    重新启动es
    启动成功,访问成功

    van@van Downloads$ curl http://10.211.55.14:9200/
    {
      "name" : "node-1",
      "cluster_name" : "my-elasticsearch",
      "cluster_uuid" : "daqaM63CTmaPVtkgzYFF3A",
      "version" : {
        "number" : "7.9.2",
        "build_flavor" : "default",
        "build_type" : "tar",
        "build_hash" : "d34da0ea4a966c4e49417f2da2f244e3e97b4e6e",
        "build_date" : "2020-09-23T00:45:33.626720Z",
        "build_snapshot" : false,
        "lucene_version" : "8.6.2",
        "minimum_wire_compatibility_version" : "6.8.0",
        "minimum_index_compatibility_version" : "6.0.0-beta1"
      },
      "tagline" : "You Know, for Search"
    }
    

    值得注意的是:es不允许以root身份启动,否则会提示错误

     org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root 
    
    展开全文
  • SpringBoot下使用ElasticSearch教程(一)

    千次阅读 2018-11-12 16:37:51
    一:ElasticSearch简单介绍.  ES是一款实时分析的分布式搜索引擎,易扩展集群,全文搜索,分布式实时文件存储,基于RESTful风格的简单易用API,底层使用Lucene封装,使用Java语言开发,和多种语言容易交互使用,速度非常快...
  • ElasticSearch7.6.x

    2020-09-26 13:33:53
    ElasticSearch7.6.x ElasticSearchClientConfig package com.example.demo.config; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client....
  • 通过前面两篇文章的介绍,我们大概已经知道了 Elasticsearch处理数据的流程,其中在Elasticsearch和磁盘之间还有一层称为FileSystem Cache的系统缓存,正是由于这层cache的存在才使得es能够拥有更快搜索响应能力。...
  • Elasticsearch教程-从入门到精通(转)

    万次阅读 2020-09-03 09:10:57
    utm_medium=referral 各位运维同行朋友们,大家好,非常高兴能有这么个机会与大家一起交流一些技术问题。此前的各位分享达人们在技术领域或管理领域均有十分精彩的分享,他们带给我们的是多个领域中研究或实践的最...
  • elasticSearch中文文档

    万次阅读 2020-08-03 18:32:51
    分词器调试 以下是分词器调试的全部过程 开始调试 查看分词结果语句 //myindex:index //_analyze:要进行的查看分词结果操作 //whitespace:使用的分词器 //You're the 1st runner home!:实验用的文本数据 ...
  • Elasticsearch之开启外网访问-yellowcong

    万次阅读 热门讨论 2017-12-07 13:14:34
    启动elastic 后,发现elastic不可以直接外网访问,需要修改elasticsearch.yml 配置文件,添加network.host 配置,才可以外方访问到
  • ElasticSearch停止启动

    千次阅读 2017-03-08 10:25:40
    ElasticSearch停止启动
  • Elasticsearch在后台启动

    万次阅读 2016-04-21 09:30:10
    Elasticsearch在linux下使用命令sh elasticsearch start,按键ctrl+c的时候程序就会stop掉,如何将程序在后台启动呢? 需要使用:./elasticsearch -d 这时执行的时候会出现没有权限./elasticsearch: Permission ...
  • elasticsearch查看所有索引

    万次阅读 2019-06-28 15:08:00
    GET _cat/indices
  • elasticsearch-head 无法连接elasticsearch的原因和解决

    万次阅读 热门讨论 2018-09-29 11:24:40
    首先确定的是,elasticsearch-head启动无误,elasticsearch启动无误。 点击连接elasticsearch出现这个问题:提示未连接 作为一个web开发,通常会查看请求是否出错,关键是请求没有出错,就是没有返回值: ...
  • elasticsearch查看所有index

    万次阅读 2016-10-17 12:18:38
    curl 'localhost:9200/_cat/indices?v'
  • 有一种情况,当我们启动elasticsearch之后, 经过很长一段时间没有操作, 自己已经忘了是否已经启动了elasticsearch, 这时候我们可以通过下面的方式验证是否启动,并重新启动: step1 查找ES进程号 ps -ef | ...
1 2 3 4 5 ... 20
收藏数 420,119
精华内容 168,047
关键字:

elasticsearch