1 索引Index入门

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

  • 在生产上,我们需要自己手动建立索引和映射,是为了更好的管理索引,就像数据库的建表数据一样。

1.2 索引管理

1.2.1 创建索引

  • 创建索引的语法:
  1. PUT /index
  2. {
  3. "settings":{...},
  4. "mappings":{
  5. "properties":{
  6. ...
  7. }
  8. },
  9. "aliases":{
  10. "default_index":{}
  11. }
  12. }
  • 示例:
  • 创建索引:
  1. PUT /my_index
  2. {
  3. "settings": {
  4. "number_of_shards": 1,
  5. "number_of_replicas": 1
  6. },
  7. "mappings": {
  8. "properties": {
  9. "field1":{
  10. "type": "text"
  11. },
  12. "field2":{
  13. "type": "text"
  14. }
  15. }
  16. },
  17. "aliases": {
  18. "default_index": {}
  19. }
  20. }
  • 插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "field1":"java",
  4. "field2":"js"
  5. }
  • 查询数据:
  1. GET /my_index/_doc/1
  1. GET /default_index/_doc/1

1.2.2 查询索引

  • 语法:查询索引:
  1. GET /index
  • 语法:查询索引中的映射信息
  1. GET /index/_mapping
  • 语法:查询索引中的设置信息
  1. GET /index/_settings
  • 示例:
  1. GET /my_index
  1. GET /my_index/_mapping
  1. GET /my_index/_settings

1.2.3 修改索引

  • 语法:
  1. PUT /index/_settings
  2. {
  3. "index" : {
  4. "number_of_replicas" : 5
  5. }
  6. }
  • 示例:修改副本分片数
  1. PUT /my_index/_settings
  2. {
  3. "index" : {
  4. "number_of_replicas" : 5
  5. }
  6. }

1.2.4 删除索引

  • 语法:删除索引
  1. DELETE /index
  • 语法:删除多个索引
  1. DELETE /index1,index2
  • 语法:删除多个索引
  1. DELETE /index*
  • 语法:删除全部索引(危险)
  1. DELETE /_all
  • 示例:删除索引
  1. DELETE /book
  • 示例:删除多个索引
  1. DELETE /book,my_index
  • 示例:
  1. DELETE /book*
  • 示例:删除所有索引
  1. DELETE /_all

为了安全起见,防止恶意删除索引,删除的时候必须指定索引名,我们可以在elasticsearch.yml中配置action.destructive_requires_name: true。

1.3 定制分词器

1.3.1 默认分词器

  • 分词器有三个组件,分别为:character filter,tokenizer和token filter。
  • 默认的分词器是:standard 分词器。
  • standard tokenizer:以单词边界进行切分。
  • standard token filter:什么都不做。
  • lowercase token filter:将所有字母都转换为小写。
  • stop token filter(默认被禁用):移除停用词,比如a、the、it等等。

1.3.2 修改分词器的位置

  • 启用english停用词token filter:
  1. PUT /my_index
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": {
  6. "es_std":{
  7. "type":"standard",
  8. "stopwords":"_english_"
  9. }
  10. }
  11. }
  12. }
  13. }
  • 使用标准分词器测试分词:
  1. GET /my_index/_analyze
  2. {
  3. "analyzer": "standard",
  4. "text": "a dog is in the house"
  5. }
  • 使用english停用词测试分词:
  1. GET /my_index/_analyze
  2. {
  3. "analyzer": "es_std",
  4. "text":"a dog is in the house"
  5. }

1.3.3 定制自己的分词器

  • 定制自己的分词器:
  1. PUT /my_index
  2. {
  3. "settings": {
  4. "analysis": {
  5. "char_filter": {
  6. "&_to_and": {
  7. "type": "mapping",
  8. "mappings": ["&=> and"]
  9. }
  10. },
  11. "filter": {
  12. "my_stopwords": {
  13. "type": "stop",
  14. "stopwords": ["the", "a"]
  15. }
  16. },
  17. "analyzer": {
  18. "my_analyzer": {
  19. "type": "custom",
  20. "char_filter": ["html_strip", "&_to_and"],
  21. "tokenizer": "standard",
  22. "filter": ["lowercase", "my_stopwords"]
  23. }
  24. }
  25. }
  26. }
  27. }
  • 测试自定义分词器:
  1. GET /my_index/_analyze
  2. {
  3. "analyzer": "my_analyzer",
  4. "text": "tom&jerry are a friend in the house, <a>, HAHA!!"
  5. }
  • 设置字段使用自定义分词器:
  1. PUT /my_index/_mapping/
  2. {
  3. "properties": {
  4. "content": {
  5. "type": "text",
  6. "analyzer": "my_analyzer"
  7. }
  8. }
  9. }

1.4 type底层结构及弃用原因

1.4.1 type是什么?

  • type是一个Index中用来区分类似的数据的。这些类似的数据,可能有不同的fields,而且有不同的属性来控制索引和分词器的建立。
  • field的value,在底层的Lucene中建立索引的时候,全都是opaque bytes类型,是不区分类型的。
  • Lucene是没有type的概念的,在document(文档)中,实际上是将type作为document(文档)的field来存储的,即_type,ES通过_type来进行type的过滤和筛选。

