全文检索引擎Elasticsearch-2
3 Elasticsearch分词详解
ES分词介绍
1 | ES中在添加数据,也就是创建索引的时候,会先对数据进行分词。 |

1 | 图中左侧是创建索引的过程: |
倒排索引介绍
1 | 假设有一批数据,数据中有两个字段,文档编号和文档内容。 |

1 | 针对这一批数据,在ES中创建索引之后,最终产生的倒排索引内容大致是这样的: |

1 | 解释: |
1 | 以单词 加盟 为例,其单词编号为6,文档频率为3,代表整个文档集合中有3个文档包含这个单词,对应的倒排列表为{(2;1;<4>),(3;1;<7>),(5;1;<5>)},含义是在文档2,3,5中出现过这个单词,在每个文档中都只出现过1次,单词 加盟 在第一个文档的POS(位置)是4,即文档的第四个单词是 加盟 ,其它的类似。 |
分词器的作用
1 | 前面分析了ES在创建索引和查询索引的时候都需要进行分词,分词需要用到分词器。下面来具体分析一下分词器的作用: |
分词器的工作流程
1 | 分词器的工作流程一般是这样的: |
停用词
1 | 有些词在文本中出现的频率非常高,但是对文本所携带的信息基本不产生影响。 |
1 | a |
1 | 常见的中文停用词汇总: |
中文分词方式
1 | 针对中文而言,在分词的时候有多种分词规则: |
常见的中文分词器
1 | 针对前面分析的几种中文分词方式,对应的有一些已经实现好的中分分词器。 |

1 | 在词库分词方式领域里面,最经典的就是IK分词器,你懂得! |
ES中文分词插件(es-ik)
1 | 在中文数据检索场景中,为了提供更好的检索效果,需要在ES中集成中文分词器,因为ES默认是按照英文的分词规则进行分词的,基本上可以认为是单字分词,对中文分词效果不理想。 |


1 | 最终的下载地址为: |
1 | 1:将下载好的elasticsearch-analysis-ik-7.13.4.zip上传到bigdata01的/data/soft/ elasticsearch-7.13.4目录中。 |
1 | 3:在bigdata01节点离线安装IK插件。 |
1 | 注意:在安装的过程中会有警告信息提示需要输入y确认继续向下执行。 |
1 | config目录下面的analysis-ik里面存储的是ik的配置文件信息。 |
1 | plugins目录下面的analysis-ik里面存储的是ik的核心jar包。 |
1 | 4:在bigdata02节点离线安装IK插件。 |
1 | 6:如果集群正在运行,则需要停止集群。 |
1 | 7:修改elasticsearch-7.13.4的plugins目录下analysis-ik子目录的权限。 |
1 | 8:重新启动ES集群。 |
1 | 9:验证IK的分词效果。 |
1 | 然后使用IK分词器测试中文分词效果。 |
1 | 在这里我们发现分出来的单词里面有一个是,这个单词其实可以认为是一个停用词,在分词的时候是不需要切分出来的。 |
1 | [root@bigdata01 elasticsearch-7.13.4]# cd config/analysis-ik/ |
1 | ik的停用词词库是stopword.dic这个文件,这个文件里面目前都是一些英文停用词。 |
1 | [root@bigdata01 analysis-ik]# vi stopword.dic |
1 | 然后把这个文件的改动同步到集群中的所有节点上。 |
1 | 重启集群让配置生效。 |
1 | 此时再查看会发现没有"是" 这个单词了,相当于在过滤停用词的时候把它过滤掉了。 |
es-ik添加自定义词库
自定义词库
1 | 针对一些特殊的词语在分词的时候也需要能够识别。 |
1 | 结果发现ik分词器会把数据大脑分为 数据 和 大脑这两个单词。 |
1 | 创建自定义词库文件my.dic |
1 | 注意:这个my.dic词库文件可以在Linux中直接使用vi命令创建,或者在Windows中创建之后上传到这里。 |

