admin 管理员组

文章数量: 1087818

ElasticSearch入门实操

中台最近在优化选品业务,希望能给客户提供搜索商品的功能,自然想到了可以快速存储和搜索海量数据的全文搜索引擎—ElasticSearch.

目录

1.简介

2.安装(Win10环境,超级简单,开箱即用)

3.基本概念

4.入门

5.Spring Boot整合ES

6.《深入理解Elasticsearch》读书笔记


1.简介

ElasticSearch是Elastic技术栈中一套分布式、可扩展、实时的搜索与数据分析引擎。Elasticsearch不仅仅只是全文搜索,同时也提供结构化搜索、数据分析、复杂的人类语言处理、地理位置和对象间关联关系等。

ElasticSearch的底层是开源库 Lucene。Apache Lucene是时下最先进、高性能、全功能的搜索引擎库,但其原理和实现相对复杂。ES(后文将以ES代替ElasticSearch)基于Lucene,使用Java语言开发,对Lucene做了一层封装,提供一套简单一致的Restfull风格API,使得全文检索更简单。

2.安装(Win10环境,超级简单,开箱即用)

2.1 ES安装

安装指引:.html

简单3步:

  1. 配置JDK环境(JDK8及以上版本)。
  2. ES压缩包解压。

解压后目录如上图

bin  启动文件

config    配置文件

    log4j2    日志配置

    jvm.options  JVM相关配置,ES是通过Java语言实现,里面可以配置JVM相关参数

    elasticsearch.yml       ES的配置文件,默认端口:9200

lib   相关jar包

modules       功能模块

plugins        插件,IK分词器就会放到这来

3. 启动,双击bin目录下elasticsearch.bat文件

在浏览器输入localhost:9200,出现以上界面表示安装启动成功。

2.2安装Elasticsearch-Head

ElasticSearch-head是一个ElasticSearch的集群管理工具,它是完全由html5编写的独立网页程序,下载地址:

       1)nodejs环境配置

       2)下载解压

       3)启动

通过命令:npm run start,输出以上信息,表示启动成功。

此时在浏览器端输入localhost:9100,控制台却出现错误信息,这是因为跨域问题引起的,需要在ES配置文件elasticsearch.yml添加如下配置:

http.cors.enabled: true

http.cors.allow-origin: "*"

重启ES即可得到以上界面,安装成功!可以在该页面进行节点,索引等管理。一般把它当成数据展示工具,查询我们可以用Kibana工具。

2.3 Kibana安装

Kibana 是为ES设计的开源分析和可视化平台,其主要功能是搜索,查看存储在ES索引中的数据并与之交互,很容易地实现高级的数据分析和可视化,以图表的形式展现出来。官网:,下载对应ES的版本

  1. 同样需要nodejs环境
  2. 解压(时间较长)
  3. 启动,双击bin目录中kibana.bat文件
  4. 访问测试,localhost:5601,出现如下界面表示安装成功。

我们可以在Dev Tools界面进行数据的增删改查工作。

5. 汉化,修改kibana配置即可,zh-CN.

3.基本概念

3.1 Lucene

Lucene是一种高性能、可伸缩的信息搜索(IR)库,在2000年开源,最初由鼎鼎大名的Doug Cutting开发,是基于Java实现的高性能的开源项目。

Lucene 中的主要模块说明如下:

1)analysis:主要负责词法分析及语言处理,也就是我们常说的分词,通过该模块可最终形成存储或者搜索的最小单元Term。

2)index 模块:主要负责索引的创建工作。

3)store 模块:主要负责索引的读写,主要是对文件的一些操作,其主要目的是抽象出和平台文件系统无关的存储。

4)queryParser 模块:主要负责语法分析,把我们的查询语句生成 Lucene 底层可以识别的条件。

5)search 模块:主要负责对索引的搜索工作。

6)similarity 模块:主要负责相关性打分和排序的实现。