1.4.2 ES中不同的type存储机制

  • 一个Index中的多个type,实际上是放在一起存储的,因此同一个Index下,不能有多个type重名,因为那样是无法处理的。
  • 比如:创建索引:
  1. {
  2. "goods": {
  3. "mappings": {
  4. "electronic_goods": {
  5. "properties": {
  6. "name": {
  7. "type": "string",
  8. },
  9. "price": {
  10. "type": "double"
  11. },
  12. "service_period": {
  13. "type": "string"
  14. }
  15. }
  16. },
  17. "fresh_goods": {
  18. "properties": {
  19. "name": {
  20. "type": "string",
  21. },
  22. "price": {
  23. "type": "double"
  24. },
  25. "eat_period": {
  26. "type": "string"
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }
  • 插入数据:
  1. PUT /goods/electronic_goods/1
  2. {
  3. "name": "小米空调",
  4. "price": 1999.0,
  5. "service_period": "one year"
  6. }
  1. PUT /goods/fresh_goods/1
  2. {
  3. "name": "澳洲龙虾",
  4. "price": 199.0,
  5. "eat_period": "one week"
  6. }
  • ES文档在底层的存储是这个样子的:
  1. {
  2. "goods": {
  3. "mappings": {
  4. "_type": {
  5. "type": "text",
  6. "index": "false"
  7. },
  8. "name": {
  9. "type": "text"
  10. }
  11. "price": {
  12. "type": "double"
  13. }
  14. "service_period": {
  15. "type": "text"
  16. },
  17. "eat_period": {
  18. "type": "text"
  19. }
  20. }
  21. }
  22. }
  • 那么插入的数据在底层的数据存储格式:
  1. {
  2. "_type": "electronic_goods",
  3. "name": "小米空调",
  4. "price": 1999.0,
  5. "service_period": "one year",
  6. "eat_period": ""
  7. }
  1. {
  2. "_type": "fresh_goods",
  3. "name": "澳洲龙虾",
  4. "price": 199.0,
  5. "service_period": "",
  6. "eat_period": "one week"
  7. }

1.4.3 type弃用的原因

  • 同一个索引下,不同type的数据存储其他type的field的大量空值,会造成资源浪费。
  • 所以,不同类型的数据,要放在不同的索引中。
  • ES9中,将彻底删除type。

1.5 定制动态映射(dynamic mapping)

1.5.1 定制动态映射(dynamic mapping)策略

  • 语法:
  1. PUT /index
  2. {
  3. "settings": {...},
  4. "mappings": {
  5. "dynamic": "xxx",
  6. "properties": {
  7. "filed1": {
  8. "type": ""
  9. },
  10. "filed2": {
  11. "type": "",
  12. "dynamic": "xxx"
  13. },
  14. ...
  15. }
  16. },
  17. "aliases": {
  18. "default_index": {}
  19. }
  20. }

dynamic:

  • true,遇到陌生字段,就进行dynamic mapping,默认值。
  • false:新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍将出现在返回的源字段中。这些字段不会添加到映射中,必须显示的添加新字段。
  • strict:遇到陌生字段,就报错。
  • 示例:
  • 创建索引
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "dynamic": "strict",
  5. "properties": {
  6. "title": {
  7. "type": "text"
  8. },
  9. "address": {
  10. "type": "object",
  11. "dynamic": "true"
  12. }
  13. }
  14. }
  15. }
  • 测试插入数据
  1. PUT /my_index/_doc/1
  2. {
  3. "title": "my article",
  4. "content": "this is my article",
  5. "address": {
  6. "province": "guangdong",
  7. "city": "guangzhou"
  8. }
  9. }
  • 报错:
  1. {
  2. "error" : {
  3. "root_cause" : [
  4. {
  5. "type" : "strict_dynamic_mapping_exception",
  6. "reason" : "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
  7. }
  8. ],
  9. "type" : "strict_dynamic_mapping_exception",
  10. "reason" : "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
  11. },
  12. "status" : 400
  13. }

1.5.2 自定义动态映射(dynamic mapping)策略

  • ES会根据传入的值,自动推断类型。

ES会根据传入的值,自动推断类型.png

  • 日期探测(Date Detection):默认会按照一定格式识别date,比如yyyy-MM-dd,但是如果某个field先过来一个”2019-11-11”的值,就会自动被dynamic mapping识别成date,后面如果在过来一个”hello world”之类的值,就会报错。可以手动关闭某个type的date detection,如果有需要,自己手动指定某个field为date类型。
  • 语法:
  1. PUT /index
  2. {
  3. "mappings": {
  4. "date_detection": false,
  5. "properties": {
  6. "filed1": {
  7. "type": ""
  8. },
  9. "filed2": {
  10. "type": ""
  11. },
  12. ...
  13. }
  14. }
  15. }
  • 示例:关闭日期探测
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "date_detection": false,
  5. "properties": {
  6. "title": {
  7. "type": "text"
  8. },
  9. "address": {
  10. "type": "object",
  11. "dynamic": "true"
  12. }
  13. }
  14. }
  15. }
  • 测试插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "title": "my article",
  4. "content": "this is my article",
  5. "address": {
  6. "province": "guangdong",
  7. "city": "guangzhou"
  8. },
  9. "post_date":"2019-09-10"
  10. }
  • 自定义日期格式:dynamic_date_formats可以定制自己的格式。
  • 语法:
  1. PUT /index
  2. {
  3. "mappings": {
  4. "dynamic_date_formats": ["MM/dd/yyyy"],
  5. "properties": {
  6. "filed1": {
  7. "type": ""
  8. },
  9. "filed2": {
  10. "type": ""
  11. },
  12. ...
  13. }
  14. }
  15. }
  • 示例:自定义日期格式
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "dynamic_date_formats": ["MM/dd/yyyy"]
  5. }
  6. }
  • 测试插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "create_date": "09/25/2019"
  4. }
  • 数字探测(Numeric Detection):虽然JSON支持本机浮点和整数数据类型,但是某些应用程序或语言有时候可能将数字呈现为字符串。通常正确的解决方案是显示的映射这些字段,那么就可以启用数字检测(默认情况下禁用)来自动完成这些操作。
  • 语法:
  1. PUT /index
  2. {
  3. "mappings": {
  4. "numeric_detection": true,
  5. "properties": {
  6. "filed1": {
  7. "type": ""
  8. },
  9. "filed2": {
  10. "type": ""
  11. },
  12. ...
  13. }
  14. }
  15. }
  • 示例:开启数字探测
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "numeric_detection": true
  5. }
  6. }
  • 测试插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "my_float": "1.0",
  4. "my_integer": "1"
  5. }