1 | 2:修改ik的IKAnalyzer.cfg.xml配置文件 |
1 | 注意:需要把my.dic词库文件添加到key="ext_dict"这个entry中,切记不要随意新增entry,随意新增的entry是不被IK识别的,并且entry的名称也不能乱改,否则也不会识别。 |
1 | 3:将修改好的IK配置文件复制到集群中的所有节点中 |
1 | 先从bigdata01上将my.dic拷贝到bigdata02和bigdata03 |
1 | 注意:因为现在使用的是普通用户es,所以在使用scp的时候需要指定目标机器的用户名(如果是root可以省略不写),并且还需要手工输入密码,因为之前是基于root用户做的免密码登录。 |
1 | 再从bigdata01上将IKAnalyzer.cfg.xml拷贝到bigdata02和bigdata03 |
1 | 注意:如果后期想增加自定义停用词库,也需要按照这个思路进行添加,只不过停用词库需要配置到 key="ext_stopwords"这个entry中。 |
1 | 4:重启ES验证一下自定义词库的分词效果 |
1 | 现在发现数据大脑这个词语可以被识别出来了,说明自定义词库生效了。 |
热更新词库
1 | 针对前面分析的自定义词库,后期只要词库内容发生了变动,就需要重启ES才能生效,在实际工作中,频繁重启ES集群不是一个好办法 |
1 | [root@bigdata04 soft]# ll apache-tomcat-8.0.52.tar.gz |
1 | tomcat的ROOT项目中创建一个自定义词库文件hot.dic,在文件中输入一行内容:测试 |
1 | 启动Tomcat |
1 | 验证一下hot.dic文件是否可以通过浏览器访问: |
1 | 注意:页面会显示乱码,这是正常的,不用处理即可。 |
1 | [es@bigdata01 analysis-ik]$ vi IKAnalyzer.cfg.xml |
1 | 3:将修改好的IK配置文件复制到集群中的所有节点中 |
1 | 验证: |
1 | 正常情况下 北京雾霾 会被分被拆分为多个词语,但是在这我希望ES能够把 北京雾霾 认为是一个完整的词语,又不希望重启ES。 |
1 | 文件保存之后,在bigdata01上查看ES的日志会看到如下日志信息: |
1 | 再对北京雾霾这个词语进行分词 |
1 | 此时,发现北京雾霾这个词语就可以完整被切分出来了,到这为止,我们就成功实现了热更新自定义词库的功能。 |

4 Elasticsearch查询详解
ES Search查询
1 | 在ES中查询单条数据可以使用Get,想要查询一批满足条件的数据的话,就需要使用Search了。 |
1 | package com.imooc.es; |
1 | 在执行代码之前先初始化数据: |
1 | 在IDEA中执行代码,可以看到下面结果: |
searchType详解
1 | ES在查询数据的时候可以指定searchType,也就是搜索类型 |

1 | 其中QUERY AND FETCH和DFS QUERY AND FETCH这两种searchType现在已经不支持了。 |
1 | 这4种搜索类型到底有什么区别,下面我们来详细分析一下: |
1 | 然而这其中有两个问题。 |
1 | 这两个问题,ES也没有什么较好的解决方法,最终把选择的权利交给用户,方法就是在搜索的时候指定searchType。 |
1 | 1.QUERY AND FETCH(淘汰) |
DFS是一个什么样的过程?
1 | DFS其实就是在进行真正的查询之前,先把各个分片的词频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。显然如果使用DFS_QUERY_THEN_FETCH这种查询方式,效率是最低的,因为一个搜索,可能要请求3次分片。但使用DFS方法,搜索精度是最高的。 |
ES查询扩展
1 | 在查询数据的时候可以在searchRequest中指定一些参数,实现过滤、分页、排序、高亮等功能 |
EsSearchOp
1 | package com.imooc.es; |
过滤
1 | 首先看一下如何在查询的时候指定过滤条件 |
1 | //指定查询条件 |
1 | 带权重 |

1 | 使用match,还是模糊查询 |

1 | termQuery,查不到结果,因为他直接拿“刘德华”去查询,索引库里没有 |

1 | 利用queryStringQuery实现精准查询 |