Lucene 中的核心术语

Term是索引里最小的存储和查询单元,对于英文来说一般是指一个单词,对于中文来说一般是指一个分词后的词。

FST(finite-state transducer):倒排索引的一个二级缓存索引树,在生成倒排索引之后,根据分词表,将原先的分词表划分为多个block,每个block包含25-48个词,将每个block里的词的公共前缀取出来作为作为1个节点,其叶子节点对应是block的首地址,形成一个前缀树,放在堆内存中,永不回收,这就是FST。

词典(Term Dictionary,也叫作字典):是Term的集合。词典的数据结构可以有很多种,每种都有自己的优缺点。

比如:排序数组通过二分查找来检索数据:HashMap(哈希表)比排序数组的检索速度更快,但是会浪费存储空间。FST有更高的数据压缩率和查询效率,因为词典是常驻内存的,而 FST 有很好的压缩率,所以 FST 在Lucene的最新版本中有非常多的使用场景,也是默认的词典数据结构。

倒排序(Posting List):一篇文章通常由多个词组成,倒排表记录的是某个词在哪些文章中出现过。

正向信息:原始的文档信息,可以用来做排序、聚合、展示等。

段(Segment:索引中最小的独立存储单元。一个索引文件由一个或者多个段组成。在Luence中的段有不变性,也就是说段一旦生成,在其上只能有读操作,不能有写操作。

分片处理:将一个索引分割成若干个更小的索引的过程,从而能在同一集群不同的节点上散布他们。

路由:提供信息给ES,ES根据这个信息来决定哪个分片来存储和执行查询。

3.2  ES

ES是基于Lucene的封装和增强,采取面向文档的方式存储数据,以下是ES数据存储与关系型数据库的对比。

Relational DB

ElasticSearch

数据库(database)

索引(indices)

表(tables)

Types

行(rows)

Documents

字段(columns)

Fields

1.字段的数据类型

字段的数据类型由字段的属性type指定,ElasticSearch支持的基础数据类型主要有:

1)字符串类型:string;

2)数值类型:字节(byte)、2字节(short)、4字节(integer)、8字节(long)、float、double;

3)布尔类型:boolean,值是true或false;

4)时间/日期类型:date,用于存储日期和时间;

5)二进制类型:binary;

6)IP地址类型:ip,以字符串形式存储IPv4地址;

7)特殊数据类型:token_count,用于存储索引的字数信息

2.字段的公共属性

1)index:该属性控制字段是否编入索引被搜索,该属性共有三个有效值:analyzed(该字段被分析,编入索引,产生的token能被搜索到)、no(不编入索引,无法搜索该字段)和not_analyzed(字段不会被分析,使用原始值编入索引,在索引中作为单个词);

2)store:指定是否将字段的原始值写入索引,默认值是no,字段值被分析,能够被搜索,但是,字段值不会存储,这意味着,该字段能够被查询,但是不会存储字段的原始值。

3)boost:字段级别的助推,默认值是1,定义了字段在文档中的重要性/权重;

4)include_in_all:该属性指定当前字段是否包括在_all字段中,默认值是ture,所有的字段都会包含_all字段中;如果index=no,那么属性include_in_all无效,这意味着当前字段无法包含在_all字段中。

5)copy_to:该属性指定一个字段名称,ES引擎将当前字段的值复制到该属性指定的字段中;

6)doc_values:文档值是存储在硬盘上的索引时(indexing time)数据结构,对于not_analyzed字段,默认值是true,analyzed string字段不支持文档值;

7)fielddata:字段数据是存储在内存中的查询时(querying time)数据结构,只支持analyzed string字段;

8)null_value:该属性指定一个值,当字段的值为NULL时,该字段使用null_value代替NULL值;在ES中,NULL 值不能被索引和搜索,当一个字段设置为NULL值,ES引擎认为该字段没有任何值,使用该属性为NULL字段设置一个指定的值,使该字段能够被索引和搜索。

