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;
}
}