搜索系统 基础教程

搜索 query 分析

搜索系统 索引教程

搜索系统 高级教程

搜索系统 排序层

搜索系统 笔记

如何线上搭建线程安全且高可用的 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;
    }

}