3.字符串类型常用的其他属性

1)analyzer:该属性定义用于建立索引和搜索的分析器名称,默认值是全局定义的分析器名称,该属性可以引用在配置结点(settings)中自定义的分析器;

2)search_analyzer:该属性定义的分析器,用于处理发送到特定字段的查询字符串;

3)ignore_above:该属性指定一个整数值,当字符串字段(analyzed string field)的字节数量大于该数值之后,超过长度的部分字符数据将不能被analyzer处理,不能被编入索引;对于 not analyzed string字段,超过长度的部分字符将被忽略,不会被编入索引。默认值是0,禁用该属性;

4)position_increment_gap:该属性指定在相同词的位置上增加的gap,默认值是100;

5)index_options:索引选项控制添加到倒排索引(Inverted Index)的信息,这些信息用于搜索(Search)和高亮显示:

6)docs:只索引文档编号(Doc Number)

7)freqs:索引文档编号和词频率(term frequency)

8)positions:索引文档编号,词频率和词位置(序号)

9)offsets:索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号),默认情况下,被分析的字符串(analyzed string)字段使用positions,其他字段使用docs.

4.入门

官方文档:.html, 以下操作使用Kibana工具。

4.1基础命令

1)查看集权健康状态:GET /_cat/health?v

2)查看节点状态:GET /_cat/nodes?v

3)查看所有索引信息:GET /_cat/indices?v

以上信息都可以在ES-head界面中查看到。

4.2 索引的增删改操作

1)新建索引

PUT /索引名/~类型~/文档ID

{请求体}

2)创建索引规则

prod_name类型是text,而cate_name类型为keyword。其区别在于keyword存储数据时候,不会分词建立索引。text会自动分词,并生成索引。

3)查看文档类型

GET /product/_mapping

4)给索引插入数据

PUT /product/_doc/1

{

  "prod_name":"《费马大定律》",

  "cate_id":1001,

  "cate_name":"图书",

  "price":9.9

}

插入数据后课在ES-head中查看数据。

5)批量数据插入

POST /product/_doc/_bulk

{"index":{"_id":2}}

{"prod_name":"《数学大师》","cate_id":1001,"cate_name":"数学","price":28.9}

{"index":{"_id":3}}

{"prod_name":"《三体》","cate_id":1002,"cate_name":"文学","price":12.8}

{"index":{"_id":4}}

{"prod_name":"《高等数学》","cate_id":1003,"cate_name":"课本","price":10}6)

6)数据删除

DELETE /product/_doc/3

7)数据修改

POST /product/_doc/1/_update

{"doc":{"price": 8.8}}

8)删除索引

DELETE /test1

4.3 数据搜索

1)简单查询,搜索按价格倒排前3的商品,只显示需要的列

GET /product/_search

{

  "query": { "match_all": {} },

  "sort": { "price": { "order": "desc" } },

  "_source": ["prod_name","cate_name","price"],

  "from": 0,

  "size": 3

}

2)条件查询

GET /product/_search

{

  "query": {

    "match": {

      "prod_name": "费马大定律"

    }

  }

}

上面按商品名prod_name搜索费马大定律时,把数学大师也给查出来了,这是因为,prod_name字段类型会text,会做分词处理,两件商品中都含“大”这个字,所以被同时检索出来了,但《费马大定律》的得分远高于《数学大师》。当按品类名cate_name字段搜索文学时,因为cate_name为keyword类型,不做分词,所以检索到的只有文学,而没有数学。

3)组合搜索

使用bool来进行组合,must表示同时满足,must_not表示不同时满足,should表示满足其中任意一个,filter来过滤,比如获取price在12-15之间的商品。

4.4 分词器的使用

1)分词器的概念

I. Analysis 和 Analyzer

