【大数据】elasticSearch笔记

ElasticSearch顶尖高手系列——基础课程

大数据技术栈:Haddop、spark、storm、hive、hbase、zookeeper、elasticsearch

核心知识篇

课程特点:

使用了elasticsearch5.2版本进行讲解,市面上的书籍和视频几乎都停留在2.x版本

深入浅出es核心工作原理、全部进行了手工画图讲解、完全不同于市面上已有视频的ppt讲解。

涵盖elasticsearch所有核心知识点,系统化,体系完整详细,有一定深度,包括完整的java开发示范。

  1. 全面知识体系,包括了工作原理、文档管理、索引管理、搜索、聚合分析、分词、数据建模、java api等知识。
  2. 知识足够深入和细节、完全秒杀市面上已有的书籍和视频,比如index segment merge原理,乐观锁并发控制,索引别名与零停机,相关度评分算法与定制,近似聚合算法,doc values和fielddata机制原理,父子关系数据建模、java api执行scoll search等各种复杂操作,等等。

全程每讲必练,大量的案例实战和上机实验,实战岀真知,实战中学习知识,没有任何一讲是干讲ppt的。

包含一个实战项目,运用学到的知识,开发一个小型门户网站的搜索引擎和数据分析系统,运用了es几乎所有的核心知识,不像市面上的demo项目。

课程学完之后,学员可以掌握es所有的核心知识点,理解es核心原理,并且能够熟练动手操作所有学到的知识和功能,并且能够掌握es集群的基本部署,并且基于java开发一个适用于中小型应用系统的搜索引擎以及数据分析系统,达到学完即可上手到中小型醒目中使用的程度。

高手进阶篇

  1. 使用elasticsearch5.2版本讲解。
  2. 包含市面上几乎没有的所有leasticsearch高级知识点:包含地理位置搜索与聚合分析,term vector、suggester search,搜索模版定制,query执行剖析,数十种最全面的聚合分析,span query,shard分配定制,es插件开发,等等高级的知识点,这些知识市面上已有的书籍或者视频几乎没有。
  3. 全程每讲必练,大量的案例实战和上机实验。
  4. 包含一个复杂实战项目,运用学到的知识,开发一个复杂的基于地理位置的智能餐厅app的搜索引擎和数据分析系统,运用es从核心篇到高级篇的所有高阶知识点
  5. 课程学完之后,学员可以掌握es从核心到高阶的所有知识点,掌握完整的有深度的es知识体系,同时能够动手操作所有的知识点和功能,最后通过项目实战,能够在中小型公司中,基于java开发一个可以基于地理位置进行搜索的高级搜索引擎,以及使用复杂聚合操作进行分析的高级实时数据分析系统。

大型集群运维优化篇

  1. 最全面的elasticsearch运维、管理、调优、故障处理的知识体系:企业级监控体系的搭建,企业级集群部署,集群日常管理策略,集群版本升级方案,集群基准压测方案,集群数据的备份和恢复,系统核心配置参数,性能调优方案,故障处理方案。
  2. 全程每讲必练,大量上机实验,所有的运维、管理、部署、优化,全部上机实验。
  3. 从零开始,逐步搭建出一个大型可扩展、高性能、监控体系完善、管理体系健全的分布式集群。
  4. 学完课程之后,除了可以开发复杂的es搜索/分析系统之外,还可以掌握任何一个公司里,从零开始搭建一个分布式的大型es集群,并制定完善的监控,运维,管理,优化等方案。

大型项目架构篇

  1. 涵盖elasticsearch目前最核心的两盒应用领域,垂直搜索引擎,实时数据分析。
  2. 开发出3个企业级的大型复杂项目,是完全真实的大型企业项目,电商搜索引擎,电商实时数据分析平台,
    1. 包括了检索、数据更新、排序、分词、query分析等各个核心模块,同时架构上实现了复杂的缓存机制,热启动机制,防雪崩机制,自动降级高可用机制,等等。
    2. 大型电商实时数据分析平台,完整、复杂而且大型的电商数据分析,包括了完善的数据分析指标体系(运营指标、流量指标、销售转化指标、客户价值指标、商品指标、营销指标、风险控制指标、市场竞争指标),一站式构建出复杂的,企业级的,电商领域数据分析平台。
    3. 之所以单独拉出一篇做大型项目实战,是因为之前几篇讲的项目,重点是采用大公司的大型复杂项目作为背景,可以掌握基于es技术的大型项目架构达到架构是的水平,比如说大型电商搜索引擎,主要运用es来实现,但是es之外还有大型系统的复杂架构需要讲解,还有大型的电商实时数据分析平台,主要特点是业务繁琐复杂,需要基于es来构建出大型的数据分析平台架构。
  3. 学完大型项目架构篇之后,在之前课程中掌握了es的企业级集群运维管理,复杂搜素引擎和数据分析引用开发的技术基础之上,现在可以运用所学知识,结合电商的领域知识,开发出业务真实而且复杂的,大型的搜索、分析等电商系统以及相关架构,彻底掌握运用es和elk相关技术栈在中大型企业中,开发大型项目架构的经验和能力。

ELK深入浅出篇

  1. 电商系统日志检索平台,采用ELK技术栈,会详细讲解logstash和kibana两个技术,包括logstash的插件机制,监控方案,大规模扩展方案,升级方案,性能调优方案,kibana的可视化展现方案,同时讲解如何使用ELK技术栈开发出大规模日志存储和检索平台。
  2. 最后需要真正深入重点讲解logstash和kibana两门技术,并结合es技术,采用elk技术栈,实现大型企业级的日志采集和检索平台。
  3. 学完课程后,应该可以彻底惊精通ELK技术栈,并能够使用ELK技术栈快速搭建日志检索平台,以及数据的可视化平台。

一、什么是elasticSearch

1、什么是搜索?

百度:如果我们想要寻找任何想要感兴趣的相关信息,

百度 = 搜索 ,这是不对的。

垂直搜索(站内搜索)

互联网的搜索:电商网站,招聘网站,新闻网站,各种app