1 | 默认情况下ES会对刘德华这个词语进行分词,效果如下(使用的默认分词器): |
1 | 初始化数据: |
分页
1 | ES每次返回的数据默认最多是10条,可以认为是一页的数据,这个数据量是可以控制的 |
排序
1 | 在返回满足条件的结果之前,可以按照指定的要求对数据进行排序,默认是按照搜索条件的匹配度返回数据的。(默认情况下,查询关键字的匹配度越高越靠前) |
1 | match实现精确查询 |

高亮
1 | 针对用户搜索时的关键词,如果匹配到了,最终在页面展现的时候可以标红高亮显示,看起来比较清晰。 |
1 | 别忘了前面完整代码中,要对高亮字段查询,才会有效果 |


1 | 解析高亮内容的核心代码如下: |
1 | 注意:必须要设置查询的字段,否则无法实现高亮。 |
评分依据(了解)
1 | ES在返回满足条件的数据的时候,按照搜索条件的匹配度返回数据的,匹配度最高的数据排在最前面,这个匹配度其实就是ES中返回结果中的score字段的值。 |
1 | 此时,我们搜索name=刘华 的数据 |
1 | 可以看到第一条数据的score分值为2.59 |
1 | 再执行程序,就可以看到具体的评分依据信息了: |
ES中分页的性能问题
1 | 在使用ES实现分页查询的时候,不要一次请求过多或者页码过大的结果,这样会对服务器造成很大的压力,因为它们会在返回前排序。 |

1 | 当然还有一点原因是后面的搜索结果基本上也不是我们想要的数据了,我们在使用搜索引擎的时候,一般只会看第1页和第2页的数据。 |
aggregations聚合统计
1 | ES中可以实现基于字段进行分组聚合的统计 |
统计相同年龄的学员个数
1 | 需求:统计相同年龄的学员个数 |

1 | 首先在ES中初始化这份数据: |
1 | package com.imooc.es; |
统计每个学员的总成绩
1 | 需求:统计每个学员的总成绩 |

1 | [root@bigdata01 ~]# curl -H "Content-Type: application/json" -XPOST 'http://bigdata01:9200/score/_doc/1' -d'{"name":"tom","subject":"chinese","score":59}' |
1 | package com.imooc.es; |
aggregations获取所有分组数据
1 | 默认情况下,ES只会返回10个分组的数据,如果分组之后的结果超过了10组,如何解决? |
1 | 支持案例1的代码,查看返回的分组个数: |
1 | 发现结果中返回的分组个数是10个,没有全部都显示出来,这个其实和分页也没关系,尝试增加分页的代码发现也是无效的: |
1 | 通过在聚合操作上使用size方法进行设置: |
1 | 执行案例1的代码: |
1 | 此时可以获取到所有分组的数据,因为结果一共有12个分组,在代码中通过size设置最多可以获取到20个分组的数据。 |
1 | TermsAggregationBuilder aggregation = AggregationBuilders.terms("age_term") |
1 | 注意:在ES7.x版本之前,想要获取所有的分组数据,只需要在size中指定参数为0即可。现在ES7.x版本不支持这个数值了。 |
5 Elasticsearch的高级特性
ES中的settings
1 | ES中的settings可以设置索引库的一些配置信息,主要是针对分片数量和副本数量 |
1 | 此时分片和副本数量默认都是1。 |
1 | 查看这个索引库的settings信息: |
1 | 针对已存在的索引,只能通过settings指定副本信息。 |
ES中的mapping
1 | mapping表示索引库中数据的字段类型信息,类似于MySQL中的表结构信息。 |
ES中的常用数据类型

1 | 字符串:支持text和keyword类型 |
查看mapping
1 | ES中还支持一些其他数据类型,感兴趣的话可以到文档里面看一下: |
1 | 通过返回的mapping信息可以看到score这个索引库里面有3个字段,name、score和subject。 |