Analysis: 文本分析是把全文本转换一系列单词(term/token)的过程,也叫分词。Analysis是通过Analyzer来实现的。当一个文档被索引时,每个Field都可能会创建一个倒排索引(Mapping可以设置不索引该Field)。

倒排索引的过程就是将文档通过Analyzer分成一个一个的Term,每一个Term都指向包含这个Term的文档集合。

当查询query时,ES会根据搜索类型决定是否对query进行analyze,然后和倒排索引中的term进行相关性查询,匹配相应的文档。

II. Analyzer组成

分析器(analyzer)都由三种构件块组成的:character filters , tokenizers , token filters。

character filter字符过滤器: 一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签。

tokenizers 分词器:英文分词可以根据空格将单词分开,中文分词比较复杂,可以采用机器学习算法来分词。

Token filters Token过滤器:将切分的单词进行加工。大小写转换(例将“Quick”转为小写),去掉词(例如停用词像“a”、“and”、“the”等等),或者增加词(例如同义词像“jump”和“leap”)。

2)ES的内置分词器

    Standard Analyzer - 默认分词器,按词切分,小写处理

    Simple Analyzer - 按照非字母切分(符号被过滤), 小写处理

    Stop Analyzer - 小写处理,停用词过滤(the,a,is)

    Whitespace Analyzer - 按照空格切分,不转小写

    Keyword Analyzer - 不分词,直接将输入当作输出

    Patter Analyzer - 正则表达式,默认\W+(非字符分割)

    Language - 提供了30多种常见语言的分词器

Customer Analyzer 自定义分词器

3)IK分词器

IK分词器是比较推荐的中文分词器,下载地址:,下载对应ES版本,解压至ES目录下plugins文件夹中即可。IK分词器提供了两种分词算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度切分。

对《费马大定理》书籍名利用IK分词器的ik_smart算法做下分词处理,发现“业余数学家之王”费马的名字,竟然切分成了费和马两个字,没有把其当成一个词条,这是因为IK分词器无法识别“费马”这个词,可以采用建立自定义词库的方法让其识别。

a) 在IK分词器所在目录的config文件夹下新建chen.dic,里面添加“费马”这个词,注意保存为UTF-8编码格式。

b) 在IKAnalyzer.cfg.xml中配置扩展的词典。

重启ES,再看下分词效果,发现“费马”两个字已经被识别为一个Term

再创建一个新的索引books,并设置prod_name分词器为IK分词器,存储用最细粒度,搜索用最粗粒度。

批量添加数据,可通过命令GET /books/_doc/1/_termvectors?fields=prod_name,查看某条数据的分词效果。

4) term和match区别

term:直接精确查询,term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词,所以我们的搜索词必须是文档分词集合中的一个。拿“业余数学家之王费马”这个词条进行检索,通过term查询时,因为不会做分词,所以无法检索到包含“费马”二字的book.

match:会使用分词器解析,(先分析文档,然后通过分析的文档进行查询),match查询会先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配。分词出“费马”后再查询,就可以查询出《费马大定理》这部经典好书。

5.Spring Boot整合ES

官方文档:.6/index.html.

官方文档指出,TransportClient已被弃用,并将在ES 8.0中删除,取而代之的是Java高级REST客户端。所以本文将使用Java High Level REST Client.

5.1 Maven配置

把如下内容加入pom文件

<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.6.1</version>
</dependency>

SpringData子模块为各种数据访问提供统一编程接口,包括关系数据库(Mysql)、非关系数据库(Redis)或者类似Elasticsearch这样的分布式索引数据库。从而简化代码开发,提高开发效率。

<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-elasticsearch</artifactId><version>3.2.6.RELEASE</version><scope>compile</scope><exclusions><exclusion><groupId>jcl-over-sljf</groupId><artifactId>org.slf4j</artifactId></exclusion><exclusion><groupId>log4j</groupId><artifactId>org.apache.logging.log4j</artifactId></exclusion></exclusions>
</dependency>