it系统的搜索:OA软件,办公自动化软件,会议管理,日程管理,项目管理,员工管理,搜索“张三”,“张三儿”;如电商网站,卖家,后台管理系统,搜索“牙膏”的订单,搜索到关于牙膏相关的订单。

搜索,就是在任何场景下,寻找你想要的信息,这时候,就会输入你要搜索的关键字,然后期望找到这个关键字相关的信息。

2、如果用数据库做搜索会怎么样?

做软件开发的话,数据都是存储在数据库里面的,比如电商网站商品信息,招聘网站的职位信息,新闻网站的新闻信息,等等,从技术角度考虑的话电商网站内部的搜索功能的话就可以考虑使用数据库进行搜索。

分表分字段去做,数据库的话就是一条一条的全本扫描。

  • 比方说每条记录的指定字段的文本,可能会很长,比如说“商品描述”字段的长度,有长达数千个甚至是上万个字符,这个时候每次都要对每条记录的所有文本进行扫描,懒判断说你包含不包含我指定的这个关键词(“牙膏”)
  • 并且还不能将搜索词拆分开来,尽可能的去搜索更多的符合你的期望的结果,比如输入“生化危” 就搜索不出来“生化危机”相关的信息。

用数据库来实现搜索的话,是不太靠谱的,而且效果会很差。性能上也会很差。

3、什么是全文检索和Lucene?

全文检索:倒排索引

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

4、什么是Elasticsearch?

elasticsearch是把lucene封装起来了,当数据部署在单台机器上的时候,如果数据量很大需要部署在多台机器上如果遇到高可用、数据备份、高性能的建立索引、或者跟多台机器进行通信的时候就会特别麻烦,如果必须使用多台机器去进行数据存储和搜索,如果我们自己去实现这些的话,会很麻烦,于是elasticsearch就是做这个工作的。

es本身实现了一些性能的优化,以及分布式的东西,以及提供了很多lucene不能提供的东西:

  1. 自动维护数据的分布到多个节点的索引的建立,还有搜索请求分布到多个节点的执行。
  2. 自动维护数据的冗余副本,保证说,一些机器宕机了,不会丢失任何的数据。
  3. 封装了更多的高级功能,以给我们提供更多高级的支持,让我们快速的开发应用;开发更加复杂的代码;复杂的搜索功能;聚合分析的功能,基于地理位置的搜素(距离我当前位置1公里以内的烤肉店)

二、elasticsearch的功能、应用场景以及特点介绍

elasticsearch的功能

1、分布式的搜索引擎和数据分析引擎

​ 搜索:百度、网站的站内搜索、it系统的检索。

​ 数据分析:电商网站,比如最近7天牙膏这种商品销量排行前10的商家有哪些;新闻网站,最近一个月访问量排名前3的新闻版块是哪些。

​ 分布式:搜索,数据分析

2、全文检索,结构化检索,数据分析:

​ 全文检索:我想搜索商品名称包含牙膏的商品, select * from products where product_name like "%牙膏%"

​ 结构化检索: 我想搜索商品分类为日用品的商品都有哪些, select * from products where category_id ="日化用品"

​ 数据分析:我想分析每一个商品分类下都有哪些商品,select catagory_id , count(*) from products group by category_id

3、对海量数据进行近实时的处理

​ 分布式:es自动可以将海量数据分散到多台服务器上去存储和检索

​ 海量数据的处理:分布式以后,就可以采用大量的服务器去存储和检索数据,自然而然就可以处理海量数据处理了。

​ 近实时:检索数据要1分钟(这就不叫近实时,可以称之为离线批处理batch-processing);在秒级别对数据进行搜索和分析

​ 而跟分布式/海量数据相反的:lucene,单机应用,只能在单台服务器上使用,最多只能处理单台服务器可以处理的数据量。

elasticsearch的适用场景

国外:

  • 维基百科,类似百度百科,牙膏的维基百科,全文检索,代码高亮显示,搜索推荐。
  • The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击、浏览、收藏、评论)+ 社交网络数据(对xx新闻的相关看法),数据分析,给每篇新闻文章的作者,让他知道他的文章的公众反馈(好、坏、热门、垃圾、崇拜、鄙视等)
  • Stack Overflow(程序异常讨论网站),全文检索,搜索相关问题和答案。
  • Github(开源代码管理),搜索上千亿行代码。
  • 电商网站,检索商品,亚马逊等。
  • 日志数据分析,logstash采集日志,es进行复杂的数据分析。 (ELK技术,es+logstash+kibana)
  • 商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户。
  • BI系统(Business intelligence,es执行数据分析和挖掘)(比如大型商场集团分析xx区域近三年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表,**区,最近3年每年消费金额呈100%增长,)es执行数据分析和挖掘,kibana进行数据可视化。

国内:站内搜索(电商、招聘、门户,等等),IT系统搜素(oa、crm、erp等等),数据分析(es热门的一个使用场景)

elasticsearch的特点

  • 可以作为一个大型分布式的集群(数百台服务器)技术,处理PB级别数据,服务大公司;也可以运行在单机上,服务小公司。
  • elasticsearch不是什么新的技术,主要是把全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的es;具体是lucene(全文检索),商用的数据分析软件(现有现成的),分布式数据库(mycat)
  • 对于用户而言,开箱即用,非常简单,作为中小型的应用,直接3分钟部署一下就可以在生产环境的系统来使用了,数据量不大,操作不是太复杂。
  • 相比较于数据库而言面对很多领域都是不够用的,(比如事物、还有各种联机事务型的操作)特殊的功能,比如全文检索,同义词处理,相关度排行,复杂数据分析,海量数据的近实时处理;elasticserch作为传统数据库的补充,提供了数据库所不能提供的很多其他的功能。

三、手工画图剖析elasticsearch的核心概念:NRT、索引、分片、副本等

1、lucene和elasticsearch的前世今生

lucene,最先进、功能最强大的搜索哭,直接基于lucene开发的话非常复杂,api复杂(如果要是心一些简单的功能,需要写大量的java代码)需要深入理解原理以及(各种索引结构)

elasticsearch,基于lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他)

  • 分布式的文档存储引擎
  • 分布式的搜索引擎和分析引擎
  • 分布式,支持PB即数据

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