1.5.3 定义自己的dynamic mapping template(动态映射模板)

  • 动态映射模板允许我们自定义映射,这些映射可以应用到动态添加的字段。
  • 语法:
  1. PUT index
  2. {
  3. "mappings": {
  4. "dynamic_templates": [
  5. {
  6. "template_name": {
  7. ... match conditions ...
  8. "mapping": { ... }
  9. }
  10. },
  11. ...
  12. ]
  13. }
  14. }
  • template_name:模板名称可以是任何字符串。
  • match conditions:匹配条件,包括match_mapping_type、match、match_pattern、unmatch、path_match、path_unmatch。
  • mapping:匹配字段应该使用的映射。
  • 示例:
  • 定制自己的映射模块:
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "dynamic_templates": [
  5. {
  6. "en": {
  7. "match": "*_en",
  8. "match_mapping_type": "string",
  9. "mapping": {
  10. "type": "text",
  11. "analyzer": "english"
  12. }
  13. }
  14. }
  15. ]
  16. }
  17. }
  • 插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "title": "this is my first article"
  4. }
  1. PUT /my_index/_doc/2
  2. {
  3. "title_en": "this is my first article"
  4. }
  • 搜索:
  1. GET /my_index/_search?q=article
  1. GET /my_index/_search?q=is
  • title没有匹配到任何的动态模块,默认就是standard分词器,不会过滤掉停用词,像is会进入到倒排索引,用is来搜索是可以搜索到的。
  • title_en匹配到了动态模块,就是english分词器,会过滤掉停用词,is这种停用词会被过滤掉,用is来搜索是搜索不到的。
  • 模块写法:
  1. PUT my_index
  2. {
  3. "mappings": {
  4. "dynamic_templates": [
  5. {
  6. "integers": {
  7. "match_mapping_type": "long",
  8. "mapping": {
  9. "type": "integer"
  10. }
  11. }
  12. },
  13. {
  14. "strings": {
  15. "match_mapping_type": "string",
  16. "mapping": {
  17. "type": "text",
  18. "fields": {
  19. "raw": {
  20. "type": "keyword",
  21. "ignore_above": 256
  22. }
  23. }
  24. }
  25. }
  26. }
  27. ]
  28. }
  29. }
  • 模板参数:
  1. "match": "long_*",
  1. "unmatch": "*_text",
  1. "match_mapping_type": "string",
  1. "path_match": "name.*",
  1. "path_unmatch": "*.middle",
  1. "match_pattern": "regex",
  1. "match": "^profit_\d+$"
  • 动态模块的应用场景:
  • ①结构化搜索:

    • 默认情况下,ElasticSearch将字符串字段映射为带有子关键字字段的文本字段。但是,如果只对结构化内容进行索引,而对全文检索不感兴趣,则可以仅将字段映射为关键字。注意:为了能够搜索这些字段,必须搜索索引的时候用完全相同的值。
      1. {
      2. "strings_as_keywords": {
      3. "match_mapping_type": "string",
      4. "mapping": {
      5. "type": "keyword"
      6. }
      7. }
      8. }
  • ②仅搜索:

    • 如果只关系字符串字段的全文检索,并且不打算对字符串字段进行聚合、排序或精确搜索,可以将其映射为文本字段。
      1. {
      2. "strings_as_text": {
      3. "match_mapping_type": "string",
      4. "mapping": {
      5. "type": "text"
      6. }
      7. }
      8. }
  • ③norms :不关心评分。

    • norms是指标时间的评分因素。如果不关心评分,比如:不按评分对文档进行排序,则可以在索引中禁用这些评分因子的存储以便节省一些空间。
      1. {
      2. "strings_as_keywords": {
      3. "match_mapping_type": "string",
      4. "mapping": {
      5. "type": "text",
      6. "norms": false,
      7. "fields": {
      8. "keyword": {
      9. "type": "keyword",
      10. "ignore_above": 256
      11. }
      12. }
      13. }
      14. }
      15. }

1.6 零停机重建索引

