问题背景
商城项目,其中商品查询检索使用的是ES, 但存在某些商品查询不到的问题
例如:某商品名包含AA_BBB 这样的关键词,但是搜索"AA"不能查询到该商品,但是将商品名修改为AA BBB 后就能查询到了.
怀疑是分词的问题,但看代码,在创建ES索引时在对应字段上也定义了分词器,但是不知道什么原因不好用,于是开始了问题调查
解决过程
解决过程比较坎坷,调查问题时由于我不怎么会ES,所以只能边搜索边调查,好在最后解决了问题.由于时间过去很久了,没留存截图和代码,只能简要复述一下
查询字段: name
查询条件: AA
商品名为 AA_BBB时无法查询出,改为AA BBB可以查询到
- 还原DSL,尝试简化条件后检索
由于是业务相关的检索,调用接口查询ES时必然会除了关键词还有一些其他的查询条件,比如排序啊,分页啊,价格区间等等,所以结合日志还原DSL,省略掉其他的查询条件后一样无法查询到; 但直接使用ik分词对商品名进行分词后可以分词出所使用的查询关键词 - 仔细查阅了一下构建DSL进行搜索的代码, 未查看到疑点, 语句构建时没有异常
- 于是又查阅了一下ES索引创建的语句,发现在需要分词字段上都定义了分词器
- 于是尝试分析搜索的向量命中情况,发现确实未命中对应doc,说明分词或查询确实有问题
- 此时已经焦头烂额了...后来,考虑到从头复现一次,于是在本地安装了一个ES,版本与测试环境相同,使用代码里的语句创建索引,并写入了两个商品,名称分别为AA BBB和AA_BBB
- 尝试搜索后AA后发现依然无法查询到AA_BBB的文档,很诡异. 于是尝试查看了一下所创建的索引mapping,发现name字段上并没有analyzer!查看测试环境的实际业务索引,确实也没有analyzer! 至此已经发现了问题所在------ES索引创建定义analyzer异常
- 既然定位到问题了,就可以找到解决问题的方法了,于是查阅了一下资料,发现系统所使用的ES7版本,在创建索引时,除了在mapping中定义字段及analyzer时,还需要在定义setting时需要增加
analysis
配置,否则定义的分词器会不生效,即在创建mapping时需要有 如下配置
json
"analysis":
{
"analyzer":
{
"ik":
{
"tokenizer": "ik_max_word"
}
}
}
- 至此,重新创建索引后检查mapping,发现字段上已经定义了ik分词器了,重新写入两个doc之后发现搜索AA可以搜索到AA_BBB和AA BBB了, 问题解决!撒花!★,° :.☆( ̄▽ ̄)/$:.°★ 。
最终解决
总结就是项目中的索引创建代码版本可能比较旧,在ES7上有些不再适用(其实发现问题后发现有的字段类型是string,但是其实ES7已经不再支持string类型,而是使用keywords和text了,所以进一步说明是ES相关代码的版本有问题)
最终解决问题的索引创建代码如下:
java
// 创建索引
public boolean createIndex(String indexName) {
if (isIndexExist(indexName)) {
log.info("Index is exits!");
return true;
}
CreateIndexResponse createIndexResponse = null;
try {
//创建映射
XContentBuilder mapping = null;
mapping = getMappingBuilder();
// 初始化settings
XContentBuilder settings = getIndexSettings();
// CreateIndexRequest request = new CreateIndexRequest(indexName).source(mapping);
CreateIndexRequest request = new CreateIndexRequest(indexName)
.settings(settings)
.mapping(mapping);
//设置创建索引超时2分钟
request.setTimeout(TimeValue.timeValueMinutes(2));
createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
} catch (IOException e) {
log.error("createIndex error ! e:{}", e);
}
return createIndexResponse.isAcknowledged();
}
// 定义mapping
private XContentBuilder getMappingBuilder(){
XContentBuilder mapping = null;
try {
// 修改type类型,string->text/keyword,decimal-?double --update on 2024/8/1 by MaYue
mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
//.startObject("m_id").field("type","keyword").endObject() //m_id:字段名,type:文本类型,analyzer 分词器类型
//该字段添加的内容,查询时将会使用ik_max_word 分词 //ik_smart ik_max_word standard
.startObject("id")
.field("type", "keyword")
.endObject()
// ES7已无string类型, 把需要分词的字段改成text
.startObject("name")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.endObject()
// .......其他字段省略
// settings单独设置,这段放到getIndexSettings方法里了
// .startObject("settings")
// //分片数
// .field("number_of_shards", 3)
// //副本数
// .field("number_of_replicas", 1)
// .endObject()
.endObject();
} catch (IOException e) {
log.error("createGoodsIndex error ! e:{}", e);
}
return mapping;
}
// 定义settings
private XContentBuilder getIndexSettings() {
XContentBuilder settings = null;
try {
settings = XContentFactory.jsonBuilder()
.startObject()
// 分片/副本设置
//分片数
.field("number_of_shards", 3)
//副本数
.field("number_of_replicas", 1)
// 增加分词器设置,否则创建mapping时指定analyzer不生效
// "analysis" : {
// "analyzer" : {
// "ik" : {
// "tokenizer" : "ik_max_word"
// }
// }
// }
.startObject("analysis")
.startObject("analyzer")
.startObject("ik")
.field("tokenizer","ik_max_word")
.endObject()
.endObject()
.endObject()
.endObject();
} catch (IOException e) {
log.error("createGoodsIndex error ! e:{}", e);
}
return settings;
}
一些吐槽
最近入职了一家新公司,是做电商类项目的,其中商品查询检索那部分用的是ES. 刚来的时候不熟悉项目,也暂时没有新的需求给我,就主要熟悉一下项目,顺便给了我一个排查一些商品无法正常搜索出来的问题.比如此博文所记录的这个问题
但是在此之前其实我完全没用过ES, 招聘JD上写了ES但是我并不会面试的时候也没问过相关问题,而我还通过了面试...所以接到这个任务后基本上是现学的,粗略看了一下ES相关名词的定义,然后面向搜索引擎调查问题,查一点,学一点,最后居然解决了问题...总有种"当你在简历上吹了牛,但是获得了这份工作"的不真实感...
后续考虑系统地学习一下ES,并尝试优化一下现有的搜索逻辑,现在的搜索只是一个很基础的商品名称搜索,其实还有很多优化点,例如权重、字典、分词器等,开个坑吧!等我学会了来填!