diff --git a/elk/elasticsearch/ElasticSearch Mapping 的数据类型和参数介绍.md b/elk/elasticsearch/ElasticSearch Mapping 的数据类型和参数介绍.md new file mode 100644 index 0000000..cc1af25 --- /dev/null +++ b/elk/elasticsearch/ElasticSearch Mapping 的数据类型和参数介绍.md @@ -0,0 +1,430 @@ +> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.cnblogs.com](https://www.cnblogs.com/tanghaorong/p/16323253.html) + +**目录** + +* [1、什么是 Mapping](#_label0) +* [2、Mapping 的数据类型](#_label1) +* [3、Mapping 的主要参数](#_label2) + * [3.1、mapping 组成](#_label2_0) + * [3.2、mapping 参数](#_label2_1) + +[回到顶部](#_labelTop) + +1、什么是 Mapping +------------- + +在前面 [ElasticSearch 的基础概念介绍](https://www.cnblogs.com/tanghaorong/p/16271893.html) 中,我们讲到了 Mapping 类似于数据库中的表结构定义 schema,它的主要作用是:**用来定义索引中的字段的名称、定义字段的数据类型和定义字段类型的一些其它参数**,比如字符串、数字、布尔字段,倒排索引的相关配置,设置某个字段为不被索引、记录 position 等。每一种数据类型都有对应的使用场景,并且每个文档都有映射,但是在大多数使用场景中,我们并不需要显示的创建映射,因为 ES 中实现了动态映射。我们在索引中写入一个下面的 JSON 文档: + +```apl +{ + "name":"jack", + "age":18, + "birthDate": "1991-10-05" +} +``` + +在动态映射的作用下,name 会映射成 text 类型,age 会映射成 long 类型,birthDate 会被映射为 date 类型,映射的索引信息如下。 + +```apl +{ + "mappings": { + "_doc": { + "properties": { + "age": { + "type": "long" + }, + "birthDate": { + "type": "date" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } +} +``` + +自动判断的规则如下: + +
JSON TypeField Type
Boolean:true、flaseboolean
Whole number:123、456、876long
Floating point:123.43、234.534double
String,valid date:"2022-05-15"date
String:"Hello Elasticsearch"string
+ +在学习了 Mapping 的映射之后,让我们来看下字段的数据类型有哪些吧! + +[回到顶部](#_labelTop) + +2、Mapping 的数据类型 +--------------- + +ES 字段类型类似于 MySQL 中的字段类型,ES 字段类型主要有:核心类型、复杂类型、地理类型以及特殊类型,常见的 ELasticSearch 数据类型如下: + +> ES 数据类型的官方文档:[Field data types | Elasticsearch Guide [7.17] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-types.html) + +
一级分类二级分类具体类型
核心类型字符串类型string,text,keyword
整数类型integer,long,short,byte
浮点类型double,float,half_float,scaled_float
逻辑类型boolean
日期类型date
范围类型range(Integer_range,long_range,date_range...)
二进制类型binary (BASE64 的二进制)
复合类型数组类型array
对象类型object
嵌套类型nested
地理类型地理坐标类型geo_point
地理地图geo_shape
特殊类型IP 类型ip
.........
+ +下面简单介绍一下常用的类型: + +字符串类型 + +从 ElasticSearch 5.x 开始不再支持 string,由 text 和 keyword 类型替代: + +* text 类型适用于需要被全文检索的字段,因为它会分词,例如新闻正文、邮件内容等比较长的文字,text 类型会被 Lucene 分词器(Analyzer)处理为一个个词项,并使用 Lucene 倒排索引存储,**text 字段不能被用于排序**,如果需要使用该类型的字段只需要在定义映射时指定 JSON 中对应字段的 type 为 text。text 类型详细可参考:[Text type family | Elasticsearch Guide [7.17] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/text.html) +* keyword 只能通过精确值搜索到,适合简短、结构化字符串,例如主机名、姓名、商品名称等,**可以用于过滤、排序、聚合检索,也可以用于精确查询**。keyword 类型详细可参考:[Keyword type family | Elasticsearch Guide [7.17] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/keyword.html) + +> 总结:对 text 类型的字段,会先使用分词器分词,生成倒排索引,用于之后的搜索。对 keyword 类型的字段,不会分词,搜索时只能精确查找 + +**关于 text 类型的常用参数:** + +* analyzer:指明该字段用于索引时和搜索时的分析字符串的分词器(使用 search_analyzer 可覆盖它)。 默认为索引分析器或标准分词器 +* fielddata:指明该字段是否可以使用内存中的 fielddata 进行排序,聚合或脚本编写?默认值为 false,可取值 true 或 false。(排序,分组需要指定为 true) +* fields:【多数类型】text 类型字段会被分词搜索,不能用于排序,而当字段既要能通过分词搜索,又要能够排序,就要设置 fields 为 keyword 类型进行聚合排序。 +* index:【是否被索引】设置该字段是否可以用于搜索。默认为 true,表示可以用于搜索。 +* search_analyzer:设置在搜索时,用于分析该字段的分析器,默认是【analyzer】参数的值。 +* search_quote_analyzer:设置在遇到短语搜索时,用于分析该字段的分析器,默认是【search_analyzer】参数的值。 +* index_options:【索引选项】用于控制在索引过程中哪些信息会被写入到倒排索引中 + * docs:只索引文档号到倒排索引中,但是并不会存储 + * freqs:文档号和关键词的出现频率会被索引,词频用于给文档进行评分,重复词的评分会高于单个次评分 + * positions:文档号、词频和关键词 term 的相对位置会被索引,相对位置可用于编辑距离计算和短语查询 (不分词那种) + * offsets:文档号、词频、关键词 term 的相对位置和该词的起始字符串偏移量 + +**关于 keyword 类型的常用参数:** + +* eager_global_ordinals:指明该字段是否加载全局序数?默认为 false,不加载。 对于经常用于术语聚合的字段,启用此功能是个好主意。 +* fields:指明能以不同的方式索引该字段相同的字符串值,例如用于搜索的一个字段和用于排序和聚合的多字段 +* ignore_above:不要索引长于此值的任何字符串。默认为 2147483647,以便接受所有值 +* index:指明该字段是否可以被搜索,默认为 true,表示可以被搜索 +* index_options:指定该字段应将哪些信息存储在索引中,以便用于评分。默认为 docs,但也可以设置为 freqs,这样可以在计算分数时考虑术语频率 +* norms:在进行查询评分时,是否需要考虑字段长度,默认为 false,不考虑 +* ignore_above:默认值是 256,该参数的意思是,当字段文本的长度大于指定值时,不会被索引,但是会存储。即当字段文本的长度大于指定值时,聚合、全文搜索都查不到这条数据。ignore_above 最大值是 32766 ,但是要根据场景来设置,比如说中文最大值 应该是设定在 10922 。 + +数字类型 + +ES 支持的数字类型有整型数字类型,integer 类型、long 类型、short 类型、byte 类型。浮点型数字类型 double 类型、 float 类型、 half_float 类型、 scaled_float 类型这类数据类型都是以确切值索引的,可以使用 term 查询精确匹配。数字类型的字段在满足需求的前提下应当尽量选择范围较小的数据类型,字段长度越短,搜索效率越高,对于浮点数,可以优先考虑使用 scaled_float 类型,该类型可以通过缩放因子来精确浮点数,例如 12.34 可以转换为 1234 来存储。 + +* long 带符号的 64 位整数,最小值 - 263,最大值 263-1 +* integer 带符号的 32 位整数,最小值 - 231,最大值 231^-1 +* short 带符号的 16 位整数,最小值 - 32768,最大值 32767 +* byte 带符号的 8 位整数,最小值 - 128,最小值 127 +* double 双精度 64 位 IEEE 754 浮点数 +* float 单精度 32 位 IEEE 754 浮点数 +* half_float 半精度 16 位 IEEE 754 浮点数 +* scaled_float 带有缩放因子的缩放类型浮点数,依靠一个 long 数字类型通过一个固定的 (double 类型) 缩放因数进行缩放 + +日期类型 + +在 ES 中日期可以为以下形式:格式化的日期字符串,例如 2020-03-17 00:00、2020/03/17 时间戳(和 1970-01-01 00:00:00 UTC 的差值),单位毫秒或者秒即使是格式化的日期字符串,ES 底层依然采用的是时间戳的形式存储。 + +日期类型一般会结合一个 mapping 参数来使用:format——自定义的日期格式,默认:strict_date_optional_time || epoch_millis。 + +```apl +PUT test_index +{ + "mappings": { + "properties": { + "date1": { + "type": "date" + }, + "date2": { + "type": "date", + "format": "yyyy-MM-dd HH:mm:ss" + }, + "date3": { + "type": "date", + "format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd||epoch_millis" + } + } + } +} +#添加2条数据 +POST test_index/_doc +{ + "dateTimeFormat": "2015-01-01 12:10:30" +} + + +PUT test_index/_doc/1 +{ + "date1": 1577808000000, + "date2": "2020-01-01 00:00:00", + "date3": "2020/01/01" +} +PUT test_index/_doc/2 +{ + "date1": 1577808000001, + "date2": "2020-01-01 00:00:01", + "date3": "2020/01/01" +} +#查询测试 +GET test_index/_search +{ + "sort": [ + { + "date2": { + "order": "desc" + } + } + ] +} +``` + +布尔类型 + +JSON 文档中同样存在布尔类型,不过 JSON 字符串类型也可以被 ES 转换为布尔类型存储,前提是字符串的取值为 true 或者 false,布尔类型常用于检索中的过滤条件。 + +二进制类型 + +二进制类型 binary 接受 BASE64 编码的字符串,默认 store 属性为 false,并且不可以被搜索。 + +范围类型 + +范围类型可以用来表达一个数据的区间,所以会用到 gt、gte、lt、lte… 等逻辑表示符。可以分为 6 种:integer_range、float_range、long_range、double_range、date_range 以及 ip_range。 + +* integer_range,带符号的 32 位整数区间,最小值 - 231,最大值 231-1 +* long_range,带符号的 64 位整数区间,最小值 - 263,最小值 263-1 +* float_range,单精度 32 位 IEEE 754 浮点数区间 +* double_range,双精度 64 位 IEEE 754 浮点数区间 +* date_range,日期值范围,表示为系统纪元以来经过的无符号 64 位整数毫秒 +* ip_range,支持 IPv4 或 IPv6(或混合)地址 ip 值范围 + +```apl +PUT test_index +{ + "mappings": { + "properties": { + "data1": { + "type": "integer_range" + }, + "data2": { + "type": "float_range" + }, + "data3": { + "type": "date_range", + "format": "yyyy-MM-dd HH:mm:ss" + }, + "data4": { + "type": "ip_range" + } + } + } +} +``` + +```apl +PUT test_index/_doc/1 +{ + "date1": { + "gte": 100, + "lte": 200 + }, + "date2": { + "gte": 21.21, + "lte": 22 + }, + "date3": { + "gte": "2020-01-01 00:00:00", + "lte": "2020-01-02 00:00:00" + }, + "date4": { + "gte": "192.168.192.10", + "lte": "192.168.192.11" + } +} +``` + +对象类型 + +对象类型即一个 JSON 对象,JSON 字符串允许嵌套对象,所以一个文档可以嵌套多个、多层对象。可以通过对象类型来存储二级文档,不过由于 Lucene 并没有内部对象的概念,所以 ES 会将原 JSON 文档扁平化,例如有下面这样的文档: + +```apl +PUT test_index +{ + "mappings": { + "properties": { + "user": { + "type": "object" + } + } + } +} + +PUT test_index/_doc/1 +{ + "username": { + "first": "zhang", + "last": "san" + } +} +``` + +上面我们看到都是一个个分开的字段,而实际上 ES 会将其转换为以下格式,并通过 Lucene 存储,即使 name 是 object 类型: + +```apl +{ + "username.first": "zhang", + "username.last": "san" +} +``` + +所以我们在进行条件搜索是也必须使用这种方式: + +```apl +GET test_index/_search +{ + "query": { + "bool": { + "must": [ + { + "match": { + "username.first": "zhang" + } + } + ] + } + } +} +``` + +嵌套类型 + +嵌套类型可以看成是一个特殊的对象类型,在文档属性是一个对象数组时使用,它允许对象数组彼此独立地编制索引和查询,例如文档: + +```apl +{ + "group": "users", + "username": [ + {"first":"zhang","last":"san"}, + {"first":"li","last":"si"}, + {"first":"wang","last":"wu"} + ] +} +``` + +username 字段是一个 JSON 数组,并且每个数组对象都是一个 JSON 对象。如果将 username 设置为对象类型,那么 ES 会将其转换为: + +```apl +{ + "group": "users", + "username.first": ["zhang","li","wang"], + "username.last": ["san","si","wu"] +} +``` + +可以看出转换后的 JSON 文档中 first 和 last 的关联丢失了,如果尝试搜索 first 为 li,last 为 wu 的文档,那么成功会检索出上述文档,但是 li 和 wu 在原 JSON 文档中并不属于同一个 JSON 对象,应当是不匹配的,即检索不出任何结果。而嵌套类型就是为了解决这种问题的,嵌套类型将数组中的每个 JSON 对象作为独立的隐藏文档来存储,每个嵌套的对象都能够独立地被搜索。 + +测试查询: + +```apl +DELETE test_index + +PUT test_index +{ + "mappings": { + "properties": { + "user": { + "type": "nested" + } + } + } +} + +PUT test_index/_doc/1 +{ + "username": [ + { + "first": "li", + "last": "si" + }, + { + "first": "wang", + "last": "wu" + } + ] +} + +#查询测试 +GET test_index/_search +{ + "query": { + "bool": { + "must": [ + { + "match": { + "username.first": "li" + } + }, + { + "match": { + "username.last": "wu" + } + } + ] + } + } +} +``` + +[回到顶部](#_labelTop) + +3、Mapping 的主要参数 +--------------- + +### 3.1、mapping 组成 + +一个 mapping 主要有两部分组成:metadata 和 mapping: + +* metadata 元数据字段用于自定义如何处理文档关联的元数据。例如: + * _index:用于定义 document 属于哪个 index + * _type:类型,已经移除的概念 + * _id:document 的唯一 id + * _source:存放原始的 document 数据 + * _size:_source 字段中存放的数据的大小 +* mapping 中包含的 field,包含字段的类型和参数。本文主要介绍的 mapping 参数就需要在 field 中去定义。例如: + * type:设置字段对应的类型,常见的有 text,keyword 等 + * analyzer:指定一个用来文本分析的索引或者搜索 text 字段的分析器 应用于索引以及查询 + +### 3.2、mapping 参数 + +> 字段映射参数详细说明,更详细的请参考官方文档:[Mapping parameters | Elasticsearch Guide [7.17] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-params.html) + +主要参数如下: + +* **analyzer:**只能用于 text 字段,用于根据需求设置不通的分词器,默认是 ES 的标准分词 +* **boost:**默认值为 1。用于设置字段的权重,主要应用于查询时候的评分 +* **coerce:**默认是 true。主要用于清理脏数据来匹配字段对应的类型。例如字符串 “5” 会被强制转换为整数,浮点数 5.0 会被强制转换为整数 +* **copy_to**:能够把几个字段拼成一个字段。老字段和新组成的字段都可以查询 +* **doc_values:**默认值为 true。Doc Values 和倒排索引同时生成,本质上是一个序列化的 列式存储。列式存储适用于聚合、排序、脚本等操作,也很适合做压缩。如果字段不需要聚合、排序、脚本等操作可以关闭掉,能节省磁盘空间和提升索引速度。 +* **dynamic:**默认值为 true。默认如果插入的 document 字段中有 mapping 没有的,会自动插入成功,并自动设置新字段的类型;如果一个字段中插入一个包含多个字段的 json 对象也会插入成功。但是这个逻辑可以做限制: + * ture: 默认值,可以动态插入 + * false:数据可写入但是不能被索引分析和查询,但是会保存到_source 字段。 + * strict:无法写入 +* **eager_global_ordinals:**默认值为 false。设置每 refresh 一次就创建一个全局的顺序映射,用于预加载来加快查询的速度。需要消耗一定的 heap。 +* **enabled:**默认值为 true。设置字段是否索引分析。如果设置为 false,字段不对此字段索引分析和 store,会导致此字段不能被查询和聚合,但是字段内容仍然会存储到_source 中。 +* **fielddata:**默认值为 false,只作用于 text 字段。默认 text 字段不能排序,聚合和脚本操作,可以通过开启此参数打开此功能。但是会消耗比较大的内存。 +* **fields:**可以对一个字段设置多种索引类型,例如 text 类型用来做全文检索,再加一个 keyword 来用于做聚合和排序。 +* **format:**用于 date 类型。设置时间的格式。具体见 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html +* **ignore_above:**默认值为 256,作用于 keyword 类型。指示该字段的最大索引长度(即超过该长度的内容将不会被索引分析),对于超过 ignore_above 长度的字符串,analyzer 不会进行索引分析,所以超过该长度的内容将不会被搜索到。注意:keyword 类型的字段的最大长度限制为 32766 个 UTF-8 字符,text 类型的字段对字符长度没有限制 +* **ignore_malformed:**默认为 false。插入新 document 的时候,是否忽略字段的类型,默认字段类型必须和 mapping 中设置的一样 +* **index_options:**默认值为 positions,只作用于 text 字段。控制将哪些信息添加到倒排索引中以进行搜索和突出显示。有 4 个选项: + * docs 添加文档号 + * freqs 添加文档号和次频 + * positions 添加文档号,词频,位置 + * offsets 添加文档号,词频,位置,偏移量 +* **index:**默认值为 true。设置字段是否会被索引分析和可以查询 +* **meta:**可以给字段设置 metedata 字段,用于标记等 +* **normalizer:**可以对字段做一些标准化规则,例如字符全部大小写等 +* **norms:**默认值为 true。默认会存储了各种规范化因子,在查询的时候使用这些因子来计算文档相对于查询的得分,会占用一部分磁盘空间。如果字段不用于检索,只是过滤,查询等精确操作可以关闭。 +* **null_value:**null_value 意味着无法索引或搜索空值。当字段设置为 null , [] , 和 [null](这些 null 的表示形式都是等价的),它被视为该字段没有值。通过设置此字段,可以设置控制可以被索引和搜索。 +* **properties:**如果这个字段有嵌套属性,包含了多个子字段。需要用到 properties +* **search_analyzer:**默认值和 analyzer 相同。在查询时,先对要查询的 text 类型的输入做分词,再去倒排索引搜索,可以通过这个设置查询的分析器为其它的,默认情况下,查询将使用 analyzer 字段制定的分析器,但也可以被 search_analyzer 覆盖 +* **similarity:**用于设置 document 的评分模型,有三个: + * BM25:lucene 的默认评分模型 + * classic:TF/IDF 评分模型 + * boolean: 布尔评分模型 +* **store:**默认为 false,lucene 不存储原始内容,但是_source 仍然会存储。这个属性其实是 lucene 创建字段时候的一个选项,表明是否要单独存储原始值(_source 字段是 elasticsearch 单独加的和 store 没有关系)。如果字段比较长,从_source 中获取损耗比较大,可以关闭_source 存储,开启 store。 +* **term_vector:** 用于存储术语的规则。默认值为 no,不存储向量信息. \ No newline at end of file diff --git a/elk/elasticsearch/Java客户端RestClient基础操作.md b/elk/elasticsearch/Java客户端RestClient基础操作.md new file mode 100644 index 0000000..5cc060f --- /dev/null +++ b/elk/elasticsearch/Java客户端RestClient基础操作.md @@ -0,0 +1,341 @@ +# Java客户端RestClient基础操作.md + +## Maven 依赖 + +```xml +//SpringBoot项目在properties中添加上版本号就能生效 + + 7.12.1 + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + +``` + +## 索引基础操作 + +```java +/** + * @author ds + * @since 2023/1/3 + */ +public class HotelIndexTest { + private RestHighLevelClient client; + + //创建客户端连接 + @BeforeEach + void setUp(){ + client=new RestHighLevelClient(RestClient.builder( + HttpHost.create("http://10.0.0.101:9200") + )); + } + + //创建索引库 + @Test + void createIndexTest() throws IOException { + //创建Request对象 + CreateIndexRequest request = new CreateIndexRequest("hotel"); + + //DSL语句 + request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON); + + //发送请求 + CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT); + + System.err.println("create:"+createIndexResponse.isAcknowledged()); + + } + //查询索引库(索引库不存在会抛异常) + @Test + void getIndexTest() throws IOException { + //创建GetIndexRequest请求 + GetIndexRequest request = new GetIndexRequest("hotel"); + //执行请求 + GetIndexResponse hotelIndex = client.indices().get(request, RequestOptions.DEFAULT); + //打印相应信息 + System.err.println("get:"+JSON.toJSONString(hotelIndex.getMappings())); + } + + //删除索引库(索引库不存在会抛异常) + @Test + void deleteIndexTest() throws IOException { + //创建DeleteIndexRequest请求 + DeleteIndexRequest deleteRequest = new DeleteIndexRequest("hotel"); + //执行请求 + AcknowledgedResponse delete = client.indices().delete(deleteRequest, RequestOptions.DEFAULT); + System.err.println("delete:"+delete.isAcknowledged()); + } + + //判断索引库是否存在 + @Test + void existsIndexTest() throws IOException { + //创建GetIndexRequest请求 + GetIndexRequest request = new GetIndexRequest("hotel"); + //执行请求 + boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); + System.err.println(exists?"存在":"不存在"); + } + + //更新索引(索引库不存在会抛异常) + @Test + void updateIndexTest() throws IOException { + //创建请求 + PutMappingRequest request = new PutMappingRequest("hotel"); + //构建DSL + request.source(HotelConstants.MAPPING_UP, XContentType.JSON); + //执行请求 + AcknowledgedResponse acknowledgedResponse = client.indices().putMapping(request, RequestOptions.DEFAULT); + System.err.println(JSON.toJSONString(acknowledgedResponse)); + } + + //关闭客户端 + @AfterEach + void tearDown() throws IOException { + client.close(); + } +} + +/** + * @author ds + * @since 2023/1/3 + */ +public class HotelConstants { + //正常JSON格式见文档末尾 + public static final String MAPPING_UP="{\n" + + " \"properties\":{\n" + + " \"name2\":{\n" + + " \"type\":\"keyword\"\n" + + " }\n" + + " }\n" + + "}"; + public static final String MAPPING_TEMPLATE="{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"id\": {\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"name\":{\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"copy_to\": \"all\"\n" + + " },\n" + + " \"address\":{\n" + + " \"type\": \"keyword\",\n" + + " \"index\": false\n" + + " },\n" + + " \"price\":{\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"score\":{\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"brand\":{\n" + + " \"type\": \"keyword\",\n" + + " \"copy_to\": \"all\"\n" + + " },\n" + + " \"city\":{\n" + + " \"type\": \"keyword\",\n" + + " \"copy_to\": \"all\"\n" + + " },\n" + + " \"starName\":{\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"business\":{\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"location\":{\n" + + " \"type\": \"geo_point\"\n" + + " },\n" + + " \"pic\":{\n" + + " \"type\": \"keyword\",\n" + + " \"index\": false\n" + + " },\n" + + " \"all\":{\n" + + " \"type\": \"text\",\n" + + " \"analyzer\": \"ik_max_word\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; +} +``` + +## DOC基础操作 + +```java +/** + * @author ds + * @since 2023/1/3 + */ +@Slf4j +@SpringBootTest +public class HotelDocTest { + + @Autowired + private IHotelService iHotelService; + private RestHighLevelClient client; + + //创建连接 + @BeforeEach + void setUp(){ + client=new RestHighLevelClient(RestClient.builder( + HttpHost.create("http://10.0.0.101:9200") + )); + } + + //创建doc + @Test + void createDocTest() throws IOException { + //查询mysql中的数据 + Hotel hotel = iHotelService.getById(38812); + //转换为doc对象 + HotelDoc hotelDoc = new HotelDoc(hotel); + //创建求情 + IndexRequest indexRequest = new IndexRequest("hotel").id(hotel.getId().toString()); + //设置请求数据 + indexRequest.source(JSON.toJSONString(hotelDoc),XContentType.JSON); + //执行请求 + IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT); + log.info("create:"+JSON.toJSONString(index.getResult())); + } + + //查询doc + @Test + void getDocTest() throws IOException { + //创建请求 + GetRequest request = new GetRequest("hotel").id("38812"); + //执行请求 + GetResponse response = client.get(request, RequestOptions.DEFAULT); + log.info("get:"+JSON.toJSONString(response)); + } + + //删除doc + @Test + void deleteDocTest() throws IOException { + //创建请求 + DeleteRequest request = new DeleteRequest("hotel").id("38812"); + //执行请求 + DeleteResponse response = client.delete(request, RequestOptions.DEFAULT); + log.info("get:"+JSON.toJSONString(response)); + } + + //查询doc是否存在 + @Test + void existsDocTest() throws IOException { + //创建请求 + GetRequest request = new GetRequest("hotel","38812"); + //执行请求 + boolean exists = client.exists(request, RequestOptions.DEFAULT); + log.info(exists?"存在":"不存在"); + } + + //更新doc + @Test + void updateDocTest() throws IOException { + //创建请求 + UpdateRequest request = new UpdateRequest("hotel","38812"); + //设置请求数据 + request.doc( + "name","大聪明酒店" + ); + //有则更新,没有则新建 + request.docAsUpsert(true); + //执行请求 + UpdateResponse update = client.update(request, RequestOptions.DEFAULT); + log.info("update:"+JSON.toJSONString(update)); + } + + //批量测试 + @Test + void batchCreateDoc() throws IOException { + // 批量查询酒店数据 + List list = iHotelService.list(); + //创建求情 + BulkRequest bulkRequest = new BulkRequest("hotel"); + //构建请求数据 + for (Hotel hotel : list) { + //DeleteRequest\GetRequest\UpdateRequest都可添加 + IndexRequest indexRequest = new IndexRequest().id(hotel.getId().toString()); + indexRequest.source(JSON.toJSONString(new HotelDoc(hotel)),XContentType.JSON); + bulkRequest.add(indexRequest); + } + //执行请求 + BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT); + log.info("bulk:"+JSON.toJSONString(bulk.getItems())); + } + //关闭客户端连接 + @AfterEach + void tearDown() throws IOException { + client.close(); + } +} +``` + +HotelConstants-JSON + +```apl +#MAPPING_UP +PUT /hotel +{ + "properties": { + "name2":{ + "type": "text", + "analyzer": "ik_max_word" + } + } +} +#MAPPING_TEMPLATE +PUT /hotel +{ + "mappings": { + "properties": { + "id": { + "type": "keyword" + }, + "name":{ + "type": "text", + "analyzer": "ik_max_word", + "copy_to": "all" + }, + "address":{ + "type": "keyword", + "index": false + }, + "price":{ + "type": "integer" + }, + "score":{ + "type": "integer" + }, + "brand":{ + "type": "keyword", + "copy_to": "all" + }, + "city":{ + "type": "keyword", + "copy_to": "all" + }, + "starName":{ + "type": "keyword" + }, + "business":{ + "type": "keyword" + }, + "location":{ + "type": "geo_point" + }, + "pic":{ + "type": "keyword", + "index": false + }, + "all":{ + "type": "text", + "analyzer": "ik_max_word" + } + } + } +} +``` + diff --git a/elk/elasticsearch/assets/image-20201115230147799.png b/elk/elasticsearch/assets/image-20201115230147799.png new file mode 100644 index 0000000..1b937eb Binary files /dev/null and b/elk/elasticsearch/assets/image-20201115230147799.png differ diff --git a/elk/elasticsearch/assets/image-20201115230900504.png b/elk/elasticsearch/assets/image-20201115230900504.png new file mode 100644 index 0000000..e9d6b12 Binary files /dev/null and b/elk/elasticsearch/assets/image-20201115230900504.png differ diff --git a/elk/elasticsearch/assets/image-20210109105135812.png b/elk/elasticsearch/assets/image-20210109105135812.png new file mode 100644 index 0000000..5252541 Binary files /dev/null and b/elk/elasticsearch/assets/image-20210109105135812.png differ diff --git a/elk/elasticsearch/assets/image-20210506101053676.png b/elk/elasticsearch/assets/image-20210506101053676.png new file mode 100644 index 0000000..1ee49aa Binary files /dev/null and b/elk/elasticsearch/assets/image-20210506101053676.png differ diff --git a/elk/elasticsearch/assets/image-20210506102630393.png b/elk/elasticsearch/assets/image-20210506102630393.png new file mode 100644 index 0000000..081e934 Binary files /dev/null and b/elk/elasticsearch/assets/image-20210506102630393.png differ diff --git a/elk/elasticsearch/assets/image-20210506110249144.png b/elk/elasticsearch/assets/image-20210506110249144.png new file mode 100644 index 0000000..03baa1c Binary files /dev/null and b/elk/elasticsearch/assets/image-20210506110249144.png differ diff --git a/elk/elasticsearch/assets/image-20210506110704293.png b/elk/elasticsearch/assets/image-20210506110704293.png new file mode 100644 index 0000000..d547dd2 Binary files /dev/null and b/elk/elasticsearch/assets/image-20210506110704293.png differ diff --git a/elk/elasticsearch/assets/image-20210506112225508.png b/elk/elasticsearch/assets/image-20210506112225508.png new file mode 100644 index 0000000..f5fc895 Binary files /dev/null and b/elk/elasticsearch/assets/image-20210506112225508.png differ diff --git a/elk/elasticsearch/assets/image-20210510165308064.png b/elk/elasticsearch/assets/image-20210510165308064.png new file mode 100644 index 0000000..3b3ea27 Binary files /dev/null and b/elk/elasticsearch/assets/image-20210510165308064.png differ diff --git a/elk/elasticsearch/基础ES-DSL语句.md b/elk/elasticsearch/基础ES-DSL语句.md new file mode 100644 index 0000000..1e2454a --- /dev/null +++ b/elk/elasticsearch/基础ES-DSL语句.md @@ -0,0 +1,228 @@ +# 基础索引库、文档ES-DSL语句 + +## 基础概念 + +我们把mysql与elasticsearch的概念做一下对比: + +| **MySQL** | **Elasticsearch** | **说明** | +| --------- | ----------------- | ------------------------------------------------------------ | +| Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) | +| Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 | +| Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) | +| Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) | +| SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD | + +是不是说,我们学习了elasticsearch就不再需要mysql了呢? + +并不是如此,两者各自有自己的擅长支出: + +- Mysql:擅长事务类型操作,可以确保数据的安全和一致性 + +- Elasticsearch:擅长海量数据的搜索、分析、计算 + +因此在企业中,往往是两者结合使用: + +- 对安全性要求较高的写操作,使用mysql实现 +- 对查询性能要求较高的搜索需求,使用elasticsearch实现 +- 两者再基于某种方式,实现数据的同步,保证一致性 + +## 基础语法 + +### 索引基础语法 + +- 创建索引库:PUT /索引库名 +- 查询索引库:GET /索引库名 +- 删除索引库:DELETE /索引库名 +- 添加字段: + - PUT /索引库名/_mapping + - POST /索引库名/_mapping + +### 文档基础语法 + +- 创建文档:POST /{索引库名}/_doc/文档id { json文档 } +- 查询文档:GET /{索引库名}/_doc/文档id +- 删除文档:DELETE /{索引库名}/_doc/文档id +- 修改文档: + - 全量修改 (如果索引库中不存在则新增):PUT /{索引库名}/_doc/文档id { json文档 } + - 局部修改(如果索引库中不存在则修改失败):POST /{索引库名}/_update/文档id { "doc": {字段}} + +## 示例 + +```apl +#分词器 +#默认分词器 +GET _analyze +{ + "analyzer": "standard", + "text": "hello world" +} + +#ik_max_word ik分词器-细粒度 +GET _analyze +{ + "analyzer": "ik_max_word", + "text": "我是云南的嘤嘤怪,你是大傻逼还是大聪明" +} +#ik_smart ik分词器-粗粒度 +GET _analyze +{ + "analyzer": "ik_smart", + "text": "我是云南的" +} +#ik_max_word +GET _analyze +{ + "analyzer": "ik_max_word", + "text": "我是云南的嘤嘤怪,你是大傻逼还是大聪明" +} + +# 索引库操作 +#新增索引 +PUT /heima +{ + "mappings": { + "properties": { + "info":{ + "type": "text", + "analyzer": "ik_smart" + }, + "email":{ + "type": "keyword", + "index": false + }, + "name":{ + "properties": { + "firstName":{ + "type":"keyword" + }, + "lastName":{ + "type":"keyword" + } + } + } + } + } +} + +#查询索引 +GET /heima + +#修改索引(为索引新增加字段) +POST /heima/_mapping +{ + "properties":{ + "name2":{ + "type":"keyword" + } + } +} +#修改索引(为索引新增加字段) +PUT /heima/_mapping +{ + "properties":{ + "name3":{ + "type":"keyword" + } + } +} +#删除索引 +DELETE /heima + +#文档操作 +#新增文档 +POST /heima/_doc/1 +{ + "info":"大聪明JAVA程序员", + "email":"2111319672@qq.com", + "name":{ + "firstName":"代", + "lastName":"帅" + }, + "name2":"大聪明" +} + + +#查询文档 +GET /heima/_doc/1 + +#删除文档 +DELETE /heima/_doc/1 + +#全量修改 +PUT /heima/_doc/1 +{ + "info":"大聪明JAVA程序员", + "email":"2111319672@qq.com", + "name":{ + "firstName":"代", + "lastName":"帅" + }, + "name2":"大聪明" +} + +#局部修改 +POST /heima/_update/1 +{ + "doc": { + "email":"dcm@qq.com", + "name":{ + "firstName":"大", + "lastName":"聪明" + } + } +} +#常见字段演示 +# location:地理坐标,里面包含精度、纬度 +# all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索 +PUT /hotel +{ + "mappings": { + "properties": { + "id": { + "type": "keyword" + }, + "name":{ + "type": "text", + "analyzer": "ik_max_word", + "copy_to": "all" + }, + "address":{ + "type": "keyword", + "index": false + }, + "price":{ + "type": "integer" + }, + "score":{ + "type": "integer" + }, + "brand":{ + "type": "keyword", + "copy_to": "all" + }, + "city":{ + "type": "keyword", + "copy_to": "all" + }, + "starName":{ + "type": "keyword" + }, + "business":{ + "type": "keyword" + }, + "location":{ + "type": "geo_point" + }, + "pic":{ + "type": "keyword", + "index": false + }, + "all":{ + "type": "text", + "analyzer": "ik_max_word" + } + } + } +} +``` + diff --git a/elk/elasticsearch/安装elasticsearch.md b/elk/elasticsearch/安装elasticsearch.md new file mode 100644 index 0000000..4acc614 --- /dev/null +++ b/elk/elasticsearch/安装elasticsearch.md @@ -0,0 +1,487 @@ +# 安装elasticsearch + + + +# 1.部署单点es + +## 1.1.创建网络 + +因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络: + +```sh +docker network create es-net +``` + +## 1.2.加载镜像 + +百度云链接:https://pan.baidu.com/s/1k9IoWP7rB7FHWHUDSzsh5Q?pwd=2022 +提取码:2022 +--来自百度网盘超级会员V1的分享 + +这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。 + +课前资料提供了镜像的tar包: + +![image-20210510165308064](assets/image-20210510165308064.png) + +大家将其上传到虚拟机中,然后运行命令加载即可: + +```sh +# 导入数据 +docker load -i es.tar +``` + +同理还有`kibana`的tar包也需要这样做。 + + + +## 1.3.运行 + +运行docker命令,部署单点es: + +```sh +docker run -d \ + --name es \ + -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ + -e "discovery.type=single-node" \ + -v es-data:/usr/share/elasticsearch/data \ + -v es-plugins:/usr/share/elasticsearch/plugins \ + --privileged \ + --network es-net \ + -p 9200:9200 \ + -p 9300:9300 \ +elasticsearch:7.12.1 +``` + +命令解释: + +- `-e "cluster.name=es-docker-cluster"`:设置集群名称 +- `-e "http.host=0.0.0.0"`:监听的地址,可以外网访问 +- `-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"`:内存大小 +- `-e "discovery.type=single-node"`:非集群模式 +- `-v es-data:/usr/share/elasticsearch/data`:挂载逻辑卷,绑定es的数据目录 +- `-v es-logs:/usr/share/elasticsearch/logs`:挂载逻辑卷,绑定es的日志目录 +- `-v es-plugins:/usr/share/elasticsearch/plugins`:挂载逻辑卷,绑定es的插件目录 +- `--privileged`:授予逻辑卷访问权 +- `--network es-net` :加入一个名为es-net的网络中 +- `-p 9200:9200`:端口映射配置 + + + +在浏览器中输入:http://192.168.150.101:9200 即可看到elasticsearch的响应结果: + +![image-20210506101053676](assets/image-20210506101053676.png) + + + + + +# 2.部署kibana + +kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。 + +## 2.1.部署 + +运行docker命令,部署kibana + +```sh +docker run -d \ +--name kibana \ +-e ELASTICSEARCH_HOSTS=http://es:9200 \ +--network es-net \ +-p 5601:5601 \ +kibana:7.12.1 +``` + +- `--network es-net` :加入一个名为es-net的网络中,与elasticsearch在同一个网络中 +- `-e ELASTICSEARCH_HOSTS=http://es:9200"`:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch +- `-p 5601:5601`:端口映射配置 + +kibana启动一般比较慢,需要多等待一会,可以通过命令: + +```sh +docker logs -f kibana +``` + +查看运行日志,当查看到下面的日志,说明成功: + +![image-20210109105135812](assets/image-20210109105135812.png) + +此时,在浏览器输入地址访问:http://192.168.150.101:5601,即可看到结果 + +## 2.2.DevTools + +kibana中提供了一个DevTools界面: + +![image-20210506102630393](assets/image-20210506102630393.png) + +这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。 + + + +# 3.安装IK分词器 + + + +## 3.1.在线安装ik插件(较慢) + +```shell +# 进入容器内部 +docker exec -it elasticsearch /bin/bash + +# 在线下载并安装 +./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip + +#退出 +exit +#重启容器 +docker restart elasticsearch +``` + +## 3.2.离线安装ik插件(推荐) + +### 1)查看数据卷目录 + +安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看: + +```sh +docker volume inspect es-plugins +``` + +显示结果: + +```json +[ + { + "CreatedAt": "2022-05-06T10:06:34+08:00", + "Driver": "local", + "Labels": null, + "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data", + "Name": "es-plugins", + "Options": null, + "Scope": "local" + } +] +``` + +说明plugins目录被挂载到了:`/var/lib/docker/volumes/es-plugins/_data `这个目录中。 + + + +### 2)解压缩分词器安装包 + +下面我们需要把课前资料中的ik分词器解压缩,重命名为ik + +![image-20210506110249144](assets/image-20210506110249144.png) + +### 3)上传到es容器的插件数据卷中 + +也就是`/var/lib/docker/volumes/es-plugins/_data `: + +![image-20210506110704293](assets/image-20210506110704293.png) + + + +### 4)重启容器 + +```shell +# 4、重启容器 +docker restart es +``` + +```sh +# 查看es日志 +docker logs -f es +``` + +### 5)测试: + +IK分词器包含两种模式: + +* `ik_smart`:最少切分 + +* `ik_max_word`:最细切分 + + + +```json +GET /_analyze +{ + "analyzer": "ik_max_word", + "text": "黑马程序员学习java太棒了" +} +``` + +结果: + +```json +{ + "tokens" : [ + { + "token" : "黑马", + "start_offset" : 0, + "end_offset" : 2, + "type" : "CN_WORD", + "position" : 0 + }, + { + "token" : "程序员", + "start_offset" : 2, + "end_offset" : 5, + "type" : "CN_WORD", + "position" : 1 + }, + { + "token" : "程序", + "start_offset" : 2, + "end_offset" : 4, + "type" : "CN_WORD", + "position" : 2 + }, + { + "token" : "员", + "start_offset" : 4, + "end_offset" : 5, + "type" : "CN_CHAR", + "position" : 3 + }, + { + "token" : "学习", + "start_offset" : 5, + "end_offset" : 7, + "type" : "CN_WORD", + "position" : 4 + }, + { + "token" : "java", + "start_offset" : 7, + "end_offset" : 11, + "type" : "ENGLISH", + "position" : 5 + }, + { + "token" : "太棒了", + "start_offset" : 11, + "end_offset" : 14, + "type" : "CN_WORD", + "position" : 6 + }, + { + "token" : "太棒", + "start_offset" : 11, + "end_offset" : 13, + "type" : "CN_WORD", + "position" : 7 + }, + { + "token" : "了", + "start_offset" : 13, + "end_offset" : 14, + "type" : "CN_CHAR", + "position" : 8 + } + ] +} +``` + + + + + +## 3.3 扩展词词典 + +随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“传智播客” 等。 + +所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。 + +1)打开IK分词器config目录: + +![image-20210506112225508](assets/image-20210506112225508.png) + +2)在IKAnalyzer.cfg.xml配置文件内容添加: + +```xml + + + + IK Analyzer 扩展配置 + + ext.dic + +``` + +3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改 + +```properties +传智播客 +奥力给 +``` + +4)重启elasticsearch + +```sh +docker restart es + +# 查看 日志 +docker logs -f elasticsearch +``` + +![image-20201115230900504](assets/image-20201115230900504.png) + +日志中已经成功加载ext.dic配置文件 + +5)测试效果: + +```json +GET /_analyze +{ + "analyzer": "ik_max_word", + "text": "传智播客Java就业超过90%,奥力给!" +} +``` + +> 注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑 + +## 3.4 停用词词典 + +在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。 + +IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。 + +1)IKAnalyzer.cfg.xml配置文件内容添加: + +```xml + + + + IK Analyzer 扩展配置 + + ext.dic + + stopword.dic + +``` + +3)在 stopword.dic 添加停用词 + +```properties +习大大 +``` + +4)重启elasticsearch + +```sh +# 重启服务 +docker restart elasticsearch +docker restart kibana + +# 查看 日志 +docker logs -f elasticsearch +``` + +日志中已经成功加载stopword.dic配置文件 + +5)测试效果: + +```json +GET /_analyze +{ + "analyzer": "ik_max_word", + "text": "传智播客Java就业率超过95%,习大大都点赞,奥力给!" +} +``` + +> 注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑 + + + + + +# 4.部署es集群 + +部署es集群可以直接使用docker-compose来完成,不过要求你的Linux虚拟机至少有**4G**的内存空间 + + + +首先编写一个docker-compose文件,内容如下: + +```sh +version: '2.2' +services: + es01: + image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1 + container_name: es01 + environment: + - node.name=es01 + - cluster.name=es-docker-cluster + - discovery.seed_hosts=es02,es03 + - cluster.initial_master_nodes=es01,es02,es03 + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - data01:/usr/share/elasticsearch/data + ports: + - 9200:9200 + networks: + - elastic + es02: + image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1 + container_name: es02 + environment: + - node.name=es02 + - cluster.name=es-docker-cluster + - discovery.seed_hosts=es01,es03 + - cluster.initial_master_nodes=es01,es02,es03 + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - data02:/usr/share/elasticsearch/data + networks: + - elastic + es03: + image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1 + container_name: es03 + environment: + - node.name=es03 + - cluster.name=es-docker-cluster + - discovery.seed_hosts=es01,es02 + - cluster.initial_master_nodes=es01,es02,es03 + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - data03:/usr/share/elasticsearch/data + networks: + - elastic + +volumes: + data01: + driver: local + data02: + driver: local + data03: + driver: local + +networks: + elastic: + driver: bridge +``` + + + +Run `docker-compose` to bring up the cluster: + +```sh +docker-compose up +``` \ No newline at end of file