1.6.1 零停机重建索引

  • 场景:index被确定以后,一个field的设置是不能被修改的,如果要修改一个field,应该重新按照新的mapping,建立新的index,并将数据批量查询出来,重新用bulk api写入到index中。
  • 批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来重建索引数据,每次scroll就查询执行日期的一段数据,交给一个线程即可。
  • 示例:
  • ①一开始,依据动态映射(dynamic mapping)插入数据,但是不小心有些数据是诸如”2019-11-11”之类的日期格式,所以会被映射为date类型,实际上它是string类型。
  1. PUT /my_index/_doc/1
  2. {
  3. "title": "2019-11-11"
  4. }
  1. PUT /my_index/_doc/2
  2. {
  3. "title": "2019-11-12"
  4. }
  • ②当后期向索引中插入string类型的title值的时候,会报错。
  1. PUT /my_index/_doc/3
  2. {
  3. "title": "this is my first article"
  4. }
  • 报错:
  1. {
  2. "error" : {
  3. "root_cause" : [
  4. {
  5. "type" : "mapper_parsing_exception",
  6. "reason" : "failed to parse field [title] of type [date] in document with id '3'. Preview of field's value: 'this is my first article'"
  7. }
  8. ],
  9. "type" : "mapper_parsing_exception",
  10. "reason" : "failed to parse field [title] of type [date] in document with id '3'. Preview of field's value: 'this is my first article'",
  11. "caused_by" : {
  12. "type" : "illegal_argument_exception",
  13. "reason" : "failed to parse date field [this is my first article] with format [strict_date_optional_time||epoch_millis]",
  14. "caused_by" : {
  15. "type" : "date_time_parse_exception",
  16. "reason" : "Failed to parse with all enclosed parsers"
  17. }
  18. }
  19. },
  20. "status" : 400
  21. }
  • ③此时,想修改title的类型,是不可能的:
  1. PUT /my_index/_mapping
  2. {
  3. "properties":{
  4. "title":{
  5. "type":"text"
  6. }
  7. }
  8. }
  • 报错:
  1. {
  2. "error" : {
  3. "root_cause" : [
  4. {
  5. "type" : "illegal_argument_exception",
  6. "reason" : "mapper [title] cannot be changed from type [date] to [text]"
  7. }
  8. ],
  9. "type" : "illegal_argument_exception",
  10. "reason" : "mapper [title] cannot be changed from type [date] to [text]"
  11. },
  12. "status" : 400
  13. }
  • ④此时,唯一的办法,就是重建索引,即重新建立一个新的索引,将旧索引的数据查询出来,再导入新的索引。
  • ⑤如果说就索引的名字是old_index,新索引的名字是new_index。终端Java应用已经在使用old_index操作了,难道我们需要停止Java应用,修改使用的index为new_index,再重新启动Java应用?这个过程中,必然会导致Java应用停机,可用性也降低。
  • ⑥给索引起一个别名,Java应用指向这个别名,那么此时Java应用指向的是旧索引。
  1. PUT /my_index/_alias/prod_index
  • ⑦创建新的Index,调整title的类型为string:
  1. PUT /my_index_new
  2. {
  3. "mappings": {
  4. "properties": {
  5. "title": {
  6. "type": "text"
  7. }
  8. }
  9. }
  10. }
  • ⑧使用scroll api将数据批量查询出来:
  1. GET /my_index/_search?scroll=1m
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "size": 1
  7. }
  • 返回:
  1. {
  2. "_scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFnRRN1doOHVhUktXQllGZGlHRjN3bFEAAAAAAAANOhZzenoyWWlMd1I2T1kyMWtzMDExYVl3",
  3. "took" : 0,
  4. "timed_out" : false,
  5. "_shards" : {
  6. "total" : 1,
  7. "successful" : 1,
  8. "skipped" : 0,
  9. "failed" : 0
  10. },
  11. "hits" : {
  12. "total" : {
  13. "value" : 1,
  14. "relation" : "eq"
  15. },
  16. "max_score" : 1.0,
  17. "hits" : [
  18. {
  19. "_index" : "my_index",
  20. "_type" : "_doc",
  21. "_id" : "1",
  22. "_score" : 1.0,
  23. "_source" : {
  24. "title" : "2019-11-11"
  25. }
  26. }
  27. ]
  28. }
  29. }
  • ⑨采用bulk api将scoll查出来的一批数据,批量写入新索引:
  1. POST /_bulk
  2. { "index": { "_index": "my_index_new", "_id": "1" }}
  3. { "title": "2019-11-11" }
  • ⑩反复循环8~9,查询一批批数据出现,采用bulk api将每一批数据批量写入到新索引。
  • ⑪将prod_index这个alias切换到my_index_new上,Java应用会直接通过index别名使用新的索引中的数据,Java应用程序不需要停机,高可用。
  1. POST /_aliases
  2. {
  3. "actions": [
  4. { "remove": { "index": "my_index", "alias": "prod_index" }},
  5. { "add": { "index": "my_index_new", "alias": "prod_index" }}
  6. ]
  7. }
  • ⑫直接通过pro_index别名查询:
  1. GET /prod_index/_search

1.6.2 生产实践,基于alias对client透明切换index

  • 对索引进行别名操作:
  1. PUT /my_index_v1/_alias/my_index
  • Java客户端对别名(my_index)进行操作。
  • 重建索引后,切换v1到v2:
  1. POST /_aliases
  2. {
  3. "actions": [
  4. { "remove": { "index": "my_index_v1", "alias": "my_index" }},
  5. { "add": { "index": "my_index_v2", "alias": "my_index" }}
  6. ]
  7. }

2 中文分词器之IK分词器

2.1 IK分词器的安装和使用

2.1.1 中文分词器

  • 默认的standard分词器,仅适用于英文。
  1. GET /_analyze
  2. {
  3. "analyzer": "standard",
  4. "text": ["中华人民共和国人民大会堂"]
  5. }
  • 返回:
  1. {
  2. "tokens" : [
  3. {
  4. "token" : "中",
  5. "start_offset" : 0,
  6. "end_offset" : 1,
  7. "type" : "<IDEOGRAPHIC>",
  8. "position" : 0
  9. },
  10. {
  11. "token" : "华",
  12. "start_offset" : 1,
  13. "end_offset" : 2,
  14. "type" : "<IDEOGRAPHIC>",
  15. "position" : 1
  16. },
  17. {
  18. "token" : "人",
  19. "start_offset" : 2,
  20. "end_offset" : 3,
  21. "type" : "<IDEOGRAPHIC>",
  22. "position" : 2
  23. },
  24. {
  25. "token" : "民",
  26. "start_offset" : 3,
  27. "end_offset" : 4,
  28. "type" : "<IDEOGRAPHIC>",
  29. "position" : 3
  30. },
  31. {
  32. "token" : "共",
  33. "start_offset" : 4,
  34. "end_offset" : 5,
  35. "type" : "<IDEOGRAPHIC>",
  36. "position" : 4
  37. },
  38. {
  39. "token" : "和",
  40. "start_offset" : 5,
  41. "end_offset" : 6,
  42. "type" : "<IDEOGRAPHIC>",
  43. "position" : 5
  44. },
  45. {
  46. "token" : "国",
  47. "start_offset" : 6,
  48. "end_offset" : 7,
  49. "type" : "<IDEOGRAPHIC>",
  50. "position" : 6
  51. },
  52. {
  53. "token" : "人",
  54. "start_offset" : 7,
  55. "end_offset" : 8,
  56. "type" : "<IDEOGRAPHIC>",
  57. "position" : 7
  58. },
  59. {
  60. "token" : "民",
  61. "start_offset" : 8,
  62. "end_offset" : 9,
  63. "type" : "<IDEOGRAPHIC>",
  64. "position" : 8
  65. },
  66. {
  67. "token" : "大",
  68. "start_offset" : 9,
  69. "end_offset" : 10,
  70. "type" : "<IDEOGRAPHIC>",
  71. "position" : 9
  72. },
  73. {
  74. "token" : "会",
  75. "start_offset" : 10,
  76. "end_offset" : 11,
  77. "type" : "<IDEOGRAPHIC>",
  78. "position" : 10
  79. },
  80. {
  81. "token" : "堂",
  82. "start_offset" : 11,
  83. "end_offset" : 12,
  84. "type" : "<IDEOGRAPHIC>",
  85. "position" : 11
  86. }
  87. ]
  88. }
  • 我们想要的效果是:中华人民共和国,人民大会堂。而standard分词器不能满足我们的要求。
  • IK分词器是目前最流行的ES中文分词器。