2、elasticsearch的核心概念

  • Near Realtime(NRT):近实时,两个意思,从数据写入到可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级。
  • Cluster(集群):包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称:默认是elasticsearch)来决定的,对于一个中小型企业来说,刚开始一个集群就一个节点很正常。
  • Node(节点):集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么他们就会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群。
  • Document(文档):es中最小的数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以存储多个document(不过在最新的版本中type已经慢慢被移除了)。一个document里面有多个field,每个field就是一个数据字段。一个document里面有多个field,每个field就是一个数据字段。
1
2
3
4
5
6
7
8
product document
{
"product_id":"1",
"product_name":"高露洁牙膏",
"product_desc":"高效美白",
"category_id":"2",
"category_name":"日化用品"
}
  • Index(索引):包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index名称包含很多document,一个index就包含了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。

  • type(类型):每个索引index中都可以有一个或者多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户type,博客数据type,评论数据type。

    商品index,里面存放了所有的商品数据,商品document

    但是商品有分为很多类,每个种类的document的field可能不太一样,比如说电器用品,可能还包含一些诸如售后时间范围这样特殊的field;生鲜商品还包含一些生鲜保质期之类的特殊field。

    type:日化商品type;电器商品type;生鲜商品type

    日化商品type: product_id,product_name,product_desc,category_id,category_name

    电器商品type: product_id,product_name,product_desc,category_id,category_name,service_period

    生鲜商品type: product_id,product_name,product_desc,category_id,category_name,eat_period

    每个type里面,都会包含一堆document

    { "product_id":"2", "product_name":"长虹电视", "product_desc":"4k高清", "category_id":"3", "category_name":"电器",

    ​ "service_period": "1年"

    }

    { "product_id":"3", "product_name":"基围虾", "product_desc":"纯天然,冰岛产", "category_id":"4", "category_name":"生鲜",

    ​ "eat_period": "7天"

    }

  • shard(分片):单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展存储更多的数据了,让搜索和分析等操作分布到多台服务器上进行执行,提升吞吐量和性能。每个shard都是一个lucene index。

  • replica(副本): 任何一个服务器随时可能出现故障或者宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据的不丢失。多个replica可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能进行修改,默认5个),replica shard(随时修改参数,默认1个(意思是每个shard都有一个replica)),默认每个索引10个shard,5个primary shard ,5个replica shard,最小的高可用配置,是两台服务器。

3、elasticsearch核心概念 vs. 数据库核心概念

elasticsearch 数据库


Document 行

Type 表

Index 数据库

三、安装和启动elasticsearch

1、安装JDK,至少1.8.0_73以上版本,java -version 2、下载和解压缩Elasticsearch安装包,目录结构 3、启动Elasticsearch:bin.bat,es本身特点之一就是开箱即用,如果是中小型应用,数据量少,操作不是很复杂,直接启动就可以用了

4、检查ES是否启动成功:http://localhost:9200/?pretty

name: node名称 cluster_name: 集群名称(默认的集群名称就是elasticsearch) version.number: 5.2.0,es版本号

{ "name" : "4onsTYV", "cluster_name" : "elasticsearch", "cluster_uuid" : "nKZ9VK_vQdSQ1J0Dx9gx1Q", "version" : { "number" : "5.2.0", "build_hash" : "24e05b9", "build_date" : "2017-01-24T19:52:35.800Z", "build_snapshot" : false, "lucene_version" : "6.4.0" }, "tagline" : "You Know, for Search" }

5、修改集群名称:elasticsearch.yml 6、下载和解压缩Kibana安装包,使用里面的开发界面,去操作elasticsearch,作为我们学习es知识点的一个主要的界面入口 7、启动Kibana:bin.bat 8、进入Dev Tools界面 9、执行GET _cluster/health

四、快速入门案例实战之电商网站集群健康检查、文档CRUD

1、document数据格式

面向文档的搜索分析引擎

(1)应用系统的数据结构都是面向对象的,复杂的 (2)对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦 (3)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能 (4)es的document用json数据格式来表达

public class Employee {

private String email; private String firstName; private String lastName; private EmployeeInfo info; private Date joinDate;

}

private class EmployeeInfo {

private String bio; // 性格 private Integer age; private String[] interests; // 兴趣爱好

}

EmployeeInfo info = new EmployeeInfo(); info.setBio("curious and modest"); info.setAge(30); info.setInterests(new String[]{"bike", "climb"});

Employee employee = new Employee(); employee.setEmail("zhangsan@sina.com"); employee.setFirstName("san"); employee.setLastName("zhang"); employee.setInfo(info); employee.setJoinDate(new Date());

employee对象:里面包含了Employee类自己的属性,还有一个EmployeeInfo对象

两张表:employee表,employee_info表,将employee对象的数据重新拆开来,变成Employee数据和EmployeeInfo数据 employee表:email,first_name,last_name,join_date,4个字段 employee_info表:bio,age,interests,3个字段;此外还有一个外键字段,比如employee_id,关联着employee表

{ "email": "zhangsan@sina.com", "first_name": "san", "last_name": "zhang", "info": { "bio": "curious and modest", "age": 30, "interests": [ "bike", "climb" ] }, "join_date": "2017/01/01" }

我们就明白了es的document数据格式和数据库的关系型数据格式的区别

2、电商网站商品管理案例背景介绍

有一个电商网站,需要为其基于ES构建一个后台系统,提供以下功能:

(1)对商品信息进行CRUD(增删改查)操作 (2)执行简单的结构化查询 (3)可以执行简单的全文检索,以及复杂的phrase(短语)检索 (4)对于全文检索的结果,可以进行高亮显示 (5)对数据进行简单的聚合分析

3、简单的集群管理

(1)快速检查集群的健康状况

es提供了一套api,叫做cat api,可以查看es中各种各样的数据

GET /_cat/health?v

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent 1488006741 15:12:21 elasticsearch yellow 1 1 1 1 0 0 1 0 - 50.0%

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent 1488007113 15:18:33 elasticsearch green 2 2 2 1 0 0 0 0 - 100.0%

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent 1488007216 15:20:16 elasticsearch yellow 1 1 1 1 0 0 1 0 - 50.0%