SpringData模块中ES-clien版本可能较低,需指定其版本,保持与安装的ES版本一致。

<properties><java.version>1.8</java.version><elasticsearch.version>7.6.1</elasticsearch.version>
</properties>

5.2 RestHighLevelClient初始化

@Configuration
public class ElasticsearchClientConfig {@Beanpublic RestHighLevelClient restHighLevelClient() {RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("127.0.0.1", 9200, "http")));return client;}
}

5.3 Index API

1)索引创建

@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;//创建索引
@Test
public void testCreateIndex() throws IOException {//创建索引请求CreateIndexRequest request = new CreateIndexRequest("colin_index");//执行请求CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);System.out.println(response.toString());
}

2)获取索引

@Test
public void testExistIndex() throws IOException {GetIndexRequest request = new GetIndexRequest("colin_index");boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);System.out.println(exists);
}

3) 删除索引

@Test
public void testDeleteIndex() throws IOException {DeleteIndexRequest request = new DeleteIndexRequest("colin_index ");AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);System.out.println(delete.isAcknowledged());
}

4) 创建文档

对象类,为了保持与之前索引中字段一致,这里采用下划线的方式字段命名:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Product {private String prod_name;private int cate_id;private String cate_name;private double price;
}

向books索引添加文档

@Test
public void testAddDocument() throws IOException {//创建对象Product product = new Product("《乔布斯传》",1006,"传记",2.5);//创建请求IndexRequest request = new IndexRequest("books");// 规则request.id("5");request.timeout(TimeValue.timeValueSeconds(1));//将数据放入请求 jsonrequest.source(JSON.toJSONString(product),XContentType.JSON);//客户端发送请求IndexResponse index = client.index(request, RequestOptions.DEFAULT);System.out.println(index.toString());System.out.println(index.status());
}

在ES-head界面中可以看到《乔布斯传》已经插入到索引中。

5)获取文档信息

@Test
public void testGetDocument() throws IOException {GetRequest request = new GetRequest("books","2");GetResponse response = client.get(request,RequestOptions.DEFAULT);System.out.println(response.getSourceAsString());//打印文档信息
}

最终打印输出了《数学大师》这本数学家传记。

6)更新文档信息

@Test
public void testUpdateDocument() throws IOException {UpdateRequest request = new UpdateRequest("books","5");request.timeout("1s");Product product = new Product("《乔布斯传》",1006,"传记",88.8);request.doc(JSON.toJSONString(product),XContentType.JSON);UpdateResponse update = client.update(request, RequestOptions.DEFAULT);System.out.println(update.status());
}

7)删除文档

@Test
public void testDeleteDocument() throws IOException {DeleteRequest request = new DeleteRequest("5");request.timeout("1s");DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);System.out.println(delete.status());
}

8)批量数据插入

@Test
public void testBulkDocument() throws IOException {BulkRequest bulkRequest = new BulkRequest();bulkRequest.timeout("5s");ArrayList<Product> prods = new ArrayList<>();prods.add(new Product("《撒哈拉的故事》",1002,"文学",19.9));prods.add(new Product("《线性代数》",1003,"课本",20.8));prods.add(new Product("《穷查理宝典》",1005,"投资",6.9));prods.add(new Product("《万历十五年》",1004,"历史",101));for(int i=0;i<prods.size();i++){bulkRequest.add(new IndexRequest("books").id(""+(i+5)).source(JSON.toJSONString(prods.get(i)),XContentType.JSON));}BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);System.out.println(bulk.hasFailures());
}

9)查询

@Test
public void testSearch() throws IOException {SearchRequest request = new SearchRequest("books");//构建查询条件SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//QueryBuilders.termQuery  //精确匹配//QueryBuilders.matchAllQuery() //匹配所有TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("prod_name","费马");sourceBuilder.query(termQueryBuilder);sourceBuilder.from(0);//分页sourceBuilder.size(5);request.source(sourceBuilder);SearchResponse response = client.search(request, RequestOptions.DEFAULT);System.out.println(JSON.toJSONString(response.getHits()));System.out.println("==================================================");for(SearchHit documentFields : response.getHits().getHits()){System.out.println(documentFields.getSourceAsMap());}
}