2.1.2 IK分词器的安装

  • 官方
  • 下载地址
  • 根据ES版本下载相应的IK分词器版本,解压到ES/plugins/ik中,重启ES。

解压IK分词器到ES的plugins的ik目录中.png

前面我们使用的ES版本是7.10.1和IK分词器的版本7.10不匹配,需要使用7.10.0的ESKibana

2.1.3 IK分词器的基本知识

  • ik_smart:会做最粗颗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国“和“人民大会堂”。
  • ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国”、“中华人民”、“中华”、“华人”、“人民共和国”、“人民大会堂”、“人民大会”、“大会堂”,会穷尽各种可能的组合;

2.1.4 IK分词器的使用

  • 示例:
  • 创建索引,存储的时候,使用ik_max_word,搜索的时候,使用ik_smart
  1. PUT /my_index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "name":{
  6. "type": "text",
  7. "analyzer": "ik_max_word",
  8. "search_analyzer": "ik_smart"
  9. }
  10. }
  11. }
  12. }
  • 插入数据:
  1. PUT /my_index/_doc/1
  2. {
  3. "name":"中华人民共和国人民大会堂"
  4. }
  • 搜索数据:
  1. GET /my_index/_search?q=共和国

2.2 IK配置文件

2.2.1 IK分词器配置文件

  • IK分词器配置文件地址:ES/plugins/ik/config目录。

IK分词器配置文件地址.png

  • IKAnalyzer.cfg.xml:用来配置自定义词库。
  • main.dic(重要):IK原生内置的中文词库,总共有27万多条,只要是这些单词,都会被分在一起。
  • preposition.dic:介词。
  • quantifier.dic:放了一些单位相关的词,量词。
  • suffix.dic:放了一些后缀。
  • surname.dic:中国的姓氏。
  • stopword.dic(重要):英文停用词。

2.2.2 自定义词库

  • ELK03 - 图4自己建立词库:

    • 每年都会涌现一些特殊的流行的词,比如网红、蓝瘦香菇、喊麦等,一般不会出现在原生词典中。
    • 步骤:

      • ①创建mydict.dic文件,补充最新的词语。
      • ②IKAnalyzer.cfg.xml文件中,配置mydict.dic。

        1. <?xml version="1.0" encoding="UTF-8"?>
        2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
        3. <properties>
        4. <comment>IK Analyzer 扩展配置</comment>
        5. <!--用户可以在这里配置自己的扩展字典 -->
        6. <entry key="ext_dict">mydict.dic</entry>
        7. <!--用户可以在这里配置自己的扩展停止词字典-->
        8. <entry key="ext_stopwords"></entry>
        9. <!--用户可以在这里配置远程扩展字典 -->
        10. <!-- <entry key="remote_ext_dict">words_location</entry> -->
        11. <!--用户可以在这里配置远程扩展停止词字典-->
        12. <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
        13. </properties>
      • ③重启ES。

  • ELK03 - 图5自己建立停用词:

    • 比如了、的、地、得等,我们可能并不想去建立索引,让别人搜索。
    • 步骤:

      • ①创建ext_stopword.dic,补充常见的中文停用词。
      • ②IKAnalyzer.cfg.xml文件中,配置ext_stopword.dic。

        1. <?xml version="1.0" encoding="UTF-8"?>
        2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
        3. <properties>
        4. <comment>IK Analyzer 扩展配置</comment>
        5. <!--用户可以在这里配置自己的扩展字典 -->
        6. <entry key="ext_dict">mydict.dic</entry>
        7. <!--用户可以在这里配置自己的扩展停止词字典-->
        8. <entry key="ext_stopwords">ext_stopword.dic</entry>
        9. <!--用户可以在这里配置远程扩展字典 -->
        10. <!-- <entry key="remote_ext_dict">words_location</entry> -->
        11. <!--用户可以在这里配置远程扩展停止词字典-->
        12. <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
        13. </properties>
      • ③重启ES。

2.3 使用MySQL热更新词库

2.3.1 热更新

  • 每次都是在ES的扩展词典中,手动添加新词语,很坑:
    • ELK03 - 图6每次添加完,都要重启ES,才能生效,非常麻烦。
    • ELK03 - 图7ES是分布式的,可能有数百个节点,我们不可能每次都是一个一个节点去修改。
  • 热更新:ES不停机,我们直接在外部的某个地方添加新的词语,ES就立即加载到这些新的词语。
  • 热更新的方案:
    • ELK03 - 图8基于IK分词器的原生支持的热更新方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新。
    • ELK03 - 图9修改IK分词器的源码,然后手动支持从MySQL中每隔一段时间,自动加载新的词库,推荐方案。

