当前位置: 首页 > article >正文

记录一个ES分词器不生效的解决过程

问题背景

商城项目,其中商品查询检索使用的是ES, 但存在某些商品查询不到的问题
例如:某商品名包含AA_BBB这样的关键词,但是搜索"AA"不能查询到该商品,但是将商品名修改为AA BBB后就能查询到了.
怀疑是分词的问题,但看代码,在创建ES索引时在对应字段上也定义了分词器,但是不知道什么原因不好用,于是开始了问题调查

解决过程

解决过程比较坎坷,调查问题时由于我不怎么会ES,所以只能边搜索边调查,好在最后解决了问题.由于时间过去很久了,没留存截图和代码,只能简要复述一下
查询字段: name
查询条件: AA
商品名为 AA_BBB时无法查询出,改为AA BBB可以查询到

  1. 还原DSL,尝试简化条件后检索
    由于是业务相关的检索,调用接口查询ES时必然会除了关键词还有一些其他的查询条件,比如排序啊,分页啊,价格区间等等,所以结合日志还原DSL,省略掉其他的查询条件后一样无法查询到; 但直接使用ik分词对商品名进行分词后可以分词出所使用的查询关键词
  2. 仔细查阅了一下构建DSL进行搜索的代码, 未查看到疑点, 语句构建时没有异常
  3. 于是又查阅了一下ES索引创建的语句,发现在需要分词字段上都定义了分词器
    在这里插入图片描述
  4. 于是尝试分析搜索的向量命中情况,发现确实未命中对应doc,说明分词或查询确实有问题
  5. 此时已经焦头烂额了…后来,考虑到从头复现一次,于是在本地安装了一个ES,版本与测试环境相同,使用代码里的语句创建索引,并写入了两个商品,名称分别为AA BBB和AA_BBB
  6. 尝试搜索后AA后发现依然无法查询到AA_BBB的文档,很诡异. 于是尝试查看了一下所创建的索引mapping,发现name字段上并没有analyzer!查看测试环境的实际业务索引,确实也没有analyzer! 至此已经发现了问题所在——ES索引创建定义analyzer异常
  7. 既然定位到问题了,就可以找到解决问题的方法了,于是查阅了一下资料,发现系统所使用的ES7版本,在创建索引时,除了在mapping中定义字段及analyzer时,还需要在定义setting时需要增加analysis配置,否则定义的分词器会不生效,即在创建mapping时需要有 如下配置
"analysis":
    {
        "analyzer":
        {
            "ik":
            {
                "tokenizer": "ik_max_word"
            }
        }
    }
  1. 至此,重新创建索引后检查mapping,发现字段上已经定义了ik分词器了,重新写入两个doc之后发现搜索AA可以搜索到AA_BBB和AA BBB了, 问题解决!撒花!★,°:.☆( ̄▽ ̄)/$:.°★

最终解决

总结就是项目中的索引创建代码版本可能比较旧,在ES7上有些不再适用(其实发现问题后发现有的字段类型是string,但是其实ES7已经不再支持string类型,而是使用keywords和text了,所以进一步说明是ES相关代码的版本有问题)
最终解决问题的索引创建代码如下:

// 创建索引
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,并尝试优化一下现有的搜索逻辑,现在的搜索只是一个很基础的商品名称搜索,其实还有很多优化点,例如权重、字典、分词器等,开个坑吧!等我学会了来填!


http://www.kler.cn/a/559347.html

相关文章:

  • PHP课程预约小程序源码
  • ubuntu24.04无法安装向日葵,提示依赖libgconf-2-4怎么办?
  • 孜然单授权系统V2.0PHP授权系统
  • 《Mycat核心技术》第17章:实现MySQL的读写分离
  • 无人机+DeepSeek:放飞自我的智能化技术详解!
  • 【Rust中级教程】2.7. API设计原则之灵活性(flexible) Pt.3:借用 vs. 拥有、`Cow`类型、可失败和阻塞的析构函数及解决办法
  • 【行业解决方案篇八】【DeepSeek农业遥感:作物病虫害识别指南】
  • 使用 Spark NLP 实现中文实体抽取与关系提取
  • Linux之文件系统
  • vue2的计算属性
  • 【刷题】贪心算法
  • 算法笔记 03 —— 算法初步(上)
  • Java并发编程——ThreadLocal
  • openstack部署
  • HarmonyOS学习第3天: 环境搭建开启鸿蒙开发新世界
  • 聊聊istio服务网格
  • Grouped-Query Attention(GQA)详解: Pytorch实现
  • 低空经济应用场景细分赛道探索,无人机开源飞控二次开发详解
  • Web Worker:释放浏览器多线程的潜力
  • 麒麟v10 飞腾架构 配置Qt编译环境