package com.hailin0.elasticsearch.index.analysis; import org.apache.logging.log4j.Logger; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.core.LowerCaseFilter; import org.apache.lucene.analysis.core.WhitespaceTokenizer; import org.apache.lucene.analysis.synonym.SynonymMap; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.analysis.AbstractTokenFilterFactory; import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.analysis.TokenizerFactory; import org.elasticsearch.indices.analysis.AnalysisModule; import java.io.IOException; import java.util.Iterator; import java.util.concurrent.*; /** * 类注释/描述 * * @author [email protected] * @date 2017-5-9 22:34 */ public class DynamicSynonymTokenFilterFactory extends AbstractTokenFilterFactory { public static Logger logger = ESLoggerFactory.getLogger("dynamic-synonym"); //配置属性 protected final String indexName; protected final String location; protected final boolean ignoreCase; protected final boolean expand; protected final String format; protected final int interval; protected SynonymMap synonymMap; /** * 每个过滤器实例产生的资源-index级别 */ protected static ConcurrentHashMap<String, CopyOnWriteArrayList<SynonymDynamicSupport>> dynamicSynonymFilters = new ConcurrentHashMap(); protected static ConcurrentHashMap<String, CopyOnWriteArrayList<ScheduledFuture>> scheduledFutures = new ConcurrentHashMap(); /** * load调度器-node级别 */ private static ScheduledExecutorService monitorPool = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("monitor-synonym-Thread"); return thread; } }); public DynamicSynonymTokenFilterFactory(IndexSettings indexSettings, Environment env, AnalysisRegistry analysisRegistry, String name, Settings settings) throws IOException { //加载配置 super(indexSettings, name, settings); this.indexName = indexSettings.getIndex().getName(); this.interval = settings.getAsInt("interval", 60); this.ignoreCase = settings.getAsBoolean("ignore_case", false); this.expand = settings.getAsBoolean("expand", true); this.format = settings.get("format", ""); this.location = settings.get("synonyms_path"); logger.info("indexName:{} synonyms_path:{} interval:{} ignore_case:{} expand:{} format:{}", indexName, location, interval, ignoreCase, expand, format); //属性检查 if (this.location == null) { throw new IllegalArgumentException( "dynamic synonym requires `synonyms_path` to be configured"); } String tokenizerName = settings.get("tokenizer", "whitespace"); AnalysisModule.AnalysisProvider<TokenizerFactory> tokenizerFactoryFactory = analysisRegistry.getTokenizerProvider(tokenizerName, indexSettings); if (tokenizerFactoryFactory == null) { throw new IllegalArgumentException("failed to find tokenizer [" + tokenizerName + "] for synonym token filter"); } final TokenizerFactory tokenizerFactory = tokenizerFactoryFactory.get(indexSettings, env, tokenizerName, AnalysisRegistry.getSettingsFromIndexSettings(indexSettings, AnalysisRegistry.INDEX_ANALYSIS_TOKENIZER + "." + tokenizerName)); Analyzer analyzer = new Analyzer() { @Override protected TokenStreamComponents createComponents(String fieldName) { Tokenizer tokenizer = tokenizerFactory == null ? new WhitespaceTokenizer() : tokenizerFactory.create(); TokenStream stream = ignoreCase ? new LowerCaseFilter(tokenizer) : tokenizer; return new TokenStreamComponents(tokenizer, stream); } }; //根据location前缀初始化同义词更新策略 SynonymFile synonymFile; if (location.startsWith("http://")) { synonymFile = new RemoteSynonymFile(env, analyzer, expand, format, location); } else { synonymFile = new LocalSynonymFile(env, analyzer, expand, format, location); } synonymMap = synonymFile.reloadSynonymMap(); //加入监控队列,定时load scheduledFutures.putIfAbsent(this.indexName, new CopyOnWriteArrayList<ScheduledFuture>()); scheduledFutures.get(this.indexName) .add(monitorPool.scheduleAtFixedRate(new Monitor(synonymFile), interval, interval, TimeUnit.SECONDS)); } /** * 每个索引下创建n个TokenStream,即create方法会调用多次,此方法有并发,被多线程调用 */ @Override public TokenStream create(TokenStream tokenStream) { DynamicSynonymFilter dynamicSynonymFilter = new DynamicSynonymFilter( tokenStream, synonymMap, ignoreCase); dynamicSynonymFilters.putIfAbsent(this.indexName, new CopyOnWriteArrayList<SynonymDynamicSupport>()); dynamicSynonymFilters.get(this.indexName).add(dynamicSynonymFilter); // fst is null means no synonyms return synonymMap.fst == null ? tokenStream : dynamicSynonymFilter; } /** * 清理同义词资源 */ public static void closeIndDynamicSynonym(String indexName) { CopyOnWriteArrayList<ScheduledFuture> futures = scheduledFutures.remove(indexName); if (futures != null) { for (ScheduledFuture sf : futures) { sf.cancel(true); } } dynamicSynonymFilters.remove(indexName); logger.info("closeDynamicSynonym! indexName:{} scheduledFutures.size:{} dynamicSynonymFilters.size:{}", indexName, scheduledFutures.size(), dynamicSynonymFilters.size()); } /** * 清理插件资源 */ public static void closeDynamicSynonym() { dynamicSynonymFilters.clear(); scheduledFutures.clear(); monitorPool.shutdownNow(); } /** * 监控逻辑 * * @author [email protected] * @createDate 2016年9月24日 */ public class Monitor implements Runnable { private SynonymFile synonymFile; public Monitor(SynonymFile synonymFile) { this.synonymFile = synonymFile; } @Override public void run() { try { if (synonymFile.isNeedReloadSynonymMap()) { SynonymMap newSynonymMap = synonymFile.reloadSynonymMap(); if (newSynonymMap == null || newSynonymMap.fst == null) { logger.error("Monitor thread reload remote synonym non-null! indexName:{} path:{}", indexName, synonymFile.getLocation()); return; } synonymMap = newSynonymMap; Iterator<SynonymDynamicSupport> filters = dynamicSynonymFilters.get(indexName).iterator(); while (filters.hasNext()) { filters.next().update(synonymMap); logger.info("success reload synonym success! indexName:{} path:{}", indexName, synonymFile.getLocation()); } } } catch (Exception e) { logger.error("Monitor thread reload remote synonym error! indexName:{} path:{}", indexName, synonymFile.getLocation()); } } } }