2.3.2 步骤

  • ELK03 - 图10下载源码
  • ELK03 - 图11修改源码:
    • ①创建HotDictReloadThread线程,不断的去调用Dictionary.getSingleton().reLoadMainDict()。 ```java package org.wltea.analyzer.dic;

import org.apache.logging.log4j.Logger; import org.wltea.analyzer.help.ESPluginLoggerFactory;

/**

  • 加载字典线程 *
  • @author 许大仙
  • @version 1.0
  • @since 2020-12-15 14:02 */ public class HotDictReloadThread implements Runnable {

    private static Logger logger = ESPluginLoggerFactory.getLogger(HotDictReloadThread.class.getName());

    @Override public void run() {

    1. while (true) {
    2. logger.info("----------reload hot dict from mysql--------------");
    3. Dictionary.getSingleton().reLoadMainDict();
    4. }

    } }

    1. - ②在pom.xml中添加mysql的驱动依赖:
    2. ```xml
    3. <dependency>
    4. <groupId>mysql</groupId>
    5. <artifactId>mysql-connector-java</artifactId>
    6. <version>8.0.21</version>
    7. </dependency>
    • ③数据库中新增es数据库以及对应的表的脚本: ```sql SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;

— Table structure for hot_stopwords


DROP TABLE IF EXISTS hot_stopwords; CREATE TABLE hot_stopwords ( stopword varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, id bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;


— Table structure for hot_words


DROP TABLE IF EXISTS hot_words; CREATE TABLE hot_words ( word varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, id bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

  1. - ④在项目的config目录下新建jdbc-reload.properties文件:
  2. ```properties
  3. jdbc.url=jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
  4. jdbc.user=root
  5. jdbc.password=123456
  6. jdbc.reload.sql=select word from hot_words
  7. jdbc.reload.stopword.sql=select stopword as word from hot_stopwords
  8. jdbc.reload.interval=5000
  • ⑤修改Dictionary中的initial()方法:

    1. /**
    2. * 词典初始化 由于IK Analyzer的词典采用Dictionary类的静态方法进行词典初始化
    3. * 只有当Dictionary类被实际调用时,才会开始载入词典, 这将延长首次分词操作的时间 该方法提供了一个在应用加载阶段就初始化字典的手段
    4. *
    5. * @return Dictionary
    6. */
    7. public static synchronized void initial(Configuration cfg) {
    8. if (singleton == null) {
    9. synchronized (Dictionary.class) {
    10. if (singleton == null) {
    11. singleton = new Dictionary(cfg);
    12. singleton.loadMainDict();
    13. singleton.loadSurnameDict();
    14. singleton.loadQuantifierDict();
    15. singleton.loadSuffixDict();
    16. singleton.loadPrepDict();
    17. singleton.loadStopWordDict();
    18. //*********mysql监控线程*********
    19. new Thread(new HotDictReloadThread()).start();
    20. if (cfg.isEnableRemoteDict()) {
    21. // 建立监控线程
    22. for (String location : singleton.getRemoteExtDictionarys()) {
    23. // 10 秒是初始延迟可以修改的 60是间隔时间 单位秒
    24. pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
    25. }
    26. for (String location : singleton.getRemoteExtStopWordDictionarys()) {
    27. pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
    28. }
    29. }
    30. }
    31. }
    32. }
    33. }
  • ⑥修改Dictionary中的loadMainDict()方法:

    1. /**
    2. * 加载主词典及扩展词典
    3. */
    4. private void loadMainDict() {
    5. // 建立一个主词典实例
    6. _MainDict = new DictSegment((char) 0);
    7. // 读取主词典文件
    8. Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_MAIN);
    9. loadDictFile(_MainDict, file, false, "Main Dict");
    10. // 加载扩展词典
    11. this.loadExtDict();
    12. // 加载远程自定义词库
    13. this.loadRemoteExtDict();
    14. // ***********从MySQL中加载词典***********
    15. this.loadMySQLExtDict();
    16. }

    ```java private static Properties prop = new Properties();

static { try { Class.forName(“com.mysql.jdbc.Driver”); } catch (ClassNotFoundException ex) { logger.error(“mysql driver not found exception”, ex); } } /**

  • 从mysql加载热更新词典 */ private void loadMySQLExtDict() { Connection conn = null; Statement stmt = null; ResultSet rs = null;

    try {

    1. Path file = PathUtils.get(getDictRoot(), "jdbc-reload.properties");
    2. prop.load(new FileInputStream(file.toFile()));
    3. logger.info("[==========]jdbc-reload.properties");
    4. for(Object key : prop.keySet()) {
    5. logger.info("[==========]" + key + "=" + prop.getProperty(String.valueOf(key)));
    6. }
    7. logger.info("[==========]query hot dict from mysql, " + prop.getProperty("jdbc.reload.sql") + "......");
    8. conn = DriverManager.getConnection(
    9. prop.getProperty("jdbc.url"),
    10. prop.getProperty("jdbc.user"),
    11. prop.getProperty("jdbc.password"));
    12. stmt = conn.createStatement();
    13. rs = stmt.executeQuery(prop.getProperty("jdbc.reload.sql"));
    14. while(rs.next()) {
    15. String theWord = rs.getString("word");
    16. logger.info("[==========]hot word from mysql: " + theWord);
    17. _MainDict.fillSegment(theWord.trim().toCharArray());
    18. }
    19. Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval"))));

    } catch (Exception e) {

    1. logger.error("erorr", e);

    } finally {

    1. if(rs != null) {
    2. try {
    3. rs.close();
    4. } catch (SQLException e) {
    5. logger.error("error", e);
    6. }
    7. }
    8. if(stmt != null) {
    9. try {
    10. stmt.close();
    11. } catch (SQLException e) {
    12. logger.error("error", e);
    13. }
    14. }
    15. if(conn != null) {
    16. try {
    17. conn.close();
    18. } catch (SQLException e) {
    19. logger.error("error", e);
    20. }
    21. }

    } }

    1. - ⑦修改Dictionary中的loadStopWordDict()方法:
    2. ```java
    3. /**
    4. * 加载用户扩展的停止词词典
    5. */
    6. private void loadStopWordDict() {
    7. // 建立主词典实例
    8. _StopWords = new DictSegment((char) 0);
    9. // 读取主词典文件
    10. Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_STOP);
    11. loadDictFile(_StopWords, file, false, "Main Stopwords");
    12. // 加载扩展停止词典
    13. List<String> extStopWordDictFiles = getExtStopWordDictionarys();
    14. if (extStopWordDictFiles != null) {
    15. for (String extStopWordDictName : extStopWordDictFiles) {
    16. logger.info("[Dict Loading] " + extStopWordDictName);
    17. // 读取扩展词典文件
    18. file = PathUtils.get(extStopWordDictName);
    19. loadDictFile(_StopWords, file, false, "Extra Stopwords");
    20. }
    21. }
    22. // 加载远程停用词典
    23. List<String> remoteExtStopWordDictFiles = getRemoteExtStopWordDictionarys();
    24. for (String location : remoteExtStopWordDictFiles) {
    25. logger.info("[Dict Loading] " + location);
    26. List<String> lists = getRemoteWords(location);
    27. // 如果找不到扩展的字典,则忽略
    28. if (lists == null) {
    29. logger.error("[Dict Loading] " + location + " load failed");
    30. continue;
    31. }
    32. for (String theWord : lists) {
    33. if (theWord != null && !"".equals(theWord.trim())) {
    34. // 加载远程词典数据到主内存中
    35. logger.info(theWord);
    36. _StopWords.fillSegment(theWord.trim().toLowerCase().toCharArray());
    37. }
    38. }
    39. }
    40. //***********从mysql加载停用词************
    41. this.loadMySQLStopwordDict();
    42. }
    1. //从mysql加载停用词
    2. private void loadMySQLStopwordDict() {
    3. Connection conn = null;
    4. Statement stmt = null;
    5. ResultSet rs = null;
    6. try {
    7. Path file = PathUtils.get(getDictRoot(), "jdbc-reload.properties");
    8. prop.load(new FileInputStream(file.toFile()));
    9. logger.info("[==========]jdbc-reload.properties");
    10. for(Object key : prop.keySet()) {
    11. logger.info("[==========]" + key + "=" + prop.getProperty(String.valueOf(key)));
    12. }
    13. logger.info("[==========]query hot stopword dict from mysql, " + prop.getProperty("jdbc.reload.stopword.sql") + "......");
    14. conn = DriverManager.getConnection(
    15. prop.getProperty("jdbc.url"),
    16. prop.getProperty("jdbc.user"),
    17. prop.getProperty("jdbc.password"));
    18. stmt = conn.createStatement();
    19. rs = stmt.executeQuery(prop.getProperty("jdbc.reload.stopword.sql"));
    20. while(rs.next()) {
    21. String theWord = rs.getString("word");
    22. logger.info("[==========]hot stopword from mysql: " + theWord);
    23. _StopWords.fillSegment(theWord.trim().toCharArray());
    24. }
    25. Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval"))));
    26. } catch (Exception e) {
    27. logger.error("erorr", e);
    28. } finally {
    29. if(rs != null) {
    30. try {
    31. rs.close();
    32. } catch (SQLException e) {
    33. logger.error("error", e);
    34. }
    35. }
    36. if(stmt != null) {
    37. try {
    38. stmt.close();
    39. } catch (SQLException e) {
    40. logger.error("error", e);
    41. }
    42. }
    43. if(conn != null) {
    44. try {
    45. conn.close();
    46. } catch (SQLException e) {
    47. logger.error("error", e);
    48. }
    49. }
    50. }
    51. }
    • ⑧使用mvn package将项目进行打包。
    • ⑨将刚才打包生成的jar包替换release压缩包中的elasticsearch-analysis-ik-7.10.0.jar。
    • ⑩将刚才使用的jdbc-reload.properties文件复制到conf目录下,并顺便复制mysql的驱动到ik目录中。