1 | "ignore_above" : 256,表示keyword类型最大支持的字符串长度是256。 |
1 | 注意:ES 7.x版本之前字符串默认只会被识别为text类型,不会附加一个keyword类型。 |
手工创建mapping
1 | 下面我们首先操作一个不存在的索引库的mapping信息: |
1 | 查看这个索引库的mapping信息。 |
更新mapping
1 | 在这个已存在的索引库中增加mapping信息。 |
1 | [root@bigdata01 ~]# curl -H "Content-Type: application/json" -XPOST 'http://bigdata01:9200/test2/_mapping' -d'{"properties":{"age":{"type":"long"}}}' |
1 | 可以假设一下,假设支持修改已有字段的类型,之前name是text类型,如果我修改为long类型,这样就会出现矛盾了,所以ES不支持修改已有字段的类型。 |
1 | 查看索引库的最新mapping信息。 |
ES的偏好查询(分片查询方式)
1 | ES中的索引数据最终都是存储在分片里面的,分片有多个,并且分片还分为主分片和副本分片。 |

1 | 这个表示是一个3个节点的ES集群,集群内有一个索引库,索引库里面有P0和P1两个主分片,这两个主分片分别都有2个副本分片,R0和R1。 |
1 | 注意:当客户端的一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。 这个节点的任务是广播查询请求到所有相关分片,并将它们查询到的结果整合成全局排序后的结果集合,这个结果集合会返回给客户端。 |
1 | 接下来我们来具体分析一下如何控制查询请求到分片之间的分发规则: |

1 | 在索引库中初始化一批测试数据: |
1 | 这些测试数据在索引库的分片中的分布情况是这样的: |



1 | 在代码层面通过preference(...)来设置具体的分片查询方式: |
_local
1 | 表示查询操作会优先在本地节点(协调节点)的分片中查询,没有的话再到其它节点中查询。 |
_only_local
1 | 表示查询只会在本地节点的分片中查询。 |
_only_nodes
1 | 表示只在指定的节点中查询。 |
1 | 在这里面需要指定节点ID,节点ID应该如何获取呢? |

1 | 最终可以获取到三个节点的ID: |

1 | 在这里可以指定一个或者多个节点ID,多个节点ID之间使用逗号分割即可 |
1 | 此时执行就可以正常执行了: |
1 | 注意:在ES7.x版本之前,_only_nodes后面可以只指定某一个索引库部分分片所在的节点信息,如果不完整,不会报错,只是返回的数据是不完整的。 |
_prefer_nodes
1 | 表示优先在指定的节点上查询。 |
_shards(常用)
1 | 表示只查询索引库中指定分片的数据。 |
1 | 那我们如何控制将某一类型的数据添加到指定分片呢? |
custom-string
1 | 自定义一个参数,不能以下划线(_)开头。 |
1 | 会有问题的!如果searchType使用的是QUERY_THEN_FETCH,此时分片里面的数据在计算打分依据的时候是根据当前节点里面的词频和文档频率,两次查询使用的分片不是同一个,这样就会导致在计算打分依据的时候使用的样本不一致,最终导致两次相同的查询条件返回的结果不一样。 |
1 | searchRequest.preference("abc");//自定义参数 |
ES中的routing路由功能
1 | ES在添加数据时,会根据id或者routing参数进行hash,得到hash值再与该索引库的分片数量取模,得到的值即为存入的分片编号 |
1 | 下面来演示一下: |
1 | 如果是使用的JavaAPI,那么需要通过使用routing函数指定。 |
1 | 查看数据在分片中的分布情况,发现所有数据都在0号分片里面,说明routing参数生效了。 |