6.《深入理解Elasticsearch》读书笔记

上周末把《深入理解Elasticsearch》浏览了一边,对入门者来说,书中部分内容不容易消化理解,所以在网上搜集部分博客资料,摘抄记录如下,以便在后续实践中查阅。

6.1 如何选择合适的分片和副本数

目的:合理规划索引及配置,以达到适应应用变化的目的。

正确认知:分片数索引创建后不可以修改,副本数索引创建后可以通过API随时修改。

多副本的缺点:额外副本占据了额外的存储空间,构建索引副本的开销也随之增大。

同时要注意:如果不创建副本,当主分片发生问题时,可能会造成数据的丢失。

配置参考:最理想的分片数量应该依赖于节点的数量。

参考公式:所需的最大节点数 = 分片数 *(副本数+1)

举例:你计划5个分片和1个副本,那么所需要的最大的节点数为:5*(1+1)=10个节点。

6.2 可不可以基于时间构建索引

目的:选择感兴趣的索引上进行查询,历史索引(时间比较久)的定期删除。

正确操作方法:通过名称为logs_2017_01, logs_2017_02,…..logs_2017_12来构建索引。

6.3 底层索引,段的一些相关知识点

段的概念:ES中的每个分片包含多个segment(段),每一个segment都是一个倒排索引,在查询的时,会把所有的segment查询结果汇总归并为最终的分片查询结果返回。在创建索引的时候,ES会把文档信息写到内存中(为了安全,也一起写到translog),定时(可配置)把数据写到segment缓存小文件中,然后刷新查询,使刚写入的segment可查。虽然写入的segment可查询,但是还没有持久化到磁盘上。因此,还是会存在丢失的可能性的。所以,ES会执行flush操作,把segment持久化到磁盘上并清除translog的数据

段合并:由于自动刷新流程每秒会创建一个新的段,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。

1)消耗资源:每一个段都会消耗文件句柄、内存和CPU运行周期;

2)搜索变慢:每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

段合并的目的:

1)索引段的个数越多,搜索性能越低并且消耗更多的内存;

2)索引段是不可变的,你并不能物理上从中删除信息。(可以物理上删除document,但只是做了删除标记,物理上并没有删除)

3)当段合并时,这些被标记为删除的文档并没有被拷贝至新的索引段中,这样,减少了最终的索引段中的document数目。

段合并的好处:

1)减少索引段的数量并提高检索速度;

2)减少索引的容量(文档数)——段合并会移除被标记为已删除的那些文档;

6.4 CPU飙升排查

      GET /_nodes/hot_threads?pretty

6.5 高负载、高查询频率场景建议

1)启动过滤器缓存和分片查询缓存

过滤器缓存配置:indices.cache.filter.size

分片查询缓存配置:indices.cache.query.enable

2)优化查询语句结构

书本中的过滤器已不再使用5.X以及更高版本。但,依然可以优化查询语句,返回核对查询同样语句返回时间,进行权衡优化。

3)使用路由

有着相同路由值的数据都会保存到相同的分片上。

4)并行查询

建议数据平均分配,多个节点可以有相同的负载。

5)字段数据缓存和断路

当使用聚合和排序等字段数据缓存相关操作时,遇到了内存相关的问题(内存泄漏或者GC停顿),可以考虑使用 doc value。

6)控制size和shard_size

主要针对聚合操作。

增加size和shard_size能使得聚合结果更准确,代价是:更多的网络开销和内存使用。

减少size和shard_size能使得聚合不那么精确,代价是:网络开销小和内存使用率低。

作者:陈淅灿

本文标签: ElasticSearch入门实操