复制jdbc-reload.properites文件到ik的conf目录.png
替换ik分词器的jar包.png
复制mysql驱动到es的lib目录下.png

  • ⑪重启ES:观察日志,日志中会显示出我们打印的那些东西,比如加载了什么配置等等。
    • ⑫在MySQL中添加词库和停用词。
  • ⑬测试热更新是否成功:
    1. GET /_analyze
    2. {
    3. "analyzer": "ik_smart",
    4. "text": ["江苏苏州大学"]
    5. }

3 Java API实现索引管理

3.1 新增索引

  • 示例:
  1. package com.sunxiaping.elk;
  2. import org.elasticsearch.action.admin.indices.alias.Alias;
  3. import org.elasticsearch.action.support.ActiveShardCount;
  4. import org.elasticsearch.client.RequestOptions;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.elasticsearch.client.indices.CreateIndexRequest;
  7. import org.elasticsearch.client.indices.CreateIndexResponse;
  8. import org.elasticsearch.common.settings.Settings;
  9. import org.elasticsearch.common.unit.TimeValue;
  10. import org.junit.jupiter.api.Test;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.boot.test.context.SpringBootTest;
  13. import java.io.IOException;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. @SpringBootTest
  17. public class ElkApplicationTests {
  18. @Autowired
  19. private RestHighLevelClient client;
  20. /**
  21. *
  22. <pre>
  23. * PUT /index
  24. * {
  25. * "settings":{...},
  26. * "mappings":{
  27. * "properties":{
  28. * ...
  29. * }
  30. * },
  31. * "aliases":{
  32. * "default_index":{}
  33. * }
  34. * }
  35. * </pre>
  36. */
  37. @Test
  38. public void testCreateIndex() throws IOException {
  39. //创建请求
  40. CreateIndexRequest request = new CreateIndexRequest("my_index");
  41. //设置参数
  42. request.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "1").build());
  43. //设置映射
  44. Map<String, Object> field1 = new HashMap<>();
  45. field1.put("type", "text");
  46. Map<String, Object> field2 = new HashMap<>();
  47. field2.put("type", "text");
  48. Map<String,Object> properties = new HashMap<>();
  49. properties.put("field1", field1);
  50. properties.put("field2", field2);
  51. Map<String,Object> mappings = new HashMap<>();
  52. mappings.put("properties",properties);
  53. request.mapping(mappings);
  54. //设置别名
  55. request.alias(new Alias("default_index"));
  56. //---------------可选参数-------------
  57. //超时5秒
  58. request.setTimeout(TimeValue.timeValueSeconds(5));
  59. //主节点超时5秒
  60. request.setMasterTimeout(TimeValue.timeValueSeconds(5));
  61. //设置创建索引API返回相应之前等待活动分片的数量
  62. request.waitForActiveShards(ActiveShardCount.from(1));
  63. //执行
  64. CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
  65. //获取返回结果
  66. boolean acknowledged = response.isAcknowledged();
  67. System.out.println("acknowledged = " + acknowledged);
  68. boolean shardsAcknowledged = response.isShardsAcknowledged();
  69. System.out.println("shardsAcknowledged = " + shardsAcknowledged);
  70. String index = response.index();
  71. System.out.println("index = " + index);
  72. }
  73. }

