出自图灵学院 白起老师的 ElasticSearch 课程 ,我学完了之后 操作完了做个了笔记,然后发了个博客

概述

思考一下,github中可以使用代码片段来实现数据搜索。这是如何实现的?

在github中也使用了ES来实现数据的全文搜索。其ES中有一个记录代码内容的索引,大致数据内容如下:

  1. {
  2. "fileName" : "HelloWorld.java",
  3. "authName" : "baiqi",
  4. "authID" : 110,
  5. "productName" : "first-java",
  6. "path" : "/com/baiqi/first",
  7. "content" : "package com.baiqi.first; public class HelloWorld { //code... }"
  8. }

我们可以在github中通过代码的片段来实现数据的搜索。也可以使用其他条件实现数据搜索。但是,如果需要使用文件路径搜索内容应该如何实现?

此时相对会麻烦一点

比如说用户输入/com之后,需要给/com结果的路径找出来,但是有的用户可能忘了前缀了,就输入了中间的一段路径,比如说用户输入 /baiqi 那你需要把 es里面已经存在的 /com/baiqi/first 的路径的数据找出来.

这个时候需要为其中的字段path定义一个特殊的分词器。具体如下:

索引

  1. PUT /codes
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": {
  6. "path_analyzer": {
  7. "tokenizer": "path_hierarchy"
  8. }
  9. }
  10. }
  11. },
  12. "mappings": {
  13. "properties": {
  14. "fileName": {
  15. "type": "keyword"
  16. },
  17. "authName": {
  18. "type": "text",
  19. "analyzer": "standard",
  20. "fields": {
  21. "keyword": {
  22. "type": "keyword"
  23. }
  24. }
  25. },
  26. "authID": {
  27. "type": "long"
  28. },
  29. "productName": {
  30. "type": "text",
  31. "analyzer": "standard",
  32. "fields": {
  33. "keyword": {
  34. "type": "keyword"
  35. }
  36. }
  37. },
  38. "path": {
  39. "type": "text",
  40. "analyzer": "path_analyzer",
  41. "fields": {
  42. "keyword": {
  43. "type": "keyword"
  44. }
  45. }
  46. },
  47. "content": {
  48. "type": "text",
  49. "analyzer": "standard"
  50. }
  51. }
  52. }
  53. }

注意:

“path_analyzer”: {
“tokenizer”: “path_hierarchy”
}

path_hierarchy这个分词策略是ElasticSearch专门针对路径形式的分词器,path_hierarchy分词器的特征是

path_hierarchy分词器的缺陷

  1. GET /codes/_analyze
  2. {
  3. "text": "/a/b/c/d",
  4. "field": "path"
  5. }

结果

  1. {
  2. "tokens" : [
  3. {
  4. "token" : "/a",
  5. "start_offset" : 0,
  6. "end_offset" : 2,
  7. "type" : "word",
  8. "position" : 0
  9. },
  10. {
  11. "token" : "/a/b",
  12. "start_offset" : 0,
  13. "end_offset" : 4,
  14. "type" : "word",
  15. "position" : 0
  16. },
  17. {
  18. "token" : "/a/b/c",
  19. "start_offset" : 0,
  20. "end_offset" : 6,
  21. "type" : "word",
  22. "position" : 0
  23. },
  24. {
  25. "token" : "/a/b/c/d",
  26. "start_offset" : 0,
  27. "end_offset" : 8,
  28. "type" : "word",
  29. "position" : 0
  30. }
  31. ]
  32. }

此时发现你如果搜索 /a 或者 /a/b 或者 /a/b/c 或者 /a/b/c/d 都能给 这个文档找出来,但是如果只是输入/b 或者是输入 /b/c 就不能给文档搜索出来了, 这就是个缺陷.

插入数据开始测试搜索发现缺陷

  1. PUT /codes/_doc/1
  2. {
  3. "fileName": "HelloWorld.java",
  4. "authName": "baiqi",
  5. "authID": 110,
  6. "productName": "first-java",
  7. "path": "/com/baiqi/first",
  8. "content": "package com.baiqi.first; public class HelloWorld { // some code... }"
  9. }

测试搜索