1 | 通过代码查询的时候,可以通过偏好查询指定只查询0号分片里面的数据。 |
1 | 通过偏好查询中的_shard手工指定分片编号在使用的时候不太友好,需要我们单独维护一份数据和分片之间的关系,比较麻烦。 |
1 | 结果如下 |
1 | 我们把routing参数修改一下,改为class2 |
1 | 从这可以看出来,这个routing参数确实生效了。 |
ES的索引库模板(了解)
1 | 在实际工作中针对一批大量数据存储的时候需要使用多个索引库,如果手工指定每个索引库的配置信息的话就很麻烦了。 |
1 | 下面看一个案例: |
1 | 第二个索引库模板: |
1 | 注意:order值大的模板内容会覆盖order值小的。 |
1 | 下面创建一个索引库,索引库名称为:test10 |
1 | 查看索引库test10的setting和mapping信息。 |
1 | 接下来再创建一个索引库,索引库名称为hello |
1 | [root@bigdata01 ~]# curl -XGET 'http://bigdata01:9200/hello/_settings?pretty' |
1 | 通过结果可以看出来hello这个索引库使用到了第一个索引库模板。 |
1 | 想要删除索引库模板可以这样做: |
ES的索引库别名(了解)
1 | 在工作中使用ES收集应用的运行日志,每个星期创建一个索引库,这样时间长了就会创建很多的索引库,操作和管理的时候很不方便。 |
1 | 下面来演示一下这个案例: |
1 | 为了使用方便,我们创建了两个索引库别名:curr_week和last_3_month。 |
1 | last_3_month指向之前3个月内的索引库: |
1 | 以后使用的时候,想要操作当前星期内的数据就使用curr_week这个索引库别名就行了。 |
1 | 再使用last_3_month查询一下数据: |
1 | 过了一个星期之后,又多了一个新的索引库:log_20260329 |
1 | 然后再把索引库log_20260322添加到last_3_month别名中: |
1 | 这些关联别名映射关系和移除别名映射关系的操作需要写个脚本定时执行,这样就可以实现别名自动关联到指定索引库了。 |
1 | 如果想知道哪些别名指向了这个索引,可以这样查看: |
1 | 注意:针对3个月以前的索引基本上就很少再使用了,为了减少对ES服务器的性能损耗(主要是内存的损耗),建议把这些长时间不使用的索引库close掉,close之后这个索引库里面的索引数据就不支持读写操作了,close并不会删除索引库里面的数据,后期想要重新读写这个索引库里面的数据的话,可以通过open把索引库打开。 |
1 | 注意:这些close之后的索引库需要从索引库别名中移除掉,否则会导致无法使用从索引库别名查询数据,因为这个索引库别名中映射的有已经close掉的索引库。 |
1 | 接下来将log_20260301索引库重新open(打开)。 |
1 | 索引库close掉之后,虽然对ES服务器没有性能损耗了,但是对ES集群的磁盘占用还是存在的,所以可以根据需求,将一年以前的索引库彻底删除掉。 |
ES SQL
1 | 针对ES中的结构化数据,使用SQL实现聚合统计会很方便,可以减少很多工作量。 |
ES SQL命令行下的使用
1 | [es@bigdata01 elasticsearch-7.13.4]$ bin/elasticsearch-sql-cli http://bigdata01:9200 |
1 | 如果想要实现模糊查询,使用sql中的like是否可行? |
1 | like这种方式其实就是普通的查询了,无法实现分词查询。 |
1 | 退出ES SQL命令行,需要输入exit; |
RestAPI下ES SQL的使用。
1 | 查询user索引库中的数据,根据age倒序排序,获取前5条数据。 |
JDBC操作ES SQL
1 | 首先添加ES sql-jdbc的依赖。 |
1 | package com.imooc.es; |
1 | 注意:jdbc这种方式目前无法免费使用,需要购买授权。 |
1 | 所以在工作中常用的是RestAPI这种方式。 |
ES优化策略
1 | 1.ES中Too many open files的问题。 |

1 | ES在查询索引库里面数据的时候需要读取所有的索引片段,如果索引库中数据量比较多,那么ES在查询的时候就需要读取很多索引片段文件,此时可能就会达到Linux系统的极限,因为Linux会限制系统内最大文件打开数。 |
1 | 2.索引合并优化,清除标记为删除状态的索引数据。 |
1 | 3.分片和副本个数调整 |
1 | 4.初始化数据时,建议将副本数设置为0。 |
1 | 5. 针对不使用的index,建议close,减少性能损耗。 |
1 | 6.调整ES的JVM内存大小,单个ES实例最大不超过32G。 |