3.2 查询索引

  • 示例:
  1. package com.sunxiaping.elk;
  2. import org.elasticsearch.client.RequestOptions;
  3. import org.elasticsearch.client.RestHighLevelClient;
  4. import org.elasticsearch.client.indices.GetIndexRequest;
  5. import org.elasticsearch.client.indices.GetIndexResponse;
  6. import org.elasticsearch.cluster.metadata.AliasMetadata;
  7. import org.elasticsearch.cluster.metadata.MappingMetadata;
  8. import org.elasticsearch.common.settings.Settings;
  9. import org.junit.jupiter.api.Test;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.boot.test.context.SpringBootTest;
  12. import java.io.IOException;
  13. import java.util.List;
  14. import java.util.Map;
  15. @SpringBootTest
  16. public class ElkApplicationTests {
  17. @Autowired
  18. private RestHighLevelClient client;
  19. /**
  20. * 查询索引是否存在以及查询索引信息
  21. */
  22. @Test
  23. public void testExistIndex() throws IOException {
  24. GetIndexRequest request = new GetIndexRequest("my_index");
  25. //参数
  26. request.local(false);//从主节点返回本地索引信息状态
  27. request.humanReadable(true);//以适合人类的格式返回
  28. request.includeDefaults(false);//是否返回每个索引的所有默认配置
  29. //查询索引是否存在
  30. boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
  31. System.out.println("exists = " + exists);
  32. //查询索引
  33. GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
  34. Map<String, List<AliasMetadata>> aliases = response.getAliases();
  35. System.out.println("aliases = " + aliases);
  36. Map<String, MappingMetadata> mappings = response.getMappings();
  37. System.out.println("mappings = " + mappings);
  38. Map<String, Settings> settings = response.getSettings();
  39. System.out.println("settings = " + settings);
  40. }
  41. }

3.3 删除索引

  • 示例:
  1. package com.sunxiaping.elk;
  2. import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
  3. import org.elasticsearch.action.support.master.AcknowledgedResponse;
  4. import org.elasticsearch.client.RequestOptions;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.junit.jupiter.api.Test;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import java.io.IOException;
  10. @SpringBootTest
  11. public class ElkApplicationTests {
  12. @Autowired
  13. private RestHighLevelClient client;
  14. /**
  15. * 删除索引
  16. *
  17. * @throws IOException
  18. */
  19. @Test
  20. public void testDeleteIndex() throws IOException {
  21. DeleteIndexRequest request = new DeleteIndexRequest("my_index");
  22. AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
  23. boolean acknowledged = response.isAcknowledged();
  24. System.out.println("acknowledged = " + acknowledged);
  25. }
  26. }

3.4 关闭索引

  • 示例:
  1. package com.sunxiaping.elk;
  2. import org.elasticsearch.client.RequestOptions;
  3. import org.elasticsearch.client.RestHighLevelClient;
  4. import org.elasticsearch.client.indices.CloseIndexRequest;
  5. import org.elasticsearch.client.indices.CloseIndexResponse;
  6. import org.junit.jupiter.api.Test;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import java.io.IOException;
  10. @SpringBootTest
  11. public class ElkApplicationTests {
  12. @Autowired
  13. private RestHighLevelClient client;
  14. /**
  15. * 关闭索引:可以查询索引,但是不可以新增、修改、删除数据
  16. *
  17. * @throws IOException
  18. */
  19. @Test
  20. public void testCloseIndex() throws IOException {
  21. CloseIndexRequest request = new CloseIndexRequest("my_index");
  22. CloseIndexResponse response = client.indices().close(request, RequestOptions.DEFAULT);
  23. boolean acknowledged = response.isAcknowledged();
  24. System.out.println("acknowledged = " + acknowledged);
  25. }
  26. }

3.5 开启索引

  • 示例:
  1. package com.sunxiaping.elk;
  2. import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
  3. import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
  4. import org.elasticsearch.client.RequestOptions;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.junit.jupiter.api.Test;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import java.io.IOException;
  10. @SpringBootTest
  11. public class ElkApplicationTests {
  12. @Autowired
  13. private RestHighLevelClient client;
  14. /**
  15. * 开启索引
  16. *
  17. * @throws IOException
  18. */
  19. @Test
  20. public void testOpenIndex() throws IOException {
  21. OpenIndexRequest request = new OpenIndexRequest("my_index");
  22. OpenIndexResponse response = client.indices().open(request, RequestOptions.DEFAULT);
  23. boolean acknowledged = response.isAcknowledged();
  24. System.out.println("acknowledged = " + acknowledged);
  25. }
  26. }