搜索/com和/com/baiqi 都能搜索出来, 搜索结果我就不粘贴上了.

  1. GET /codes/_search
  2. {
  3. "query": {
  4. "match": {
  5. "path": "/com"
  6. }
  7. }
  8. }
  1. GET /codes/_search
  2. {
  3. "query": {
  4. "match": {
  5. "path": "/com/baiqi"
  6. }
  7. }
  8. }

发现缺陷

如果我搜索/baiqi ,此时发现搜不出来

  1. GET /codes/_search
  2. {
  3. "query": {
  4. "match": {
  5. "path": "/baiqi"
  6. }
  7. }
  8. }

这就是一个问题了,明明索引库有/com/baiqi ,就因为我忘了前缀/com 就搜不出来东西,这样的用户体验是不太好的,我就希望输入 /baiqi的时候我也能搜出来.

解决这个缺陷

先删除/codes

  1. DELETE /codes

创建索引,注意path字段的声明

“path”: {
“type”: “text”,
“analyzer”: “path_analyzer”,
“fields”: {
“keyword”: {
“type”: “text”, // 这个的意思是path字段按照standard分词器再次进行分词拆分
“analyzer”: “standard”
}
}
}

  1. PUT /codes
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": {
  6. "path_analyzer": {
  7. "tokenizer": "path_hierarchy"
  8. }
  9. }
  10. }
  11. },
  12. "mappings": {
  13. "properties": {
  14. "fileName": {
  15. "type": "keyword"
  16. },
  17. "authName": {
  18. "type": "text",
  19. "analyzer": "standard",
  20. "fields": {
  21. "keyword": {
  22. "type": "keyword"
  23. }
  24. }
  25. },
  26. "authID": {
  27. "type": "long"
  28. },
  29. "productName": {
  30. "type": "text",
  31. "analyzer": "standard",
  32. "fields": {
  33. "keyword": {
  34. "type": "keyword"
  35. }
  36. }
  37. },
  38. "path": {
  39. "type": "text",
  40. "analyzer": "path_analyzer",
  41. "fields": {
  42. "keyword": {
  43. "type": "text",
  44. "analyzer": "standard"
  45. }
  46. }
  47. },
  48. "content": {
  49. "type": "text",
  50. "analyzer": "standard"
  51. }
  52. }
  53. }
  54. }

插入数据

  1. PUT /codes/_doc/1
  2. {
  3. "fileName": "HelloWorld.java",
  4. "authName": "baiqi",
  5. "authID": 110,
  6. "productName": "first-java",
  7. "path": "/com/baiqi/first",
  8. "content": "package com.baiqi.first; public class HelloWorld { // some code... }"
  9. }

测试查询数据,输入/baiqi能不能搜出来

  1. GET /codes/_search
  2. GET /codes/_search
  3. {
  4. "query": {
  5. "match": {
  6. "path.keyword": "/baiqi"
  7. }
  8. }
  9. }

结果:

发现能搜出来了

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.2876821,
  16. "hits" : [
  17. {
  18. "_index" : "codes",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 0.2876821,
  22. "_source" : {
  23. "fileName" : "HelloWorld.java",
  24. "authName" : "baiqi",
  25. "authID" : 110,
  26. "productName" : "first-java",
  27. "path" : "/com/baiqi/first",
  28. "content" : "package com.baiqi.first; public class HelloWorld { // some code... }"
  29. }
  30. }
  31. ]
  32. }
  33. }

为什么输入path.keyword 才能搜出来

因为定义索引的时候path

“path”: {
“type”: “text”,
“analyzer”: “path_analyzer”,
“fields”: {
“keyword”: {
“type”: “text”, // 这个的意思是path字段按照standard分词器再次进行分词拆分
“analyzer”: “standard”
}
}
}

是这么配置的,

path的fields里面有个子字段keyword是根据standard进行分词的,

测试输入其它的能不能搜出来

“path.keyword”: “/com/baiqi”

“path.keyword”: “/baiqi/first”

“path.keyword”: “/com”

“path.keyword”: “/com/first”

上面这几种都能搜索出来

为什么 “path.keyword”: “/com/first” 也能查询出来呢?

因为path,keyword是text类型的,你输入/com/first的时候,其实是被分词了被分成了 /com /first 了