如何快速了解集群的健康状况?green、yellow、red?

green:每个索引的primary shard和replica shard都是active状态的 yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态 red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了

为什么现在会处于一个yellow状态?

我们现在就一个笔记本电脑,就启动了一个es进程,相当于就只有一个node。现在es中有一个index,就是kibana自己内置建立的index。由于默认的配置是给每个index分配5个primary shard和5个replica shard,而且primary shard和replica shard不能在同一台机器上(为了容错)。现在kibana自己建立的index是1个primary shard和1个replica shard。当前就一个node,所以只有1个primary shard被分配了和启动了,但是一个replica shard没有第二台机器去启动。

做一个小实验:此时只要启动第二个es进程,就会在es集群中有2个node,然后那1个replica shard就会自动分配过去,然后cluster status就会变成green状态。

(2)快速查看集群中有哪些索引

GET /_cat/indices?v

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open .geoip_databases RH_elHLcR-CAPrs61JpkOg 1 0 44 5 41.4mb 41.4mb

  1. 简单的索引操作

创建索引: PUT /test_index?pretty

删除索引: DELETE /test_index?pretty

4、 商品的CRUD操作

1、新增商品: 新增文档,建立索引

PUT /index/type/id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PUT /ecommerce/product/1
{
"name":"gaolujie yagao",
"desc":"gaoxiao meibai",
"price": 30,
"producer": "gaolujie producer",
"tags":["meibai", "fangzhu"]
}



PUT /ecommerce/product/2
{
"name":"jiajieshi yagao",
"desc":"youxiao fangzhu",
"price": 25,
"producer": "jiajieshi producer",
"tags":["fangzhu"]
}



PUT /ecommerce/product/3
{
"name":"zhonghua yagao",
"desc":"caoben zhiwu",
"price": 40,
"producer": "zhonghua producer",
"tags":["qingxin"]
}

es会自动的建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索到

​ 2、查询商品:检索文档

1
GET /ecommerce/product/1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结果:

{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "gaolujie yagao",
"desc" : "gaoxiao meibai",
"price" : 30,
"producer" : "gaolujie producer",
"tags" : [
"meibai",
"fangzhu"
]
}
}

​ 3、修改商品:替换文档(必须带上所有的field信息才可以进行修改)注意这里是put请求

1
2
3
4
5
6
7
8
9
PUT /ecommerce/product/1

{
"name":"jiaqiangban gaolujie yagao",
"desc": "gaoxiao meibai",
"price":30,
"producer":"gaolujie producer",
"tags":["meibai","fangzhu","qingxin"]
}

返回的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1
}

​ 4、修改商品:更新文档(局部数据)。修改商品 这里是post请求

1
2
3
4
5
6
POST /ecommerce/product/1/_update
{
"doc":{
"name":"jiaqiangban gaolujie yagao gengxin"
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "1",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 1
}

​ 5、删除商品

1
DELETE /ecommerce/product/1?pretty

五、快速入门案例实战之电商网站商品管理:多种搜索方式

搜索全部商品: GET /ecommerce/product/_search

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
"took" : 3, took耗费了几毫秒
"timed_out" : false, 是否超时
"_shards" : { 数据拆分成了1个分片,所以对于搜索请求,会发送到所有的primary shard(或者它的某个relica shard中也可以)
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3, 查询结果的数量,3个document
"relation" : "eq"
},
"max_score" : 1.0, score的含义,就是document对于一个search的相关度的匹配分数,越相关,就越匹配,分数也越高
"hits" : [ 包含了匹配搜索结果document的详细数据
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "jiajieshi yagao",
"desc" : "youxiao fangzhu",
"price" : 25,
"producer" : "jiajieshi producer",
"tags" : [
"fangzhu"
]
}
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"name" : "zhonghua yagao",
"desc" : "caoben zhiwu",
"price" : 40,
"producer" : "zhonghua producer",
"tags" : [
"qingxin"
]
}
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "gaolujie yagao",
"desc" : "gaoxiao meibai",
"price" : 30,
"producer" : "gaolujie producer",
"tags" : [
"meibai",
"fangzhu"
]
}
}
]
}
}

搜索商品名称中包含yagao的商品: GET /ecommerce/product/_search?q=name:yagao&sort=price:desc

适用于临时的在命令行使用的一些工具,比如crul,快速的发出请求,来检索想要的答案;但是如果查询请求比较复杂,是很难通过去构建在生产环境中的,几乎很少使用query srting search


2、query DSL

DSL: Domain Specified Language,特定领域的语言

http request body: 请求体,可以用json格式来构建查询语法,比较方便,可以构建各种复杂的语法 比query string search强大多

1
2
3
4
5
6
GET /ecommerce/product/_search
{
"query":{
"match_all":{}
}
}

查询名称包含yagao的商品,同时按照价格降序排序:

1
2
3
4
5
6
7
8
9
10
11
GET /ecommerce/product/_search
{
"query":{
"match":{
"name": "yagao"
}
},
"sort":[
{"price":"desc"}
]
}

分页查询商品

1
2
3
4
5
6
7
8
GET /ecommerce/product/_search
{
"query":{
"match_all":{}
},
"from":1, 从哪一个开始
"size":1 查询几个
}

指定要查询的结果的筛选字段使用_source

1
2
3
4
5
6
7
GET /ecommerce/product/_search
{
"query":{
"match_all":{}
},
"_source":["name","price"]
}

3、query filter

搜索名称中包含牙膏,而且售价大于25的商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /ecommerce/product/_search
{
"query":{
"bool":{
"must":{
"match":{
"name":"yagao"
}
},
"filter": {
"range": {
"price": {
"gt": 25
}
}
}
}
}
}

4、full-text search全文检索

1
2
3
4
5
6
7
8
9
get /ecommerce/product/_search
{
"query":{
"match":{
"producer": "yagao producer"
}
}

}

producer这个字段,会先被拆解,建立倒排索引

special 4

yagao 4

producer 1,2,3,4

gaolujie 1

zhonghua 3

jiajieshi 2

5、phrase search(短语搜索)

