搜尋引擎大家用的比較多的應該是Solr和Elasticsearch,兩者之間的差別就不在此文做闡述了,一個支援實時查詢輕量級,一個資料結構更豐富更穩定,我公司使用的是Solr,因為單點的Solr在千萬級資料進行全量建立索引時有時會産生奔潰,為了搭建一個高可用的Solr開始了優化重構之路。
Zookeeper 叢集搭建篇:
首先搭建Solr叢集需要借助Zookeeper這個分布式排程服務工具,根據Zookeeper的奇數過半原則我們至少需要準備3台機器,我們公司采用的是阿裡雲雲主機,系統是Ubuntu 16.04系統,開始安裝配置Zookeeper(安裝JDK過程省略),下載下傳位址:Apache-Zookeeper官網下載下傳,解壓完成後進入conf目錄,指令:cp zoo-sample.cfg zoo.cfg,然後nano zoo.cfg
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TPB50dJRUT0cGVjRHZzwEMW1mY1RzRapnTtxkb5ckYplTeMZTTINGMShUYvwFd4VGdvwlMvw1ayFWbyVGdhd3PxUTNwMzMzETOxkDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
标題
initLimit=10: 對于從節點最初連接配接到主節點時的逾時時間,機關為tick值的倍數。
syncLimit=5:對于主節點與從節點進行同步操作時的逾時時間,機關為tick值的倍數。
dataDir=/tmp/zookeeper: 用于配置記憶體資料庫儲存的模糊快照的目錄。即剛剛建立的data檔案夾就是在此目錄中。檔案資訊都存放在data目錄下。
clientPort=2181: 表示用戶端所連接配接的伺服器所監聽的端口号,預設是2181。即zookeeper對外提供通路的端口号,3888是Zookeeper資料通信端口
server.1=10.43.98.6:2888:3888
server.2=10.43.98.8:2888:3888
server.3=10.43.98.18:2888:3888
然後再Zookeeper的資料存儲目錄建立myid檔案,标明本機器上的Zookeeper的序号,這個序号在叢集中必須是唯一的,一般用數字代替即可,此處3台機器各為1、2、3即可,但是切記上面的server.1必須和myid配置的1号機器IP對應上,接着開始配置環境變量nano etc/profile,在檔案最下面加入
export ZOOKEEPER_HOME=/opt/zookeeper-3.4.9
export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf
儲存後進入cd /etc目錄下,輸入source profile指令使修改生效。然後開始啟動Zookeeper進入到下載下傳包的bin目錄,執行./zkServer.sh,檢視啟動是否成功./zkServer.sh status,如果啟動失敗那麼檢視錯誤日志那裡出了問題,此處有一坑需要注意三台叢集機器的防火牆一定要對各自打開内網2181,2888,3888端口。
全部啟動成功後即可看見以上資訊,狀态有Leader和Follower
SolrCloud + Zookeeper 配置搭建篇:
上面Zookeeper叢集完成搭建後,我們需要開始講Solr加入到Zookeeper的管理中,才能完成SolrCloud的搭建,Solr7下載下傳位址:Apache-Solr7 官網下載下傳,下載下傳完成後還需要下載下傳Tomcat,我用的是Tomcat8然後解壓Solr将項目cp 到tomcat的webapp目錄下面,然後進行下面幾個步驟
- 編輯web.xml檔案,nano /data/tomcat8/webapps/solr/WEB-INF/web.xml
- 打開這段配置并改寫
<env-entry> <env-entry-name>solr/home</env-entry-name> <env-entry-value>/data/tomcat8/solrhome</env-entry-value> <env-entry-type>java.lang.String</env-entry-type> </env-entry>
- 上面這段配置提到了Solrhome,我們在tomcat目錄下mkdir solrhome目錄出來,此目錄就是上面配置的資訊
- solrhome裡面由于是我們自己建立的,内部是空目錄,我們需要去http://www.apache.org/dyn/closer.lua/lucene/solr/7.4.0 下載下傳源碼版本的Solr7資源,然後将 放進去即可,接着nano solrhome/solr.xml編輯這個檔案,
SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇: host配置的是tomcat所處伺服器的IP位址,Port填寫的是tomcat的端口,由于Solr是放在tomcat跑的,是以需要知道容器的位址SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇: - 最後配置Tomcat下bin目錄的catalina.sh檔案,加入
到這步就完成了Solr清楚容器的位址,容器又知道Zookeeper叢集的位址JAVA_OPTS="-DzkHost=172.19.28.245:2181,10.29.25.172:2181,10.29.44.60:2181"
- 開始啟動3台機器上面的Tomcat,然後通路Solr的WEB界面會見到下圖 點選Add建立Collection
SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇: 上面截圖中有一個下拉選擇config,這個是需要先去将solr config上傳至Zookeeper中才能選擇到,下面我們進入到之前下載下傳下來的Solr源碼檔案此目錄:/data/solr-7.4.0/server/scripts/cloud-scripts,然後執行SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
将配置上傳至Zookeeper倉庫中,如果要去确認下是否上傳成功,那麼我們進入zookeeper/bin目錄下,執行./zkCli.sh,然後ls /configs即可看到是否有我們剛剛上傳設定的confname./zkcli.sh -zkhost ip1:2181 ip2:2181 ip3:2181 -cmd upconfig -confdir /data/tomcat8/solrhome/conf/ -confname solrConf
SpringBoot 操作SolrCloud篇:
我們SpringBoot使用的是2.0版本,在pom.xml中加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
然後再application.properties配置檔案中加入
spring.data.solr.host=http://ip:8080/solr/
spring.data.solr.repositories.enabled=true
spring.data.solr.zk-host=ip1:2181,ip2:2181,ip3:2181
下面的使我們的SolrDao操作封裝類
package com.dao.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Resource;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.SolrRequest.METHOD;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bean.BookData;
import com.pub.FinalArgs;
import com.service.AsyncTaskService;
@Repository("solrDao")
@SuppressWarnings("all")
@Scope("prototype")
public class SolrRepository {
@Autowired
public CloudSolrClient client;
private QueryResponse response;
private SolrDocumentList results;
private static String SolrName = "BxydSolrCollection";
/**
*
*
TODO - 針對Solr進行資料查詢
@param query 查詢關鍵字
@param offset 起始位置
@param limit 每次查詢條數
@param c 實體類對象
@param bool 是否關閉Solr連結
@param qf 搜尋關鍵字比對某些字段的打分比其他的字段要高
@param pf 對于某些字段,搜尋字元串的密集度(phrase)的打分中占的比重
@param fl 指定需要傳回的字段 逗号分隔
@param fq 指定過濾條件
@return
@throws Exception
2017年10月3日
mazkc
*/
public List getSolrData(String query,String offset,String limit,String c,Boolean bool,String qf,String pf,String solrFl,String fq) throws Exception {
client.setDefaultCollection(SolrName);
ModifiableSolrParams params = new ModifiableSolrParams();
List beans = null;
//如果limit為空 或者 超過限定抓取數量 那麼傳回null
if(null == limit || "".equals(limit) || Integer.parseInt(limit) > FinalArgs.SERACH_LIMIT){
return null;
}
params.set("q", query);
params.set("wt", "json");
params.set("start", "0");
params.set("rows", limit);
params = checkNotNull(params,offset,limit,qf,pf,solrFl,fq);
response = client.query(params,METHOD.POST);
if(null == c || "".equals(c)){
beans = response.getResults();
}else{
beans = response.getBeans(Class.forName(c));
if(null == beans || beans.size() == 0){
beans = new ArrayList<>();
beans.add(Class.forName(c).newInstance());
}
}
return beans;
}
/**
*
*
TODO - 驗證Solr查詢是否有優化
@param params 參數關鍵字
@param offset 起始位置
@param limit 請求資料條數
@param qf 比對權重
@param pf 出現字數權重
@param solrFl 需要傳回的字段
@param fq 過濾哪些資料
@return
2017年10月17日
mazkc
*/
private ModifiableSolrParams checkNotNull(ModifiableSolrParams params,String offset,String limit,String qf,String pf,String solrFl,String fq){
if(null != offset && !"".equals(offset)){
params.set("start", offset);
}
if(null != limit && !"".equals(limit)){
if(Integer.parseInt(limit) >= 200){
limit = "200";
}
params.set("rows", limit);
}
if(null != qf && !"".equals(qf)){
params.set("qf", qf);
params.set("defType", "dismax");
}
if(null != pf && !"".equals(pf)){
params.set("pf", pf);
params.set("defType", "dismax");
}
if(null != solrFl && !"".equals(solrFl)){
params.set("fl", solrFl);
params.set("defType", "dismax");
}
if(null != fq && !"".equals(fq)){
params.set("fq", fq);
params.set("defType", "dismax");
}
return params;
}
/**
*
*
TODO - 針對集合資料對Solr進行資料索引
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void saveSolrDataList(List<Object> li,Boolean bool) throws Exception {
if(null != li && li.size() > 0){
client.setDefaultCollection(SolrName);
client.addBeans(li);
}
}
/**
*
*
TODO - 根據索引ID查詢solr資料
@param map
@throws Exception
2017年10月2日
mazkc
*/
public SolrDocument getSolrDataById(String id,Boolean bool) throws Exception {
client.setDefaultCollection(SolrName);
SolrDocument so = client.getById(id);
return so;
}
/**
*
*
TODO - 針對單個資料對Solr進行資料索引
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void saveSolrData(Object bean,Boolean bool) throws Exception {
if(null != bean){
client.setDefaultCollection(SolrName);
client.addBean(bean);
}
}
/**
*
*
TODO - 根據集合删除所有索引文檔
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void delSolrDataList(List<String> li,Boolean bool) throws Exception {
if(null != li && li.size() > 0){
client.setDefaultCollection(SolrName);
client.deleteById(li);
client.commit();
}
}
/**
*
*
TODO - 批量删除所有索引文檔
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void delSolrDataAll(String query,Boolean bool) throws Exception {
client.setDefaultCollection(SolrName);
client.deleteByQuery(query);
client.commit();
}
/**
*
*
TODO - 判斷搜尋總數
@param map
@throws Exception
2017年10月2日
mazkc
*/
public int checkSolrIndex(String query,boolean bool) throws Exception {
client.setDefaultCollection(SolrName);
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", query);
params.set("wt", "json");
response = client.query(params);
String str = String.valueOf(response.getResponse().get("response"));
String count = "numFound=";
return Integer.parseInt(str.substring(str.indexOf(count) + count.length(),str.indexOf(",")));
}
/**
*
*
TODO - 根據ID删除所有索引文檔
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void delSolrDataId(String id,Boolean bool) throws Exception {
client.setDefaultCollection(SolrName);
client.deleteById(id);
client.commit();
}
/**
*
*
TODO - 根據索引ID更新索引資料
@param map
@throws Exception
2017年10月2日
mazkc
*/
public void updateSolrData(String queryId,HashMap<String,Object> map,Boolean bool) throws Exception {
client.setDefaultCollection(SolrName);
SolrInputDocument doc = new SolrInputDocument();
SolrDocument sd = getSolrDataById(queryId, false);
if(null != sd){
Iterator<String> it = sd.keySet().iterator();
String key = "";
//擷取所有源資料資訊
while(it.hasNext()){
key = it.next();
doc.addField(key, sd.getFieldValue(key));
}
Iterator<String> itt = map.keySet().iterator();
//将需要更新的資料補充進源資料
while(itt.hasNext()){
key = itt.next();
doc.remove(key);
doc.addField(key, map.get(key));
}
//doc.remove("id");
doc.remove("_version_");
client.add(doc);
}
}
private SolrInputDocument checkDoc(HashMap<String,Object> map,SolrInputDocument doc){
if(null != map && null != doc){
String key = "";
Iterator<String> it = map.keySet().iterator();
while(it.hasNext()){
key = it.next();
doc.addField(key, map.get(key));
}
}
return doc;
}
}