天天看點

SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:

搜尋引擎大家用的比較多的應該是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

SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:

标題

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端口。

SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:

全部啟動成功後即可看見以上資訊,狀态有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資源,然後将
    SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
    放進去即可,接着nano solrhome/solr.xml編輯這個檔案,
    SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
    host配置的是tomcat所處伺服器的IP位址,Port填寫的是tomcat的端口,由于Solr是放在tomcat跑的,是以需要知道容器的位址
  • 最後配置Tomcat下bin目錄的catalina.sh檔案,加入
    JAVA_OPTS="-DzkHost=172.19.28.245:2181,10.29.25.172:2181,10.29.44.60:2181"
               
    到這步就完成了Solr清楚容器的位址,容器又知道Zookeeper叢集的位址
  • 開始啟動3台機器上面的Tomcat,然後通路Solr的WEB界面會見到下圖
    SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
    點選Add建立Collection
    SpringBoot + SolrCloud + Zookeeper 叢集搭建Zookeeper 叢集搭建篇:SolrCloud + Zookeeper 配置搭建篇:SpringBoot 操作SolrCloud篇:
    上面截圖中有一個下拉選擇config,這個是需要先去将solr config上傳至Zookeeper中才能選擇到,下面我們進入到之前下載下傳下來的Solr源碼檔案此目錄:/data/solr-7.4.0/server/scripts/cloud-scripts,然後執行
    ./zkcli.sh -zkhost ip1:2181 ip2:2181 ip3:2181 -cmd upconfig -confdir /data/tomcat8/solrhome/conf/ -confname solrConf
               
    将配置上傳至Zookeeper倉庫中,如果要去确認下是否上傳成功,那麼我們進入zookeeper/bin目錄下,執行./zkCli.sh,然後ls /configs即可看到是否有我們剛剛上傳設定的confname

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

繼續閱讀