跟全文检索相对应,相反,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回。

pyrase search ,要求输入的搜索串,必须在指定的字段中完全包含一模一样的,才可以算作匹配,才能结果返回

1
2
3
4
5
6
7
8
9
get /ecommerce/product/_search
{
"query":{
"match_phrase":{
"producer": "yagao producer"
}
}

}

6、highlight search(高亮搜索)

1
2
3
4
5
6
7
8
9
10
11
12
13
get /ecommerce/product/_search
{
"query":{
"match":{
"producer": "producer"
}
},
"highlight":{
"fields":{
"producer": {}
}
}
}

六、快速入门案例实战之电商网站商品管理:聚合嵌套、下钻分析、聚合分析

第一个分析需求:计算每个tag下的商品数量

首先需要将文本field的fielddata属性设置为true,

ElasticSearch创建索引报错 Types cannot be provided in put mapping requests, unless the include_type_name parameter is set to true

原因是elasticsearch7.x版本不支持type(低版本写法)所致,所以在高版本使用type,需要传入include_type_name参数,值为true。

1
2
3
4
5
6
7
8
9
PUT /ecommerce/_mapping/product?include_type_name=true
{
"properties":{
"tags":{
"type":"text",
"fielddata":true
}
}
}

返回结果:

1
2
3
4
{
"acknowledged" : true
}

然后再进行聚合聚合分析

1
2
3
4
5
6
7
8
9
10
GET /ecommerce/product/_search
{
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags"
}
}
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{
"took" : 39,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"name" : "jiajieshi yagao",
"desc" : "youxiao fangzhu",
"price" : 25,
"producer" : "jiajieshi producer",
"tags" : [
"fangzhu"
]
}
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"name" : "zhonghua yagao",
"desc" : "caoben zhiwu",
"price" : 40,
"producer" : "zhonghua producer",
"tags" : [
"qingxin"
]
}
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "special yagao",
"desc" : "special meibai",
"price" : 50,
"producer" : "special yagao producer",
"tags" : [
"meibai",
"gaoxiao"
]
}
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "gaolujie yagao",
"desc" : "gaoxiao meibai",
"price" : 30,
"producer" : "gaolujie producer",
"tags" : [
"meibai",
"fangzhu"
]
}
}
]
},
"aggregations" : {
"group_by_tags" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "fangzhu",
"doc_count" : 2
},
{
"key" : "meibai",
"doc_count" : 2
},
{
"key" : "gaoxiao",
"doc_count" : 1
},
{
"key" : "qingxin",
"doc_count" : 1
}
]
}
}
}

但是此时把很多原始的数据给查询出来了,结果只是我们想要的aggregations这个列表中

我们可以添加一个 size:0来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
GET /ecommerce/product/_search
{
"size":0,
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags"
}
}
}
}

这样结果就只有聚合分析的结果了。


第二个聚合分析的需求:对名称中包含yagao的商品,计算每个tag下的商品数量

就是在聚合结果的基础上添加一个搜索。就是按照tags进行分组然后在此基础上筛选name为gaolujie的结果,其中aggs下一级:group_by_tags可以自己定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /ecommerce/product/_search
{
"query":{
"match":{
"name":"gaolujie"
}
},
"size":0,
"aggs":{
"group_by_tags":{ #可以自定义起名字
"terms":{
"field":"tags"
}
}
}
}

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

通过嵌套聚合这类语法来进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /ecommerce/product/_search
{
"size":0,
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags"
},
"aggs":{ #在聚合的基础上再进行聚合,这里聚合了平均值
"avg_price":{
"avg": {
"field": "price"
}
}
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_by_tags" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "fangzhu",
"doc_count" : 2,
"avg_price" : {
"value" : 27.5
}
},
{
"key" : "meibai",
"doc_count" : 2,
"avg_price" : {
"value" : 40.0
}
},
{
"key" : "gaoxiao",
"doc_count" : 1,
"avg_price" : {
"value" : 50.0
}
},
{
"key" : "qingxin",
"doc_count" : 1,
"avg_price" : {
"value" : 40.0
}
}
]
}
}
}

需求:聚合类后再对价格进行聚合分析,然后再对聚合分析后的平均价格进行order排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /ecommerce/product/_search
{
"size":0,
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags",
"order": {
"avg_price": "desc"
}
},
"aggs":{
"avg_price":{
"avg": {
"field": "price"
}
}
}
}
}
}

以上都是使用es的restful api方式进行学习知识点和使用,并没有使用一些编程语言(java),原因如下:restful api才是最基础的

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
GET /ecommerce/product/_search
{
"size":0,
"aggs":{
"group_by_range":{
"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"
}
}
}
}
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{
"took" : 12,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_by_range" : {
"buckets" : [
{
"key" : "0.0-20.0",
"from" : 0.0,
"to" : 20.0,
"doc_count" : 0,
"group_by_tags" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [ ]
}
},
{
"key" : "20.0-40.0",
"from" : 20.0,
"to" : 40.0,
"doc_count" : 2,
"group_by_tags" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "fangzhu",
"doc_count" : 2,
"average_price" : {
"value" : 27.5
}
},
{
"key" : "meibai",
"doc_count" : 1,
"average_price" : {
"value" : 30.0
}
}
]
}
},
{
"key" : "40.0-50.0",
"from" : 40.0,
"to" : 50.0,
"doc_count" : 1,
"group_by_tags" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "qingxin",
"doc_count" : 1,
"average_price" : {
"value" : 40.0
}
}
]
}
}
]
}
}
}

七、手工画图剖析es的基础分布式架构

1、elasticsearch对复杂分布式机制的透明隐藏特性

es是一套分布式的系统,分布式为了应对大数据量。

隐藏了复杂的分布式机制:

​ 分片机制(之前随随便便就把document插入到es集群中去了,并没有关注数据怎么进行分片的,数据到了哪个shard中去

​ cluster discovery:集群发现机制,当直接启动第二个进程作为node2,这时候就自动发现了集群,并且加入了进去还接受了部分数据,replica shard

​ shard负载均衡:假设现在有3个节点,总共有25个shard要分配到3个节点上面去,es会自动进行均匀分配,以保持每个节点的均衡的读写负载请求

​ shard副本:

​ 请求路由:

​ 集群扩容:

​ shard重分配:

2、elasticsearch的垂直扩容与水平扩容

垂直扩容:采购更强大服务器,成本非常昂贵,有瓶颈

水平扩容:业内常常采用的方案,采购越来越多普通的服务器,性能比较一般但是很多普通服务器组合在一起就能构成强大的计算和存储能力。

3、增加或减少节点时的数据rebalance

增加或减少节点时的重新负载均衡

4、master节点

管理es集群的元数据:

​ 比如说索引的创建和删除、维护索引元数据;节点的增加和移除,维护集群的元数据,master节点实质上是做了一些轻量级的工作。

​ 默认情况下,会自动选择出一台节点,作为master节点。

5、节点平等的分布式架构

(1)节点对等,每个节点都能接收所有的请求。

(2)自动请求路由

​ 当通过对等节点进行请求时,会通过这个对等节点自动的请求路由到合适的shard上去进行搜索。

(3)响应收集

八、shard&replica机制再次梳理以及单node节点中创建index图解

1、shard&replica机制再次梳理

  • index包含多个shard
  • 每个shard都是一个最小工作单元,承载部分数据,是一个lucene实例,完整的建立索引和处理请求的能力。
  • 增减节点时候,shard会自动在nodes中进行负载均衡。
  • primary shard和replica shard,每个document肯定只存在于某一个primary shard以及对应的replica shard中,不可能存在于多个primary shard中。
  • replica shard是primary shard的副本,负责容错,以及承担读请求负载。
  • primary shard数量在创建索引时就固定了,replica shard的数量可以随时修改。
  • primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard。
  • primary shard不能和自己的replica shard放在同一个节点上,否则节点宕机,primary shard和副本都会丢失,起不到容错的作用,但是可以和其他primary shard的replica shard放在同一个节点上。

2、图解单node环境下创建index是什么样子

  • 单node环境下,创建一个index,有3个primary shard,3个replica shard。

  • 集群statsus是yellow的。

  • 这时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的。

  • 集群可以正常进行工作,但是一旦出现了节点宕机,那么数据就会全部丢失,而且集群不可用,无法承接任何的需求。

    1
    2
    3
    4
    5
    6
    7
    PUT /test_index
    {
    "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
    }
    }

    九、图解2个node环境下replica shard是如何分配的

  • ​ replica shard分配: 3个primary shard, 3个replica shard,1个node节点

  • primary shard 与 replica shard 同步 可以查询同样的功能操作

  • 读请求的话 一个java程序都可以对primry和replica进行读取操作。(关于存储不清楚是不是可以)

九、分布式原理:图解横向扩容过程,如何超出扩容极限,以及如何提升容错性

6个shard被平均分配到3个node上 对于每一份数据都会存储在不同的node上,如p0与r0 ,如果出现某一台节点宕机,并不会影响。尽量保证每个shard上面有相同的节点数量。

  • Primary&replica自动负载均衡,6个shard,3个primary,3个replica。

  • 每个node有更少的shard,IO/CPU/Memory资源给每个shard分配更多,每个shard性能更好。

  • 扩容的极限,6个shard(3primary,3replica),最多扩容到6台机器,每个shard可以占用单台服务器的所有资源,性能最好。

  • 超出扩容极限,可以动态的修改replica数量,9个shard(3个primary,6个replica),最多可以扩容到9台机器,比3台机器时,拥有3倍的读吞吐量。

  • 3台机器下,9个shard(3个primary,6个replica),资源更少,但是容错性更好,最多容纳两台的机器宕机,6个shard只能容纳1台机器宕机。

  • 这里的这些知识点,综合起来,就是说,一方面告诉你扩容的原理,怎么扩容,怎么提升系统整体的吞吐量;另一方面要考虑到系统的容错性,怎么保证提高容错性,让尽可能多的服务器宕机,保证数据尽可能的不丢失。

    image-20220331172132565

十、图解elastic的容错机制:master选举,replica容错, 数据恢复

(1) 9shard,3 node

(2)master node宕机,自动master选举,red状态

(3)replica容错:新master将replica提升为primary shard, yellow状态

(4)重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green状态

image-20220331173815548

master node宕机的一瞬间,p0这个primary shard就没了,此时

容错第一步:master选举,自动选举另外一个node成为新的master,承担起master的责任来。

容错第二步:新master,将丢失掉的primary shard的某个replica shard提升为primary shard,此时cluster status会变为yellow。

容错第三步:重启故障的node,new master,会将缺失的副本都copy一份到该node上去,而且该node会使用之前已有的shard数据,只是同步一下宕机之后发生过的修改。

image-20220331173514618

十、初步解析document核心元数据以及图解剖析index创建范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
}

1、_index元数据

image-20220401103322149

类似的数据放在一个index,这批数据的功能和支持的需求,可能是很类似的,相同功能的数据放在一个index,在自己独立的shard中,与其他的数据不在一个shard中,就不会相互影响。

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

2、_type元数据

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

3、_id元数据

  • 代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
  • 可以手动指定document的id(put /index/type/id),也可以不指定,es自动给为我们创建一个id。

十一、document中id的手动指定和自动生成两种策略解析

  • 手动指定document id

    • 应当根据应用的情况来说,是否满足手动指定document id的前提:

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

    • ​ put /index/type/id
  • 自动生成document id

1
2
3
4
post /test_index/test_type
{
"test_content":"my test noid"
}

这里使用的是post 并非手动指定id的put

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "dvw0438BQ1kM2qC8Hjfc", #这里是自动生成的id
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
  • 自动生成的id,长度为20个字符,url安全,bse64编码,GUID方式进行生成的,保证分布式系统生成的时候并不会产生冲突。
image-20220401115233015

十二、document的source元数据以及定制返回结果解析

  • _source元数据
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "1",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"test_field1" : "test field1",
"test_field2" : "test field2"
}
}

在创建一个document的时候,使用的那个放在request body中的json串,默认情况下,在get的时候,会原封不动的返回回来。

  • 定制返回结果
