ES快速入门 ES作为一个索引及搜索服务,对外提供丰富的REST接口,快速入门部分的实例使用kibana来测试,目的是对ES的使用方法及流程有个初步的认识。
1. index管理 1.1. 创建index 索引库。包含若干相似结构的 Document 数据,相当于数据库的database。
语法:PUT /index_name
如:
1 2 3 4 5 6 7 PUT /java{ "settings" : { "number_of_shards" : 2 , "number_of_replicas" : 1 } }
number_of_shards - 表示一个索引库将拆分成多片分别存储不同的结点,提高了ES的处理能力
number_of_replicas - 是为每个 primary shard分配的replica shard数,提高了ES的可用性,如果只有一台机器,设置为0
1.2. 修改index 注意:索引一旦创建,primary shard 数量不可变化,可以改变replica shard 数量。
语法:PUT /index_name/_settings
如:
1 2 3 4 PUT /java/_settings{ "number_of_replicas" : 1 }
ES 中对 shard 的分布是有要求的,有其内置的特殊算法:
Replica shard 会保证不和他的那个 primary shard 分配在同一个节点上;如过只有一个节点,则此案例执行后索引的状态一定是yellow。
1.3. 删除index 1 DELETE /java[, other_index]
2. mapping管理 映射,创建映射就是向索引库中创建field(类型、是否索引、是否存储等特性)的过程,下边是document和field与关系数据库的概念的类比:
elasticsearch
关系数据库
index(索引库)
database(数据库)
type(类型)
table(表)
document(文档)
row(记录)
field(域)
column(字段)
注意:6.0之前的版本有type(类型)概念,type相当于关系数据库的表,ES6.x 版本之后,type概念被弱化ES官方将在ES7.0版本中彻底删除type。
2.1 创建mapping 语法:POST /index_name/type_name/_mapping
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /java/course/_mapping{ "properties" : { "name" : { "type" : "text" } , "description" : { "type" : "text" } , "studymodel" : { "type" : "keyword" } } }
2.2.查询mapping 查询所有索引的映射:
1 GET /java/course/_mapping
2.3.更新mapping 映射创建成功可以添加新字段,已有字段不允许更新。
2.4.删除mapping 通过删除索引来删除映射。
3. document管理 3.1. 创建document ES中的文档相当于MySQL数据库表中的记录。
3.1.1. POST语法 此操作为 ES 自动生成 id 的新增 Document 方式。
语法:POST /index_name/type_name/id
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /java/course/1 { "name" : "python从入门到放弃" , "description" : "人生苦短,我用Python" , "studymodel" : "201002" } POST /java/course{ "name" : ".net从入门到放弃" , "description" : ".net程序员谁都不服" , "studymodel" : "201003" }
3.1.2. PUT语法 此操作为手工指定 id 的 Document 新增方式。
语法:PUT/index_name/type_name/id{field_name:field_value}
如:
1 2 3 4 5 6 PUT /java/course/2 { "name" : "php从入门到放弃" , "description" : "php是世界上最好的语言" , "studymodel" : "201001" }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "_index" : "test_index" , 新增的 document 在什么 index 中, "_type" : "my_type" , 新增的 document 在 index 中的哪一个 type 中。 "_id" : "1" , 指定的 id 是多少 "_version" : 1 , document 的版本是多少,版本从 1 开始递增,每次写操作都会+1 "result" : "created" , 本次操作的结果,created 创建,updated 修改,deleted 删除 "_shards" : { 分片信息 "total" : 2 , 分片数量只提示 primary shard "successful" : 1 , 数据 document 一定只存放在 index 中的某一个 primary shard 中 "failed" : 0 } , "_seq_no" : 0 , "_primary_term" : 1 }
通过head查询数据
3.2. 查询document 语法:
GET /index_name/type_name/id
或
GET /index_name/type_name/_search?q=field_name:field_value
如:根据课程id查询文档
如:查询所有记录
1 GET /java/ course/_search
如:查询名称中包括php 关键字的的记录
1 GET /java/course/_search?q =name:门
结果:
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 { "took" : 1 , # 执行的时长。单位毫秒 "timed_out" : false , # 是否超时 "_shards" : { # shard 相关数据 "total" : 1 , # 总计多少个 shard "successful" : 1 , # 成功返回结果的 shard 数量 "skipped" : 0 , "failed" : 0 } , "hits" : { # 搜索结果相关数据 "total" : 3 , # 总计多少数据,符合搜索条件的数据数量 "max_score" : 1 , # 最大相关度分数,和搜索条件的匹配度 "hits" : [ # 具体的搜索结果 { "_index" : "java" , # 索引名称 "_type" : "course" , # 类型名称 "_id" : "1" , # id 值 "_score" : 1 , # 匹配度分数,本条数据匹配度分数 "_source" : { # 具体的数据内容 "name" : "php从入门到放弃" , "description" : "php是世界上最好的语言" , "studymodel" : "201001" } , { "_index" : "java" , "_type" : "course" , "_id" : "2" , "_score" : 0.13353139 , "_source" : { "name" : "php从入门到放弃" , "description" : "php是世界上最好的语言" , "studymodel" : "201001" } } , { "_index" : "java" , "_type" : "course" , "_id" : "6ljFCnIBp91f7uS8FkjS" , "_score" : 0.13353139 , "_source" : { "name" : ".net从入门到放弃" , "description" : ".net程序员谁都不服" , "studymodel" : "201003" } } ] } }
3.3. 删除Document ES 中执行删除操作时,ES先标记Document为deleted状态,而不是直接物理删除。当ES 存储空间不足或工作空闲时,才会执行物理删除操作,标记为deleted状态的数据不会被查询搜索到(ES 中删除 index ,也是标记。后续才会执行物理删除。所有的标记动作都是为了NRT(近实时)实现)
语法:DELETE /index_name/type_name/id
如:
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "_index" : "java" , "_type" : "course" , "_id" : "2" , "_version" : 2 , "result" : "deleted" , "_shards" : { "total" : 1 , "successful" : 1 , "failed" : 0 } , "_seq_no" : 3 , "_primary_term" : 1 }
4. ES读写过程 4.1. documnet routing(数据路由) 当客户端创建document的时候,es需要确定这个document放在该index哪个shard上,这个过程就是document routing。
路由过程:
路由算法:shard = hash(id) %number_of_primary_shards
id:document的_id,可能是手动指定,也可能是自动生成,决定一个document在哪个shard上
number_of_primary_shards: 主分片數量。
4.4. 为什么primary shard数量不可变? 原因:假如我们的集群在初始化的时候有5个primary shard,我们往里边加入一个document id=5,假如hash(5)=23,这时该document 将被加入 (shard=23%5=3)P3这个分片上。如果随后我们给es集群添加一个primary shard ,此时就有6个primary shard,当我们GET id=5 ,这条数据的时候,es会计算该请求的路由信息找到存储他的 primary shard(shard=23%6=5) ,根据计算结果定位到P5分片上。而我们的数据在P3上。所以es集群无法添加primary shard,但是可以扩展replicas shard。
5. luke查看ES的逻辑结构 luke 使用指南
IK分词器 1. 测试分词器 在添加文档时会进行分词,索引中存放的就是一个一个的词(term),当你去搜索时就是拿关键字去匹配词,最终找到词关联的文档。
测试当前索引库使用的分词器:
1 2 3 4 POST /_analyze{ "text" : "测试分词器,后边是测试内容:spring cloud实战" }
结果如下:
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 { "tokens" : [ { "token" : "测" , "start_offset" : 0 , "end_offset" : 1 , "type" : "<IDEOGRAPHIC>" , "position" : 0 } , { "token" : "试" , "start_offset" : 1 , "end_offset" : 2 , "type" : "<IDEOGRAPHIC>" , "position" : 1 } , { "token" : "分" , "start_offset" : 2 , "end_offset" : 3 , "type" : "<IDEOGRAPHIC>" , "position" : 2 } , { "token" : "词" , "start_offset" : 3 , "end_offset" : 4 , "type" : "<IDEOGRAPHIC>" , "position" : 3 } , { "token" : "器" , "start_offset" : 4 , "end_offset" : 5 , "type" : "<IDEOGRAPHIC>" , "position" : 4 } , { "token" : "后" , "start_offset" : 6 , "end_offset" : 7 , "type" : "<IDEOGRAPHIC>" , "position" : 5 } , { "token" : "边" , "start_offset" : 7 , "end_offset" : 8 , "type" : "<IDEOGRAPHIC>" , "position" : 6 } , { "token" : "是" , "start_offset" : 8 , "end_offset" : 9 , "type" : "<IDEOGRAPHIC>" , "position" : 7 } , { "token" : "测" , "start_offset" : 9 , "end_offset" : 10 , "type" : "<IDEOGRAPHIC>" , "position" : 8 } , { "token" : "试" , "start_offset" : 10 , "end_offset" : 11 , "type" : "<IDEOGRAPHIC>" , "position" : 9 } , { "token" : "内" , "start_offset" : 11 , "end_offset" : 12 , "type" : "<IDEOGRAPHIC>" , "position" : 10 } , { "token" : "容" , "start_offset" : 12 , "end_offset" : 13 , "type" : "<IDEOGRAPHIC>" , "position" : 11 } , { "token" : "spring" , "start_offset" : 14 , "end_offset" : 20 , "type" : "<ALPHANUM>" , "position" : 12 } , { "token" : "cloud" , "start_offset" : 21 , "end_offset" : 26 , "type" : "<ALPHANUM>" , "position" : 13 } ] }
会发现分词的效果将“测试”这个词拆分成两个单字“测”和“试”,这是因为当前索引库使用的分词器对中文就是单字分词。
2. 中文分词器 2.1.Lucene自带中文分词器
StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”, 效果:“我”、“爱”、“中”、“国”。
CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。
上边两个分词器无法满足需求。
SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库和禁用词库等不好处理
2.2.第三方中文分析器 paoding : 庖丁解牛 中最多支持Lucene 3.0,已经过时,不予考虑。
IK-analyzer :IK 支持Lucene 4.10从20年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开 始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词 歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词衍化。 但是也就是2012年12月后没有在更新。
3. 安装IK分词器 使用IK分词器可以实现对中文分词的效果。
下载IK分词器:(Github地址 )
1、下载zip:
2、解压,并将解压的文件拷贝到ES安装目录的plugins下的ik(重命名)目录下,重启es
3、测试分词效果:
1 2 3 4 5 POST /_analyze{ "text" : "中华人民共和国人民大会堂" , "analyzer" : "ik_smart" }
4. 两种分词模式 ik分词器有两种分词模式:ik_max_word和ik_smart模式。
1、ik_max_word
会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民大会堂、人民、共和国、大会堂、大会、会堂等词语。
2、ik_smart
会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
5. 自定义词库 如果要让分词器支持一些专有词语,可以自定义词库。
iK分词器自带的main.dic的文件为扩展词典,stopword.dic为停用词典。
也可以上边的目录中新建一个my.dic文件(注意文件格式为utf-8(不要选择utf-8 BOM) )
可以在其中自定义词汇:
比如定义:
配置文件中 配置my.dic,
1 2 3 4 5 6 7 8 9 10 11 12 13 ?xml version="1.0" encoding="UTF-8"?><!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd" > <properties > <comment > IK Analyzer 扩展配置</comment > <entry key ="ext_dict" > main.dic</entry > <entry key ="ext_stopwords" > stopword.dic</entry > </properties >
field详细介绍 上边安装了ik分词器,如何在索引和搜索时去使用ik分词器呢?如何指定field的类型?比如日期类型、数值类型等。
ES字段类型
1. field的属性介绍 1.1. type: 通过type属性指定field的类型。
1 2 3 "name" : { "type" : "text" }
1.2. analyzer: 通过analyzer属性指定分词模式。
1 2 3 4 "name" : { "type" : "text" , "analyzer" : "ik_max_word" }
上边指定了analyzer是指在索引和搜索都使用ik_max_word,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性。 对于ik分词器建议是索引时使用ik_max_word将搜索内容进行细粒度分词,搜索时使用ik_smart提高搜索精确性。
1 2 3 4 5 "name" : { "type" : "text" , "analyzer" : "ik_max_word" , #生成索引目录时 "search_analyzer" : "ik_smart" #检索时 }
1.3. index: 通过index属性指定是否索引 。 默认为index=true,即要进行索引,只有进行索引才可以从索引库搜索到。 但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置 为false。 删除索引,重新创建映射,将pic的index设置为false,尝试根据pic去搜索,结果搜索不到数据
1 2 3 4 "pic" : { "type" : "text" , "index" : false }
1.4. source: 如果某个字段内容非常多,业务里面只需要能对该字段进行搜索,比如:商品描述。查看文档内容会再次到mysql或者hbase中取数据,把大字段的内容存在Elasticsearch中只会增大索引,这一点文档数量越大结果越明显,如果一条文档节省几KB,放大到亿万级的量结果也是非常可观的。
如果只想存储某几个字段的原始值到Elasticsearch,可以通过incudes参数来设置,在mapping中的设置如下:
1 2 3 4 5 6 POST /java/course/_mapping{ "_source" : { "includes" : [ "description" ] } }
同样,可以通过excludes参数排除某些字段:
1 2 3 4 5 6 POST /java/course/_mapping{ "_source" : { "excludes" : [ "description" ] } }
2. 常用field类型 2.1. text文本字段 例如: 1、创建新映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 POST /java/course/_mapping{ "_source" : { "includes" : [ "description" ] } "properties" : { "name" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } , "description" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } , "pic" : { "type" : "text" , "index" : false } } }
2、插入文档:
1 2 3 4 5 6 POST /java/course/1 { "name" : "python从入门到放弃" , "description" : "人生苦短,我用Python" , "pic" : "250.jpg" }
3、查询测试:
1 2 3 GET /java/course/_search?q=name: 放弃 GET /java/course/_search?q=description: 人生 GET /java/course/_search?q=pic: 250. jpg
结果:name和description都支持全文检索,pic不可作为查询条件
2.2. keyword关键字字段 上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段往索引目录写时是不进行分词 的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过虑、排序、聚合等。
例如: 1、更改映射:
1 2 3 4 5 6 7 8 POST /java/course/_mapping{ "properties" : { "studymodel" : { "type" : "keyword" } } }
2、插入文档:
1 2 3 4 5 6 7 PUT /java/course/2 { "name" : "java编程基础" , "description" : "java语言是世界第一编程语言" , "pic" : "250.jpg" , "studymodel" : "2010年01月" }
3、根据name查询文档:
1 GET /java/course/_search?q =studymodel:2010年01月
name是keyword类型,所以查询方式是精确查询。
2.3. date日期类型 日期类型不用设置分词器,通常日期类型的字段用于排序。 1)format 通过format设置日期格式,多个格式使用双竖线||分隔, 每个格式都会被依次尝试, 直到找到匹配的
例如: 1、设置允许date字段存储年月日时分秒、年月日及毫秒三种格式。
1 2 3 4 5 6 7 8 9 POST /java/course/_mapping{ "properties" : { "timestamp" : { "type" : "date" , "format" : "yyyy-MM-dd" } } }
2、插入文档:
1 2 3 4 5 6 7 8 PUT /java/course/3 { "name" : "spring开发基础" , "description" : "spring 在java领域非常流行,java程序员都在用。" , "studymodel" : "201001" , "pic" : "250.jpg" , "timestamp" : "2018-07-04 18:28:58" }
2.4. Numeric类型 es中的数字类型经过分词(特殊)后支持排序和区间搜索
例如: 1、更新已有映射:
1 2 3 4 5 6 7 8 POST /java/course/_mapping{ "properties" : { "price" : { "type" : "float" } } }
2、插入文档
1 2 3 4 5 6 7 8 PUT /java/course/3 { "name" : "spring开发基础" , "description" : "spring 在java领域非常流行,java程序员都在用。" , "studymodel" : "201001" , "pic" : "250.jpg" , "price" : 38.6 }
3. field属性的设置标准
属性
标准
type
是否有意义
index
是否搜索
source
是否展示
Spring Boot整合ElasticSearch 1. ES客户端 ES提供多种不同的客户端:
1、TransportClient
ES提供的传统客户端,官方计划8.0版本删除此客户端。
2、RestClient
RestClient是官方推荐使用的,它包括两种:REST Low Level Client和 REST High Level Client。ES在6.0之后提供REST High Level Client, 两种客户端官方更推荐使用 REST High Level Client,不过当前它还处于完善中,有些功能还没有。
2. 搭建工程 2.1. pom.xml 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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.2.RELEASE</version > </parent > <groupId > com.example</groupId > <artifactId > springboot_elasticsearch</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <elasticsearch.version > 6.2.3</elasticsearch.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-high-level-client</artifactId > <version > ${elasticsearch.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > </dependency > </dependencies > </project >
2.2. application.yml 1 2 3 4 5 spring: elasticsearch: rest: uris: - http://192.168.204.132:9200
2.3. app 1 2 3 4 5 6 7 8 9 10 11 12 package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class ElasticsearchApp { public static void main (String[] args) { SpringApplication.run(ElasticsearchApp.class, args); } }
3. 索引管理 3.1. 创建索引库 3.1.1.api 创建索引库:
1 2 3 4 5 6 7 PUT /java{ "settings" : { "number_of_shards" : 2 , "number_of_replicas" : 0 } }
创建映射:
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 POST /java/course/_mapping{ "_source" : { "excludes" : [ "description" ] } , "properties" : { "name" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } , "description" : { "type" : "text" , "analyzer" : "ik_max_word" , "search_analyzer" : "ik_smart" } , "studymodel" : { "type" : "keyword" } , "price" : { "type" : "float" } , "pic" : { "type" : "text" , "index" : false } } }
3.1.2.Java Client 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 package com.example.test;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;import org.elasticsearch.action.DocWriteResponse;import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;import org.elasticsearch.action.delete.DeleteRequest;import org.elasticsearch.action.delete.DeleteResponse;import org.elasticsearch.action.get.GetRequest;import org.elasticsearch.action.get.GetResponse;import org.elasticsearch.action.index.IndexRequest;import org.elasticsearch.action.index.IndexResponse;import org.elasticsearch.client.IndicesClient;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.common.settings.Settings;import org.elasticsearch.common.xcontent.XContentType;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = {ElasticsearchApp.class}) public class IndexWriterTest { @Autowired private RestHighLevelClient restHighLevelClient; @Test public void testCreateIndex () throws IOException { CreateIndexRequest createIndexRequest = new CreateIndexRequest ("java" ); createIndexRequest.settings("{\n" + " \"number_of_shards\" : 2,\n" + " \"number_of_replicas\" : 0\n" + " }" , XContentType.JSON); createIndexRequest.mapping("course" , "{\r\n" + " \"_source\": {\r\n" + " \"excludes\":[\"description\"]\r\n" + " }, \r\n" + " \"properties\": {\r\n" + " \"name\": {\r\n" + " \"type\": \"text\",\r\n" + " \"analyzer\":\"ik_max_word\",\r\n" + " \"search_analyzer\":\"ik_smart\"\r\n" + " },\r\n" + " \"description\": {\r\n" + " \"type\": \"text\",\r\n" + " \"analyzer\":\"ik_max_word\",\r\n" + " \"search_analyzer\":\"ik_smart\"\r\n" + " },\r\n" + " \"studymodel\": {\r\n" + " \"type\": \"keyword\"\r\n" + " },\r\n" + " \"price\": {\r\n" + " \"type\": \"float\"\r\n" + " },\r\n" + " }\r\n" + "}" , XContentType.JSON); IndicesClient indices = restHighLevelClient.indices(); CreateIndexResponse createIndexResponse = indices.create(createIndexRequest); boolean acknowledged = createIndexResponse.isAcknowledged(); System.out.println(acknowledged); } }
3.2. 删除索引库 3.2.1.api
3.2.2.java client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testDeleteIndex () throws IOException { DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest ("java" ); IndicesClient indices = restHighLevelClient.indices(); DeleteIndexResponse deleteIndexResponse = indices.delete(deleteIndexRequest); boolean acknowledged = deleteIndexResponse.isAcknowledged(); System.out.println(acknowledged); }
3.2. 添加文档 3.2.1.api 1 2 3 4 5 6 7 POST /java/course/1 { "name" : "spring cloud实战" , "description" : "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。" , "studymodel" : "201001" , "price" : 5.6 }
3.2.2.java client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testAddDocument () throws IOException { IndexRequest indexRequest = new IndexRequest ("java" , "course" , "1" ); indexRequest.source("{\n" + " \"name\":\"spring cloud实战\",\n" + " \"description\":\"本课程主要从四个章节进行讲解: 1.微服务架构入门 " + "2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心nacos。\",\n" + " \"studymodel\":\"201001\",\n" + " \"price\":5.6\n" + "}" , XContentType.JSON); IndexResponse indexResponse = restHighLevelClient.index(indexRequest); System.out.println(indexResponse.toString()); }
3.3. 批量添加文档 支持在一次API调用中,对不同的索引进行操作。支持四种类型的操作:index、create、update、delete。
1 2 3 4 5 6 POST /_bulk { action: { metadata } } { requestbody } \n { action: { metadata } } { requestbody } \n ...
3.3.1.api 1 2 3 4 5 POST /_bulk{ "index" : { "_index" : "java" , "_type" : "course" } } { "name" : "php实战" , "description" : "php谁都不服" , "studymodel" : "201001" , "price" : "5.6" } { "index" : { "_index" : "java" , "_type" : "course" } } { "name" : "net实战" , "description" : "net从入门到放弃" , "studymodel" : "201001" , "price" : "7.6" }
3.3.2.java client 1 2 3 4 5 6 7 8 9 10 11 @Test public void testBulkAddDocument () throws IOException { BulkRequest bulkRequest = new BulkRequest (); bulkRequest.add(new IndexRequest ("java" , "course" ).source("{...}" , XContentType.JSON)); bulkRequest.add(new IndexRequest ("java" , "course" ).source("{...}" , XContentType.JSON)); BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest); System.out.println(bulkResponse.hasFailures()); }
3.4. 修改文档 3.4.1.api 1 2 3 4 PUT /java/course/1 { "price" : 66.6 }
3.4.2.java client 1 2 3 4 5 6 7 8 9 10 11 @Test public void testUpdateDocument () throws IOException { UpdateRequest updateRequest = new UpdateRequest ("java" , "course" , "1" ); updateRequest.doc("{\n" + " \"price\":7.6\n" + "}" , XContentType.JSON); UpdateResponse updateResponse = restHighLevelClient.update(updateRequest); System.out.println(updateResponse.getResult()); }
3.5. 删除文档 3.5.1.api
3.5.2.java client 1 2 3 4 5 6 7 8 9 10 @Test public void testDelDocument () throws IOException { DeleteRequest deleteRequest = new DeleteRequest ("java" ,"course" ,"1" ); DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest); System.out.println(deleteResponse.getResult()); }
4. 文档搜索 4.1.准备环境 向索引库中插入以下数据:
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 PUT /java/course/1 { "name" : "Bootstrap开发" , "description" : "Bootstrap是由Twitter推出的一个前台页面开发css框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个css,不受浏览器限制的精美界面css效果。" , "studymodel" : "201002" , "price" : 38.6 , "pic" : "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" } PUT /java/course/2 { "name" : "java编程基础" , "description" : "java语言是世界第一编程语言,在软件开发领域使用人数最多。" , "studymodel" : "201001" , "price" : 68.6 , "pic" : "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" } PUT /java/course/3 { "name" : "spring开发基础" , "description" : "spring 在java领域非常流行,java程序员都在用。" , "studymodel" : "201001" , "price" : 88.6 , "pic" : "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" }
4.2. 简单搜索 简单搜索就是通过url进行查询,以get方式请求ES。 语法:
GET /index_name/type_name/doc_id GET [/index_name/type_name/]_search[?parameter_name=parameter_value&…]
例如:
1 GET /java/course/_search?q=name: spring&sort=price: desc
注意: 如果查询条件复杂,很难构建搜索条件 ,生产环境中很少使用。 例如:要求搜索条件为商品名称包含手机,价格在 1000~5000之间,销量在每月 500 以上,根据价格升序排列,分页查询第二页,每页 40 条数据:?q=xxxx:xxx&range=xxx:xxx:xxx&aggs&sort&from&size
4.2.1.api
4.2.2.java client 1 2 3 4 5 6 7 8 9 10 @Test public void getDoc () throws IOException { GetRequest getRequest = new GetRequest ("java" ,"course" ,"1" ); GetResponse getResponse = restHighLevelClient.get(getRequest); boolean exists = getResponse.isExists(); System.out.println(exists);String source = getResponse.getSourceAsString(); System.out.println(source); }
4.3. DSL搜索 DSL(Domain Specific Language)是ES提出的基于json的搜索方式,在搜索时传入特定的json格式的数据来完成不同的搜索需求,DSL比URI搜索方式功能强大,在项目中建议使用DSL方式来完成搜索。 语法:
GET /index_name/type_name/_search { “commond”:{ “parameter_name” : “parameter_value” } }
4.3.1.match_all查询 4.3.1.1.api 1 2 3 4 5 6 GET /java/course/_search{ "query" : { "match_all" : { } } }
4.3.1.2.java client 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 package com.example.test;import com.example.ElasticsearchApp;import org.elasticsearch.action.get.GetRequest;import org.elasticsearch.action.get.GetResponse;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.elasticsearch.search.sort.SortOrder;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.io.IOException;@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = {ElasticsearchApp.class}) public class IndexReaderTest { @Autowired private RestHighLevelClient restHighLevelClient; private SearchRequest searchRequest; private SearchResponse searchResponse; @Before public void init () { searchRequest = new SearchRequest (); searchRequest.indices("java" ); searchRequest.types("course" ); } @Test public void testMatchAll () throws IOException { SearchRequest searchRequest = new SearchRequest (); searchRequest.indices("java" ); searchRequest.types("course" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); searchSourceBuilder.query(QueryBuilders.matchAllQuery()); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest); SearchHits searchHits = searchResponse.getHits(); long totalHits = searchHits.getTotalHits(); System.out.println("共搜索到" +totalHits+"条文档" ); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(hit.getSourceAsString()); } } @After public void show () { SearchHits searchHits = searchResponse.getHits(); long totalHits = searchHits.getTotalHits(); System.out.println("共搜索到" +totalHits+"条文档" ); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(hit.getSourceAsString()); } } }
3.3.2.分页查询 3.3.2.1.api 1 2 3 4 5 6 7 8 9 GET /java/course/_search{ "query" : { "match_all" : { } } , "from" : 1 , # 从第几条数据开始查询,从0 开始计数 "size" : 3 , # 查询多少数据 "sort" : [ { "price" : "asc" } ] }
3.3.2.2.java client 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testSearchPage () throws Exception { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); searchSourceBuilder.query(QueryBuilders.matchAllQuery()); searchSourceBuilder.from(1 ); searchSourceBuilder.size(5 ); searchSourceBuilder.sort("price" , SortOrder.ASC); searchRequest.source(searchSourceBuilder); searchResponse = restHighLevelClient.search(searchRequest); }
3.3.4.match查询 match Query即全文检索,它的搜索方式是先将搜索字符串分词,再使用各各词条从索引中搜索。
3.3.4.1.api query:搜索的关键字 operator:or 表示 只要有一个词在文档中出现则就符合条件,and表示每个词都在文档中出现则才符合条件。
1、基本使用:
1 2 3 4 5 6 7 8 9 10 GET /java/course/_search{ "query" : { "match" : { "name" : { "query" : "spring开发" } } } }
2、operator:
1 2 3 4 5 6 7 8 9 10 11 GET /java/course/_search{ "query" : { "match" : { "name" : { "query" : "spring开发" , "operator" : "and" } } } }
上边的搜索的执行过程是: 1、将“spring开发”分词,分为spring、开发两个词 2、再使用spring和开发两个词去匹配索引中搜索。 3、由于设置了operator为and,必须匹配两个词成功时才返回该文档。
3.3.4.2 java client 1 2 3 4 5 6 7 8 9 10 11 @Test public void testMatchQuery () throws Exception { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); searchSourceBuilder.query(QueryBuilders.matchQuery("name" , "spring开 发" ).operator(Operator.AND)); searchRequest.source(searchSourceBuilder); searchResponse = restHighLevelClient.search(searchRequest); }
3.3.5.multi_match查询 matchQuery是在一个field中去匹配,multiQuery是拿关键字去多个Field中匹配。
3.3.5.1.api 1、基本使用 例子:关键字 “开发”去匹配name 和description字段
1 2 3 4 5 6 7 8 9 GET /java/course/_search{ "query" : { "multi_match" : { "query" : "开发" , "fields" : [ "name" , "description" ] } } }
注意:此搜索操作适合构建复杂查询条件,生产环境常用。
3.3.5.2.java client 1 2 3 4 5 6 7 8 9 10 @Test public void testMultiMatchQuery () throws Exception { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); searchSourceBuilder.query(QueryBuilders.multiMatchQuery("开发" ,"name" ,"description" )); searchRequest.source(searchSourceBuilder); searchResponse = restHighLevelClient.search(searchRequest); }
3.3.6.bool查询 布尔查询对应于Lucene的BooleanQuery查询,实现将多个查询组合起来。 参数: must:表示必须,多个查询条件必须都满足。(通常使用must) should:表示或者,多个查询条件只要有一个满足即可。 must_not:表示非。
3.3.6.1.api 例如:查询name包括“开发”并且价格区间是1-100的文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GET /java/course/_search{ "query" : { "bool" : { "must" : [ { "match" : { "name" : "开发" } } , { "range" : { "price" : { "gte" : 50 , "lte" : 100 } } } ] } } }
3.3.6.2.java client 1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testBooleanMatch () throws IOException { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("name" ,"开发" )); boolQueryBuilder.must(QueryBuilders.rangeQuery("price" ).gte("50" ).lte(100 )); searchSourceBuilder.query(boolQueryBuilder); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest); }
3.3.7.filter查询 过滤查询。此操作实际上就是 query DSL 的补充语法。过滤的时候,不进行任何的匹配分数计算,相对于 query 来说,filter 相对效率较高。Query 要计算搜索匹配相关度分数。Query更加适合复杂的条件搜索。
3.3.7.1.api 如:使用bool查询,搜索 name中包含 “开发”的数据,且price在 10~100 之间 1、不使用 filter, name和price需要计算相关度分数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GET /java/course/_search{ "query" : { "bool" : { "must" : [ { "match" : { "name" : "开发" } } , { "range" : { # 范围, 字段的数据必须满足某范围才有结果。 "price" : { "gte" : 10 , # 比较符号 lt gt lte gte "lte" : 100 } } } ] } } }
2、使用 filter, price不需要计算相关度分数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GET /java/course/_search{ "query" : { "bool" : { "must" : [ { "match" : { "name" : "开发" } } ] , "filter" : { # 过滤,在已有的搜索结果中进行过滤,满足条件的返回。 "range" : { "price" : { "gte" : 1 , "lte" : 100 } } } } } }
3.3.7.2.java client 1 2 3 4 5 6 7 8 9 10 @Test public void testFilterQuery () throws IOException { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("name" ,"开发" )); boolQueryBuilder.filter(QueryBuilders.rangeQuery("price" ).gte(10 ).lte(100 )) searchSourceBuilder.query(boolQueryBuilder); searchRequest.source(searchSourceBuilder); searchResponse = restHighLevelClient.search(searchRequest); }
3.3.8.highlight查询 高亮显示:高亮不是搜索条件,是显示逻辑,在搜索的时候,经常需要对搜索关键字实现高亮显示。
3.3.8.1.api 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 GET /java/course/_search{ "query" : { "match" : { "name" : "开发" } } , "highlight" : { "pre_tags" : [ "<font color='red'>" ] , "post_tags" : [ "</font>" ] , "fields" : { "name" : { } } } }
3.3.8.2.java clent 1、查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testHighLightQuery () throws Exception { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder (); searchSourceBuilder.query(QueryBuilders.matchQuery("name" , "spring" )); HighlightBuilder highlightBuilder = new HighlightBuilder (); highlightBuilder.preTags("<font color='red'>" ); highlightBuilder.postTags("</font>" ); highlightBuilder.fields().add(new HighlightBuilder .Field("name" )); searchSourceBuilder.highlighter(highlightBuilder); searchRequest.source(searchSourceBuilder); searchResponse = restHighLevelClient.search(searchRequest); }
2、遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @After public void displayDoc () { SearchHits searchHits = searchResponse.getHits(); long totalHits = searchHits.getTotalHits(); System.out.println("共搜索到" + totalHits + "条文档" ); SearchHit[] hits = searchHits.getHits(); for (int i = 0 ; i < hits.length; i++) { SearchHit hit = hits[i]; String id = hit.getId(); System.out.println("id:" + id); String source = hit.getSourceAsString(); System.out.println(source); Map<String, HighlightField> highlightFields = hit.getHighlightFields(); if (highlightFields != null ) { HighlightField highlightField = highlightFields.get("name" ); Text[] fragments = highlightField.getFragments(); System.out.println("高亮字段:" + fragments[0 ].toString()); } } }
集群管理 1.集群结构 ES通常以集群方式工作,这样做不仅能够提高 ES的搜索能力 还可以处理大数据搜索的能力,同时也增加了系统的容错能力 及高可用。
ES集群结构的示意图
此处的设置为:每个主分片有两个副本, 如果某个节点挂了也不怕,比如节点1挂了,我们可以查询位于节点3和节点3上的副本0
添加文档过程:
(1)假设用户把请求发给了节点1
(2)系统通过余数算法得知这个’文档’应该属于主分片2,于是请求被转发到保存该主分片的节点3
(3)系统把文档保存在节点3的主分片2中,然后将请求转发至其他两个保存副本的节点。
查询文档过程:
(1) 请求被发给了节点1
(2)节点1计算出该数据属于主分片2,这时候,有三个选择,分别是位于节点1的副本2, 节点2的副本2,节点3的主分片2, 假设节点1负载均衡,采用轮询的方式,选中了节点2,把请求转发。
(3) 节点2把数据返回给节点1, 节点1 最后返回给客户端。
2. 创建结点2 1、拷贝节点elasticsearch-1
2、修改elasticsearch.yml内容如下:
1 2 node.name: power_shop_node_2 discovery.zen.ping.unicast.hosts: ["192.168.204.132:9300" , "192.168.204.133:9300" ]
3、删除节点2的data目录
3. 查看集群健康状态 1、查询当前集群的健康信息:
2、结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "cluster_name" : "power_shop" , "status" : "green" , "timed_out" : false , "number_of_nodes" : 2 , "number_of_data_nodes" : 2 , "active_primary_shards" : 2 , "active_shards" : 4 , "relocating_shards" : 0 , "initializing_shards" : 0 , "unassigned_shards" : 0 , "delayed_unassigned_shards" : 0 , "number_of_pending_tasks" : 0 , "number_of_in_flight_fetch" : 0 , "task_max_waiting_in_queue_millis" : 0 , "active_shards_percent_as_number" : 100 }
status :用三种颜色来展示健康状态
green:索引库的每个 primary shard 和 replica shard 都是 active 的
yellow:索引库的每个 primary shard 都是 active 的,但部分的 replica shard 不是 active 的,如单节点创建
备份分配
red:不是所有的 primary shard 都是 active 状态的。
4. 测试 1、启动两个节点 ,测试集群健康状况和分片情况
2、关闭节点2,测试集群状态
3、创建备份分配,关闭节点2,再测试集群状态
参考 ♥ElasticSearch知识体系详解♥
ES-入门介绍
Elasticsearch基础入门知识
https://github.com/i-xiaoxin/2022/10/24/ElasticSearch基础知识/