IK分詞器雖然自帶詞庫java
可是在實際開發應用中對於詞庫的靈活度的要求是遠遠不夠的,IK分詞器雖然配置文件中能添加擴展詞庫,可是須要重啓ESmysql
這章就當寫一篇擴展了git
其實IK自己是支持熱更新詞庫的,可是須要我感受不是很好github
詞庫熱更新方案:sql
1:IK 原生的熱更新方案,部署一個WEB服務器,提供一個Http接口,經過Modified和tag兩個Http響應頭,來完成詞庫的熱更新數據庫
2:經過修改IK源碼支持Mysql定時更新數據apache
注意:推薦使用第二種方案,也是比較經常使用的方式,雖然第一種是官方提供的,可是官方也不建議使用服務器
方案一:IK原生方案網絡
1:外掛詞庫,就是在IK配置文件中添加擴展詞庫文件多個之間使用分號分割elasticsearch
優勢:編輯指定詞庫文件,部署比較方便
缺點:每次編輯更新後都須要重啓ES
2:遠程詞庫,就是在IK配置文件中配置一個Http請求,能夠是.dic文件,也能夠是接口,一樣多個之間使用分號分割
優勢:指定靜態文件,或者接口設置詞庫實現熱更新詞庫,不用重啓ES,是IK原生自帶的
缺點:須要經過Modified和tag兩個Http響應頭,來提供詞庫的熱更新,有時候會不生效
具體使用就不說了,在這裏具體說第二種方案
方案二:經過定時讀取Mysql完成詞庫的熱更新
首先要下載IK分詞器的源碼
網址:https://github.com/medcl/elasticsearch-analysis-ik
下載的時候必定要選對版本,保持和ES的版本一致,不然會啓動的時候報錯,版本不一致
接着把源碼導入IDEA中,並在POM.xml中添加Mysql的依賴,根據本身的Mysql版本須要添加
個人Mysql是5.6.1因此添加5的驅動包
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
而後再config目錄下建立一個新的.properties配置文件
在裏面配置Mysql的一些配置,以及咱們須要的配置
jdbc.url=jdbc:mysql://192.168.43.154:3306/es?characterEncoding=UTF-8&serverTimezone=GMT&nullCatalogMeansCurrent=true jdbc.user=root jdbc.password=root # 更新詞庫 jdbc.reload.sql=select word from hot_words # 更新停用詞詞庫 jdbc.reload.stopword.sql=select stopword as word from hot_stopwords # 從新拉取時間間隔 jdbc.reload.interval=5000
建立一個新的線程,用於調用Dictionary得reLoadMainDict()方法從新加載詞庫
package org.wltea.analyzer.dic; import org.wltea.analyzer.help.ESPluginLoggerFactory; public class HotDicReloadThread implements Runnable{ private static final org.apache.logging.log4j.Logger logger = ESPluginLoggerFactory.getLogger(Dictionary.class.getName()); @Override public void run() { while (true){ logger.info("-------從新加載mysql詞典--------"); Dictionary.getSingleton().reLoadMainDict(); } } }
修改org.wltea.analyzer.dic文件夾下的Dictionary
在Dictionary類中加載mysql驅動類
private static Properties prop = new Properties(); static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { logger.error("error", e); } }
接着,建立重Mysql中加載詞典的方法
/** * 從mysql中加載熱更新詞典 */ private void loadMySqlExtDict(){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { Path file = PathUtils.get(getDictRoot(),"jdbc-reload.properties"); prop.load(new FileInputStream(file.toFile())); logger.info("-------jdbc-reload.properties-------"); for (Object key : prop.keySet()) { logger.info("key:{}", prop.getProperty(String.valueOf(key))); } logger.info("------- 查詢詞典, sql:{}-------", prop.getProperty("jdbc.reload.sql")); // 創建mysql鏈接 connection = DriverManager.getConnection( prop.getProperty("jdbc.url"), prop.getProperty("jdbc.user"), prop.getProperty("jdbc.password") ); // 執行查詢 statement = connection.createStatement(); resultSet = statement.executeQuery(prop.getProperty("jdbc.reload.sql")); // 循環輸出查詢啊結果,添加到Main.dict中去 while (resultSet.next()) { String theWord = resultSet.getString("word"); logger.info("------熱更新詞典:{}------", theWord); // 加到mainDict裏面 _MainDict.fillSegment(theWord.trim().toCharArray()); } } catch (Exception e) { logger.error("error:{}", e); } finally { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e){ logger.error("error", e); } } }
接着,建立加載停用詞詞典方法
/** * 從mysql中加載停用詞 */ private void loadMySqlStopwordDict(){ Connection conn = null; Statement stmt = null; ResultSet rs = null; try { Path file = PathUtils.get(getDictRoot(), "jdbc-reload.properties"); prop.load(new FileInputStream(file.toFile())); logger.info("-------jdbc-reload.properties-------"); for(Object key : prop.keySet()) { logger.info("-------key:{}", prop.getProperty(String.valueOf(key))); } logger.info("-------查詢停用詞, sql:{}",prop.getProperty("jdbc.reload.stopword.sql")); conn = DriverManager.getConnection( prop.getProperty("jdbc.url"), prop.getProperty("jdbc.user"), prop.getProperty("jdbc.password")); stmt = conn.createStatement(); rs = stmt.executeQuery(prop.getProperty("jdbc.reload.stopword.sql")); while(rs.next()) { String theWord = rs.getString("word"); logger.info("------- 加載停用詞 : {}", theWord); _StopWords.fillSegment(theWord.trim().toCharArray()); } Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval")))); } catch (Exception e) { logger.error("error", e); } finally { try { if(rs != null) { rs.close(); } if(stmt != null) { stmt.close(); } if(conn != null) { conn.close(); } } catch (SQLException e){ logger.error("error:{}", e); } } }
接下來,分別在loadMainDict()方法和loadStopWordDict()方法結尾處調用
/** * 加載主詞典及擴展詞典 */ private void loadMainDict() { // 創建一個主詞典實例 _MainDict = new DictSegment((char) 0); // 讀取主詞典文件 Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_MAIN); loadDictFile(_MainDict, file, false, "Main Dict"); // 加載擴展詞典 this.loadExtDict(); // 加載遠程自定義詞庫 this.loadRemoteExtDict(); // 加載Mysql外掛詞庫 this.loadMySqlExtDict(); }
/** * 加載用戶擴展的中止詞詞典 */ private void loadStopWordDict() { // 創建主詞典實例 _StopWords = new DictSegment((char) 0); // 讀取主詞典文件 Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_STOP); loadDictFile(_StopWords, file, false, "Main Stopwords"); // 加載擴展中止詞典 List<String> extStopWordDictFiles = getExtStopWordDictionarys(); if (extStopWordDictFiles != null) { for (String extStopWordDictName : extStopWordDictFiles) { logger.info("[Dict Loading] " + extStopWordDictName); // 讀取擴展詞典文件 file = PathUtils.get(extStopWordDictName); loadDictFile(_StopWords, file, false, "Extra Stopwords"); } } // 加載遠程停用詞典 List<String> remoteExtStopWordDictFiles = getRemoteExtStopWordDictionarys(); for (String location : remoteExtStopWordDictFiles) { logger.info("[Dict Loading] " + location); List<String> lists = getRemoteWords(location); // 若是找不到擴展的字典,則忽略 if (lists == null) { logger.error("[Dict Loading] " + location + " load failed"); continue; } for (String theWord : lists) { if (theWord != null && !"".equals(theWord.trim())) { // 加載遠程詞典數據到主內存中 logger.info(theWord); _StopWords.fillSegment(theWord.trim().toLowerCase().toCharArray()); } } } // 加載Mysql停用詞詞庫 this.loadMySqlStopwordDict(); }
最後在initial()方法中啓動更新線程
/** * 詞典初始化 因爲IK Analyzer的詞典採用Dictionary類的靜態方法進行詞典初始化 * 只有當Dictionary類被實際調用時,纔會開始載入詞典, 這將延長首次分詞操做的時間 該方法提供了一個在應用加載階段就初始化字典的手段 * * @return Dictionary */ public static synchronized void initial(Configuration cfg) { if (singleton == null) { synchronized (Dictionary.class) { if (singleton == null) { singleton = new Dictionary(cfg); singleton.loadMainDict(); singleton.loadSurnameDict(); singleton.loadQuantifierDict(); singleton.loadSuffixDict(); singleton.loadPrepDict(); singleton.loadStopWordDict(); // 執行更新mysql詞庫的線程 new Thread(new HotDicReloadThread()).start(); if(cfg.isEnableRemoteDict()){ // 創建監控線程 for (String location : singleton.getRemoteExtDictionarys()) { // 10 秒是初始延遲能夠修改的 60是間隔時間 單位秒 pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS); } for (String location : singleton.getRemoteExtStopWordDictionarys()) { pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS); } } } } } }
而後,修改src/main/assemblies/plugin.xml文件中,加入Mysql
<dependencySet> <outputDirectory>/</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <useTransitiveFiltering>true</useTransitiveFiltering> <includes> <include>mysql:mysql-connector-java</include> </includes> </dependencySet>
源碼到此修改完成,在本身的數據庫中建立兩張新的表
建表SQL
CREATE TABLE hot_words ( id bigint(20) NOT NULL AUTO_INCREMENT, word varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '詞語', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE TABLE hot_stopwords ( id bigint(20) NOT NULL AUTO_INCREMENT, stopword varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '停用詞', PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
接下來對源碼進行打包:
打包以前檢查本身的POM.xml中的elasticsearch.version的版本,記得和本身的ES的版本對應,不然到時候會報錯
檢查完畢後,點擊IDEA右側的package進行項目打包,若是版本不對,修改版本並點擊IDEA右側的刷新同步,進行版本的更換,而後打包
打包完成後在左側項目中會出現target目錄,會看到一個zip,個人是由於解壓了,因此有文件夾
點擊右鍵在文件夾中展現,而後使用解壓工具解壓
解壓完成後,雙擊進入
先把原來ES下的plugins下的IK文件夾中的東西刪除,能夠先備份,而後把本身打包解壓后里面的東西所有拷貝到ES下的plugins下的IK文件夾中
接下來進入bin目錄下啓動就能夠了
固然按照慣例,個人啓動時不會那麼簡單的,很高興,個人報錯了,全部的坑都踩了一遍,以前的版本不對就踩了兩次
第一次是源碼下載的版本不對
第二次的ES依賴版本不對
好了說報錯:報錯只貼主要內容
第三次報錯:
Caused by: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "setContextClassLoader")
這個是JRE的類的建立設值權限不對
在jre/lib/security文件夾中有一個java.policy文件,在其grant{}中加入受權便可
permission java.lang.RuntimePermission "createClassLoader"; permission java.lang.RuntimePermission "getClassLoader"; permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "setContextClassLoader";
第四次報錯:
Caused by: java.security.AccessControlException: access denied ("java.net.SocketPermission" "192.168.43.154:3306" "connect,resolve")
這個是通訊連接等權限不對
也是,在jre/lib/security文件夾中有一個java.policy文件,在其grant{}中加入受權便可
permission java.net.SocketPermission "192.168.43.154:3306","accept"; permission java.net.SocketPermission "192.168.43.154:3306","listen"; permission java.net.SocketPermission "192.168.43.154:3306","resolve"; permission java.net.SocketPermission "192.168.43.154:3306","connect";
到此以後啓動無異常
最後就是測試了,啓動個人head插件和kibana,這兩個沒有或者不會的能夠看我以前寫的,也能夠百度
執行分詞
可是我想要 天青色
在Mysql中添加記錄
insert into hot_words(word) value("天青色");
從新執行
也好比我想要這就是一個詞 天青色等煙雨
在Mysql中添加記錄
insert into hot_words(word) value("天青色等煙雨");
再次執行
到此實現了ES定時從mysql中讀取熱詞,停用詞這個通常用的比較少,有興趣本身測測,在使用的時候,經過業務系統往數據庫熱詞表和停用詞表添加記錄就能夠了
做者:彼岸舞
時間:2020\09\13
內容關於:ElasticSearch
本文來源於網絡,只作技術分享,一律不負任何責任