1
GET /test_index/test_type/1?_source=test_field2

返回多个field 使用逗号隔开,

1
GET /test_index/test_type/1?_source=test_field1,test_field2

当其不存在时候的发送put是创建,当存在的时候再发送就是全量替换了

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

1、document的全量替换

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

2、document的强制创建

  • 创建文档与全量替换的语法是一样的,有时我们只是想创建文档,而不想替换文档,如果强制进行创建呢?

  • ``` PUT /test_index/test_type/1?_create

    1

    PUT /test_index/test_type/1?op_type=create
    1
    2
    3
    4
    5

    > **这里在7.16版本中不行,未做解决。**

    ## 3、document的删除

    PUT /test_index/test_type/1?op_type=create
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    并不会物理删除,而是标记为deleted ,只有当数据越来越多的时候,在后台自动删除

    # 十四、深度图解剖析elasticsearch并发冲突问题

    > 举例子:电商场景下,假设我们有个程序,工作流程是如下:
    >
    > 1、读取商品信息(包含了商品库存)
    >
    > 2、用户下单购买
    >
    > 3、更新商品信息(主要讲库存减1)

    比如我们的程序是多线程,所以可以能有多个线程并发的去执行上述的3步操作。

    又一个牙膏,库存100件,现在同时有两个人都过来读取了牙膏的数据,然后下单购买了该管牙膏,此时两个线程并发的服务于两个人,同时再进行商品库存数据的修改/

    ![image-20220402135058407](https://image.baidu.com/search/down?url=https://image.baidu.com/search/down?url=https://tva1.sinaimg.cn/large/e6c9d24ely1h0vbyltpn0j21wa0rcwlp.jpg)

    # 十五、深度剖析乐观锁与悲观锁两种并发控制方案

    悲观锁并发控制方案,就是在各种情况下,都上锁。都上锁之后就只有一个线程可以操作这一条数据了,当然不同的场景下,上的锁不同,有行级锁、表级锁、读锁、写锁。

    ![image-20220402155415715](https://image.baidu.com/search/down?url=https://image.baidu.com/search/down?url=https://tva1.sinaimg.cn/large/e6c9d24ely1h0vfiw2ocfj21zw0r2n55.jpg)

    1、悲观锁的优点:方便,直接加锁,对应用程序来说,透明不需要做额外的操作;缺点,并发能力很弱,同一时间只能有一条线程操作数据。

    2、乐观锁的优点:并发能力很高,不给数据加锁,大量线程并发操作;缺点:麻烦,每次更新的时候都要先对比版本号,然后可能需要重新

    # 十六、图解elasticsearch内部如何基于_version进行乐观锁并发控制

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

    在删除一个document之后,从侧面验证他不是立即物理删除掉的,因为她的一些版本号信息还是保留着的,先删除一个document,然后再创建这个条document,其实会在delete version的基础上,再把version+1

    ![image-20220402173716684](https://image.baidu.com/search/down?url=https://image.baidu.com/search/down?url=https://tva1.sinaimg.cn/large/e6c9d24ely1h0vii2divoj21sm0pe0xi.jpg)

    # 十七、上机器动手实战演练基于_vesion进行乐观锁并发控制

    - 先构造一条数据出来

    ```json
    PUT /test_index/test_type/7
    {
    "test_field":"test test"
    }

  • 模拟两个客户端,都获取到了同一条数据

1
GET test_index/test_type/7
1
2
3
4
5
6
7
8
9
10
11
12
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "7",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 2,
"found" : true,
"_source" : {
"test_field" : "test test"
}
}
  • 其中一个客户端先更新了这个数据,同时带上了数据的版本号,确保地说es中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改
1
2
3
4
PUT /test_index/test_type/7?version=1
{
"test_field":"test client 1"
}

但是这样在旧版本中可行,在es新版本中注意:一些老的版本es使用version,但是新版本不支持了,会报这个错误,提示我们用if_seq_no和if_primary_term。

版本元数据 **_seq_no:文档版本号,作用同_version(相当于学生编号,每个班级的班主任为学生分配编号,效率要比学校教务处分配来的更加高效,管理起来更方便) _primary_term:文档所在位置(相当于班级)**

如下修改:

1
2
3
4
PUT /test_index/test_type/7?if_seq_no=0&if_primary_term=2
{
"test_field":"test client1"
}

返回的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "7",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 2
}
  • 另外一个客户端,尝试基于version=1的数据去进行修改,同样带上version版本号,进行乐观锁的并发控制
1
2
3
4
put test_index/test_type/7?if_seq_no=0&if_primary_term=2
{
"test_field":"test client 3 "
}

这时候报错就如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[7]: version conflict, required seqNo [0], primary term [2]. current document has seqNo [2] and primary term [2]",
"index_uuid" : "ps1w8fp5RmO4wdaUqLuc1A",
"shard" : "0",
"index" : "test_index"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[7]: version conflict, required seqNo [0], primary term [2]. current document has seqNo [2] and primary term [2]",
"index_uuid" : "ps1w8fp5RmO4wdaUqLuc1A",
"shard" : "0",
"index" : "test_index"
},
"status" : 409
}
  • 在乐观锁成功阻止并发问题之后,尝试正确的完成更新(控制版本号正确无误)
1
2
3
4
put test_index/test_type/7?if_seq_no=2&if_primary_term=2
{
"test_field":"test client 3 "
}

十八、上机动手实战演练基于external version进行乐观锁并发控制

external version

es提供了一个feature,就是说可以不用它提供的内部verison版本号进行并发控制。举个例子,假如数据在mysq里有一份,然后在应用系统本身就维护了一个版本号,无论是什么自己生成的,还是程序控制的。这个时候,进行乐观锁并发控制的时候,可能并不是想要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

1
2
3
4
5
#先构造一条数据
put test_index/test_type/8
{
"test_field":"test test"
}
1
2
3
4
5
#使用version_type=external
put test_index/test_type/8?version=2&version_type=external
{
"test_field":"test client 2"
}

返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "8",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 2
}

十九、图解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数据发送过去。

image-20220406165443906

2、代码演练

