搜索系统 基础教程

搜索 query 分析

搜索系统 索引教程

搜索系统 高级教程

搜索系统 排序层

搜索系统 笔记

original icon
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.knowledgedict.com/tutorial/search-build-thread-safe-ik.html

如何线上搭建线程安全且高可用的 ik 分词器

搜索系统 笔记 搜索系统 笔记


ik 分词器是 java 编写的,它是字典树结构存储的基于词表的分词器,简单便捷及高效,常用于分词操作,那如何在搜索系统中,用 ik 来在线上搭建线程安全且高可用的 ik 分词器。

构建方式

要先明确的是,ik 分词器的 IKAnalyzer 类是线程不安全的,它在分词过程中的很多中间数据或中间变量存放在对象里,线程不安全。

引入 jar 包

首先引入 ik 相关 jar 包,可以根据不同的 lucene 版本,选择相应的版本,具体如下:

<!-- https://mvnrepository.com/artifact/com.jianggujin/IKAnalyzer-lucene -->
<dependency>
    <groupId>com.jianggujin</groupId>
    <artifactId>IKAnalyzer-lucene</artifactId>
    <version>7.0.0</version>
</dependency>

构建线程安全包装类

如上所述,IKAnalyzer 线程不安全,那我们给每个服务线程(指的是每个请求对应线程)创建 IKAnalyzer 对象,把其放在 ThreadLocal 局部变量里,这样每个请求都对应一个 IKAnalyzer 对象,这样就不会并发导致的线程安全问题了。

这里给出基于 spring 的 ik 分词器封装组件,具体代码如下:

import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * IKAnalyzer 组件
 **/
@Component("ikAnalyzer")
public class IKAnalyzerManager implements InitializingBean {

    private ThreadLocal<IKAnalyzer> ikAnalyzerThreadLocal = ThreadLocal.withInitial(() -> new IKAnalyzer(true));

    @Override
    public void afterPropertiesSet() throws Exception {

        /**
         * 这里初始化一个 IKAnalyzer 对象是为了服务启动时,加载 ik 的一些全局信息,防止第一次调用时非常慢
         */
        Analyzer analyzer = new IKAnalyzer(true);
        TokenStream ts = analyzer.tokenStream("", "初始化 IK 的字典");
        CharTermAttribute term = ts.getAttribute(CharTermAttribute.class);
        ts.reset();

        try {
            while (ts.incrementToken()) {
                log.info("init ik dictionary, {}|", term.toString());
            }
            if (ts != null) {
                ts.close();
            }
        } catch (IOException e) {
            log.error("IKAnalyzerComponent init error", e);
        } finally {
            analyzer.close();
        }
       
    }

    /**
     * 分词操作
     */
    public List<String> tokenize(String queryStr) {
        List<String> termList = new ArrayList<>();
        if (StringUtils.isBlank(queryStr)) {
            return termList;
        }
        IKAnalyzer analyzer = ikAnalyzerThreadLocal.get();
        TokenStream ts = analyzer.tokenStream("", queryStr);
        CharTermAttribute term = ts.getAttribute(CharTermAttribute.class);
        try {
            ts.reset();
            // 遍历分词数据
            while (ts.incrementToken()) {
                termList.add(term.toString());
            }
            if (ts != null) {
                ts.close();
            }
        } catch (IOException e) {
            log.error("IKAnalyzerComponent tokenize {} error", queryStr, e);
        } finally {

        }
        return termList;
    }

}