1
2
3
4
5
6
POST /test_index/test_type/10/_update
{
"doc":{
"test_filed2":"test2 copy"
}
}

上面的代码中是partial update 请求是post,后面添加了_update

二十、上机动手实战演练基于groovy脚本进行partial update

1、创建数据

1
2
3
4
5
PUT /test_index/test_type/11
{
"num":0,
"tags":[]
}

2、内置脚本

1
2
3
4
post /test_index/test_type/11/_update
{
"script":"ctx._source.num+=1"
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 3,
"_seq_no" : 7,
"_primary_term" : 2,
"found" : true,
"_source" : {
"test_field" : "test groovy",
"num" : 1,
"tags" : [ ]
}
}

3、外部脚本

1
2
3
4
5
6
7
8
9
10
11
POST /test_index/test_type/11/_update
{
"script":{
"lang": "groovy",
"file":"test-add-tags",
"params": {
"new_tag":"tag1"
}
}
}
#这样在es7版本中是错误的

elasticsearch7.3报错[script] unknown field [file], parser not found

原因:高版本的elasticsearch不支持file类型,也就是说不支持从外部文件读取脚本

1.需要先将脚本post到es的库中

1
2
3
4
5
6
7
8
9
10
11
12
13
POST _scripts/new_tags
{
"script":{
"lang":"painless",
"source": "ctx._source.tags.add(params.new_tag)"
}
}
#返回结果:
{
"acknowledged" : true
}


现document有一个字段tags是一个数组['a','b']。

现在要往这个tags里新增一个tag,脚本里写的是ctx._source.tags+=params.new_tag。实际上这个写法在5.x里是没有问题的,而高版本里会报错Cannot cast java.lang.String to java.util.ArrayList

正确的写法应该是ctx._source.tags.add(params.new_tag)

2.根据id来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
POST test_index/product/11/_update
{
"script":{
"id":"new_tags",
"params": {
"new_tag": "tag1"
}
}
}
#返回结果
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 4,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 8,
"_primary_term" : 2
}
#查看document,多了tags中的tag1:
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 4,
"_seq_no" : 8,
"_primary_term" : 2,
"found" : true,
"_source" : {
"test_field" : "test groovy",
"num" : 1,
"tags" : [
"tag1"
]
}
}

如果不是arraylist的话 就可以是下面这种定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST _scripts/new_price
{
"script":{
"lang":"painless",
"source": "ctx._source.price+=params.new_price"
}
}
#这样就可以直接定义一个
{
"script":{
"id":"new_price",
"params": {
"new_price": 22
}
}
}

4、用脚本来删除文档

1、将脚本post到es的库中

1
2
3
4
5
6
7
8
POST _scripts/delete
{
"script":{
"lang":"painless",
#错误"source": "ctx.op =ctx._source.num==count?'delete':'none'"
"source": "ctx.op =ctx._source.num==params.count?'delete':'none'"
}
}

原因是语法改变了,需要用新的语法写,或者是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
POST /test_index/test_type/11/_update
{

"script": {
"source" : "ctx.op = ctx._source.num == params.count ? 'delete' : 'none'",
"params" : {
"count": 0
}
}
}
#当脚本参数值正确的时候,结果如下:
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 2,
"result" : "deleted", #标记为deleted
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 11,
"_primary_term" : 2
}
#当脚本对不上的时候 结果如下:
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 1,
"result" : "noop", #标记为no 操作
"_shards" : {
"total" : 0,
"successful" : 0,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 2
}

2、根据id来调用

1
2
3
4
5
6
7
8
9
POST /test_index/test_type/11/_update
{
"script":{
"id":"delete",
"params": {
"count":0
}
}
}

5、upsert操作

当document不存在的时候 如果执行post update操作

1
2
3
4
5
6
POST /test_index/test_type/11/_update
{
"doc":{
"num":1
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"error" : {
"root_cause" : [
{
"type" : "document_missing_exception",
"reason" : "[test_type][11]: document missing",
"index_uuid" : "ps1w8fp5RmO4wdaUqLuc1A",
"shard" : "2",
"index" : "test_index"
}
],
"type" : "document_missing_exception",
"reason" : "[test_type][11]: document missing",
"index_uuid" : "ps1w8fp5RmO4wdaUqLuc1A",
"shard" : "2",
"index" : "test_index"
},
"status" : 404

执行:

1
2
3
4
5
6
7
8
POST /test_index/test_type/11/_update
{
"script":"ctx._source.num+=1",
"upsert":{
"num":0,
"tags":[]
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 1,
"_seq_no" : 12,
"_primary_term" : 2,
"found" : true,
"_source" : {
"num" : 0,
"tags" : [ ]
}
}

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

再次执行上面的post 请求:

1
2
3
4
5
6
7
8
POST /test_index/test_type/11/_update
{
"script":"ctx._source.num+=1",
"upsert":{
"num":0,
"tags":[]
}
}

结果中num就变成了1,因为已经有document存在了

二十一、图解partial update内置乐观锁并发控制原理及相关操作

image-20220407005044569

二十二、上机动手实战演练之mget批量查询api

1、批量查询的好处:

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

2、mget的语法

一条一条的查询

GET /test_index/test_type/1

GET /test_index/test_type/2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET _mget
{
"docs":[
{
"_index":"test_index",
"_type":"test_type",
"_id":11
},
{
"_index":"test_index",
"_type":"test_type",
"_id":2
}
]
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"docs" : [
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "11",
"_version" : 2,
"_seq_no" : 13,
"_primary_term" : 2,
"found" : true,
"_source" : {
"num" : 1,
"tags" : [ ]
}
},
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : "2",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"test_content" : "my test"
}
}
]

3、如果查询的是一个_index下不同_type的话可以使用以下的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /test_index/_mget
{
"docs":[
{
"_type":"test_type",
"_id":11
},
{
"_type":"test_type",
"_id":2
}
]
}

4、如果查询的是一个_index下相同_type的可以使用以下的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /test_index/test_type/_mget
{
"ids":[11,2]
}
#或者
GET /test_index/test_type/_mget
{
"docs":[
{
"_id":11
},
{
"_id":2
}
]
}

5、mget的重要性

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