solr搭建分布式搜索引擎
SolrCloud詳解
SolrCloud的基本概念
1、Cluster集群:Cluster是一組Solr節點,邏輯上作為一個單元進行管理,整個集群必須使用同一套schema和SolrConfig。
2、Node節點:一個運行Solr的JVM實例。
3、Collection:在SolrCloud集群中邏輯意義上的完整的索引,常常被劃分為一個或多個Shard,這些Shard使用相同的Config Set,如果Shard數超過一個,那么索引方案就是分布式索引。SolrCloud允許客戶端用戶通過Collection名稱引用它,這樣用戶不需要關心分布式檢索時需要使用的和Shard相關參數。
4、Core: 也就是Solr Core,一個Solr中包含一個或者多個Solr Core,每個Solr Core可以獨立提供索引和查詢功能,Solr Core的提出是為了增加管理靈活性和共用資源。SolrCloud中使用的配置是在Zookeeper中的,而傳統的Solr Core的配置文件是在磁盤上的配置目錄中。
5、Config Set: Solr Core提供服務必須的一組配置文件,每個Config Set有一個名字。最小需要包括solrconfig.xml和schema.xml,除此之外,依據這兩個文件的配置內容,可能還需要包含其它文件,如中文索引需要的詞庫文件。Config Set存儲在Zookeeper中,可以重新上傳或者使用upconfig命令進行更新,可使用Solr的啟動參數bootstrap_confdir進行初始化或更新。
6、Shard分片: Collection的邏輯分片。每個Shard被分成一個或者多個replicas,通過選舉確定哪個是Leader。
7、Replica: Shard的一個拷貝。每個Replica存在于Solr的一個Core中。換句話說一個SolrCore對應著一個Replica,如一個命名為“test”的collection以numShards=1創建,并且指定replicationFactor為2,這會產生2個replicas,也就是對應會有2個Core,分別存儲在不同的機器或者Solr實例上,其中一個會被命名為test_shard1_replica1,另一個命名為test_shard1_replica2,它們中的一個會被選舉為Leader。
8、 Leader: 贏得選舉的Shard replicas,每個Shard有多個Replicas,這幾個Replicas需要選舉來確定一個Leader。選舉可以發生在任何時間,但是通常他們僅在某個Solr實例發生故障時才會觸發。當進行索引操作時,SolrCloud會將索引操作請求傳到此Shard對應的leader,leader再分發它們到全部Shard的replicas。
9、Zookeeper: Zookeeper提供分布式鎖功能,這對SolrCloud是必須的,主要負責處理Leader的選舉。Solr可以以內嵌的Zookeeper運行,也可以使用獨立的Zookeeper,并且Solr官方建議最好有3個以上的主機。
SolrCloud中完整索引(Collection)的邏輯
在SolrCloud模式下Collection是訪問Cluster的入口,這個入口有什么用呢?比如說集群里面有好多臺機器,那么訪問這個集群通過哪個地址呢,必須有一個接口地址,Collection就是這個接口地址。可見Collection是一個邏輯存在的東西,因此是可以跨Node的,在任意節點上都可以訪問Collection。Shard其實也是邏輯存在的,因此Shard也是可以跨Node的;?1個Shard下面可以包含0個或者多個Replication,但1個Shard下面能且只能包含一個Leader如果Shard下面的Leader掛掉了,會從Replication里面再選舉一個Leader。??
SolrCloud索引操作的基本架構圖
圖中所示為擁有4個Solr節點的集群,索引分布在兩個Shard里面,每個Shard包含兩個Solr節點,一個是Leader節點,一個是Replica節點,此外集群中有一個負責維護集群狀態信息的Overseer節點,它是一個總控制器。集群的所有狀態信息都放在Zookeeper集群中統一維護。從圖中還可以看到,任何一個節點都可以接收索引創建或者更新的請求,然后再將這個請求轉發到索引文檔所應該屬于的那個Shard的Leader節點,Leader節點更新結束完成后,最后將版本號和文檔轉發給同屬于一個Shard的replicas節點。
SolrCloud的工作模式
SolrCloud中包含有多個Solr Instance,而每個Solr Instance中包含有多個Solr Core,Solr Core對應著一個可訪問的Solr索引資源,每個Solr Core對應著一個Replica或者Leader,這樣,當Solr Client通過Collection訪問Solr集群的時候,便可通過Shard分片找到對應的Replica即SolrCore,從而就可以訪問索引文檔了。
在SolrCloud模式下,同一個集群里所有Core的配置是統一的,Core有leader和replication兩種角色,每個Core一定屬于一個Shard,Core在Shard中扮演Leader還是replication由Solr內部Zookeeper自動協調。
訪問SolrCloud的過程:Solr Client向Zookeeper咨詢Collection的地址,Zookeeper返回存活的節點地址供訪問,插入數據的時候由SolrCloud內部協調數據分發(內部使用一致性哈希)。
SolrCloud創建索引和更新索引
索引存儲細節:
當Solr客戶端發送add/update請求給CloudSolrServer,CloudSolrServer會連接至Zookeeper獲取當前SolrCloud的集群狀態,并會在/clusterstate.json 和/live_nodes中注冊watcher,便于監視Zookeeper和SolrCloud,這樣做的好處有以下兩點:
1、CloudSolrServer獲取到SolrCloud的狀態后,它可直接將document發往SolrCloud的leader,從而降低網絡轉發消耗。
2、注冊watcher有利于建索引時候的負載均衡,比如如果有個節點leader下線了,那么CloudSolrServer會立馬得知,那它就會停止往已下線的leader發送document。
此外,CloudSolrServer 在發送document時候需要知道發往哪個shard?對于建好的SolrCloud集群,每一個shard都會有一個Hash區間,當Document進行update的時候,SolrCloud就會計算這個Document的Hash值,然后根據該值和shard的hash區間來判斷這個document應該發往哪個shard,Solr使用documentroute組件來進行document的分發。目前Solr有兩個DocRouter類的子類CompositeIdRouter(Solr默認采用的)類和ImplicitDocRouter類,當然我們也可以通過繼承DocRouter來定制化我們的document route組件。
舉例來說當Solr Shard建立時候,Solr會給每一個shard分配32bit的hash值的區間,比如SolrCloud有兩個shard分別為A,B,那么A的hash值區間就為80000000-ffffffff,B的hash值區間為0-7fffffff。默認的CompositeIdRouter hash策略會根據document ID計算出唯一的Hash值,并判斷該值在哪個shard的hash區間內。
SolrCloud對于Hash值的獲取提出了以下兩個要求:
1、hash計算速度必須快,因為hash計算是分布式建索引的第一步。
2、 hash值必須能均勻的分布于每一個shard,如果有一個shard的document數量大于另一個shard,那么在查詢的時候前一個shard所花的時間就會大于后一個,SolrCloud的查詢是先分后匯總的過程,也就是說最后每一個shard查詢完畢才算完畢,所以SolrCloud的查詢速度是由最慢的shard的查詢速度決定的。
基于以上兩點,SolrCloud采用了MurmurHash 算法以提高hash計算速度和hash值的均勻分布。
更新索引:
1、 Leader接受到update請求后,先將update信息存放到本地的update log,同時Leader還會給document分配新的version,對于已存在的document,如果新的版本高就會拋棄舊版本,最后發送至replica。
2、一旦document經過驗證以及加入version后,就會并行的被轉發至所有上線的replica。SolrCloud并不會關注那些已經下線的replica,因為當他們上線時候會有recovery進程對他們進行恢復。如果轉發的replica處于recovering狀態,那么這個replica就會把update放入updatetransaction 日志。
3、當leader接受到所有的replica的反饋成功后,它才會反饋客戶端成功。只要shard中有一個replica是active的,Solr就會繼續接受update請求。這一策略其實是犧牲了一致性換取了寫入的有效性。這其中有一個重要參數:leaderVoteWait參數,它表示當只有一個replica時候,這個replica會進入recovering狀態并持續一段時間等待leader的重新上線。如果在這段時間內leader沒有上線,那么他就會轉成leader,其中可能會有一些document丟失。當然可以使用majority quorum來避免這個情況,這跟Zookeeper的leader選舉策略一樣,比如當多數的replica下線了,那么客戶端的write就會失敗。
4、索引的commit有兩種,一種是softcommit,即在內存中生成segment,document是可見的(可查詢到)但是沒寫入磁盤,斷電后數據會丟失。另一種是hardcommit,直接將數據寫入磁盤且數據可見。
SolrCloud索引的檢索
1、用戶的一個查詢,可以發送到含有該Collection的任意Solr的Server,Solr內部處理的邏輯會轉到一個Replica。
2、此Replica會基于查詢索引的方式,啟動分布式查詢,基于索引的Shard的個數,把查詢轉為多個子查詢,并把每個子查詢定位到對應Shard的任意一個Replica。
3、每個子查詢返回查詢結果。
4、最初的Replica合并子查詢,并把最終結果返回給用戶。
SolrShard Splitting的具體過程
?一般情況下,增加Shard和Replica的數量能提升SolrCloud的查詢性能和容災能力,但是我們仍然得根據實際的document的數量,document的大小,以及建索引的并發,查詢復雜度,以及索引的增長率來統籌考慮Shard和Replica的數量。Solr依賴Zookeeper實現集群的管理,在Zookeeper中有一個Znode 是/clusterstate.json ,它存儲了當前時刻下整個集群的狀態。同時在一個集群中有且只會存在一個overseer,如果當前的overseer fail了那么SolrCloud就會選出新的一個overseer,就跟shard leader選取類似。
Shard分割的具體過程(old shard split為newShard1和newShard2)可以描述為:
a、在一個Shard的文檔到達閾值,或者接收到用戶的API命令,Solr將啟動Shard的分裂過程。
b、此時,原有的Shard仍然會提供服務,Solr將會提取原有Shard并按路由規則,轉到新的Shard做索引。同時,新加入的文檔:
1.2.用戶可以把文檔提交給任意一個Replica,并轉交給Leader。
3.Leader把文檔路由給原有Shard的每個Replica,各自做索引。
III.V. 同時,會把文檔路由給新的Shard的Leader
IV.VI.新Shard的Leader會路由文檔到自己的Replica,各自做索引,在原有文檔重新索引完成,系統會把分發文檔路由切到對應的新的Leader上,原有Shard關閉。Shard只是一個邏輯概念,所以Shard的Splitting只是將原有Shard的Replica均勻的分不到更多的Shard的更多的Solr節點上去。
搭建
搭建solr集群所需要的服務器為:192.168.121.12和192.168.121.14。
1、搭建zookeeper集群步驟:需要三臺zookeeper、分別是zk1、zk2、zk3,對應的端口分別為192.168.121.12:2181:192.168.121.12:2182:192.168.121.14:2181。
2、搭建solrcloud集群步驟:
1)搭建四個單機版的solr服務對應的jetty,端口分別是192.168.121.12:8080、192.168.121.12:8081、192.168.121.14:8080、192.168.121.14:8081。
2)設置jetty的啟動參數,在每個jetty目錄下的bin/jetty.sh,添加以下-DzkHost參數內容內容:
3)將solr配置文件上傳到zookeeper中,進行統一管理,進入到/root/soft/solr-4.10.3/example/scripts/cloud-scripts目錄中執行zkcli.sh命令:./zkcli.sh ? -zkhost ? 192.168.121.12:2181,192.168.121.12:2182,192.168.121.14:2181? ?-cmd ??upconfig ??-confdir ? /usr/local/solrcloud/solrhome/collection/conf ? -confname ? myconf(每個ip地址之間用逗號分隔)
collection為自己創建的solr集合
myconf為上傳到zookeeper的集合配置文件的名稱
3、啟動所有的solr服務、啟動所有的zookeeper服務。
4、訪問部署的solr集群中任意的端口服務。
5、創建索引集合指令:
http://192.168.121.12:8080/solr/admin/collections?action=CREATE&name=AddressBean&numShards=2&replicationFactor=2&collection.configName=AddressBean
action:表示執行的動作,是創建還是刪除。
name:創建的集合名稱。
numShards:分片的個數。
replicationFactor:每個分片有多少個副本集。
collection.configName:引用的集合配置文件的名稱。
6、刪除索引集合指令:
http://192.168.121.12:8080/solr/admin/collections?action=DELETE&name=haixin
實例
package solrtest;import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID;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.SolrQuery.ORDER; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.CloudSolrClient.Builder; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.UpdateResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; import org.aspectj.lang.annotation.Before;import net.sf.json.JSONArray; import net.sf.json.JSONObject;public class SolrCloudService {// zookeeper地址 private static final String zkHostString ="192.168.121.12:2181,192.168.121.12:2182,192.168.121.14:2181"; // collection默認名稱,比如我的solr服務器上的collection是collection2_shard1_replica1,就是去掉“_shard1_replica1”的名稱 private static String defaultCollection ="AddressBean"; // cloudSolrServer實際 private static CloudSolrClient cloudSolrClient; final static int zkClientTimeout = 30000; final static int zkConnectTimeout = 30000; // 測試方法之前構造 CloudSolrServer public static void main(String[] args) throws IOException, Exception{cloudSolrClient = new CloudSolrClient.Builder().withZkHost(zkHostString).build();cloudSolrClient.setDefaultCollection(defaultCollection); cloudSolrClient.setZkClientTimeout(zkClientTimeout); cloudSolrClient.setZkConnectTimeout(zkConnectTimeout);cloudSolrClient.connect(); JSONObject jsonObject = new JSONObject();jsonObject.put("id", UUID.randomUUID().toString());jsonObject.put("userName", "凌晨零點");//jsonObject.put("realName", "尤忠民");//jsonObject.put("department", "青島大學網絡中心技術管理二部");jsonObject.put("createTime", new Date());//jsonObject.put("jobTitle", "項目經理");jsonObject.put("gender", "女");jsonObject.put("address", "青島市嶗山區青島大學五號樓五樓");SolrCloudService.addIndexs(jsonObject);} // 向solrCloud上創建單個索引 public static void addIndexs(JSONObject jsonObject) throws SolrServerException, IOException { SolrInputDocument doc = new SolrInputDocument();doc.addField("id", jsonObject.getString("id"));doc.addField("userName", jsonObject.getString("userName"));//doc.addField("realName", jsonObject.getString("realName"));//doc.addField("department", jsonObject.getString("department"));doc.addField("createTime", new Date());//doc.addField("jobTitle", jsonObject.getString("jobTitle")); doc.addField("gender", jsonObject.getString("gender"));doc.addField("address", jsonObject.getString("address")); System.out.println(jsonObject.toString());cloudSolrClient.add(doc); cloudSolrClient.commit(); } //向solrCloud上創建多個個索引 public void addIndexs(JSONArray jsonArray) throws Exception, IOException {List<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();for(int i=0;i<jsonArray.size();i++){JSONObject jsonObject = jsonArray.getJSONObject(i);SolrInputDocument doc = new SolrInputDocument();doc.addField("name", jsonObject.getString("name"));doc.addField("id", UUID.randomUUID().toString());doc.addField("phoneType", jsonObject.getString("phoneType"));doc.addField("price", jsonObject.getString("price"));docs.add(doc);}cloudSolrClient.add(docs);// 提交cloudSolrClient.commit();}//向solrCloud上創建多個對象的索引 public void addIndexs(List<Phone> phoneList) throws Exception, IOException {cloudSolrClient.addBeans(phoneList);cloudSolrClient.commit();}//向solrCloud上創建一個對象的索引 public void addIndex(Phone phone) throws Exception, IOException {// 創建一個文檔//添加的Field 必須是在schema.xml 中配置了,不然就報錯// 創建文檔2cloudSolrClient.addBean(phone);// 提交cloudSolrClient.commit();}//通過指定的id刪除solrCloud上的索引public void delete(List<String> ids) throws Exception {cloudSolrClient.deleteById(ids);cloudSolrClient.commit(); //提交}//通過指定的域值刪除數據public void deleteByField(String fieldName,String fieldValue) throws Exception {String query = fieldName + ":" + fieldValue;cloudSolrClient.deleteByQuery(query);cloudSolrClient.commit(); //提交}//通過指定的域值更新數據public void updateByField(String id,String fieldName,String fieldValue) throws Exception {SolrInputDocument doc = new SolrInputDocument();doc.addField("id", id);doc.addField(fieldName, fieldValue);cloudSolrClient.add(doc);cloudSolrClient.commit(); //提交}//查詢索引public void query(String fieldName,String fieldValue, Integer page, Integer rows,String fieldSort) throws Exception {//添加查詢SolrQuery solrQuery = new SolrQuery();//查詢//代表Solr控制界面Query中的p字段 ,查詢字符串,這個是必須的。//如果查詢所有*:* ,根據指定字段查詢(Name:張三 AND Address:北京)//solrQuery.set(fieldName, fieldValue);solrQuery.setQuery(fieldName+":"+fieldValue);//代表fq字段 ,(filter query)過慮查詢,作用:在q查詢符合結果中同時是fq查詢符合的,//例如:q=Name:張三&fq=CreateDate:[20081001 TO 20091031],找關鍵字mm,并且CreateDate是20081001 // solrQuery.addFilterQuery("itemName:yby");//指定返回那些字段內容,用逗號或空格分隔多個。solrQuery.addField("id,phoneType,price");//按照指定的字段排序solrQuery.setSort(fieldSort, ORDER.asc);// 設置分頁 start=0就是從0開始,,rows=5當前返回5條記錄,第二頁就是變化start這個值為5就可以了。solrQuery.setStart((Math.max(page, 1) - 1) * rows);solrQuery.setRows(rows);// 設置高亮solrQuery.setHighlight(true); // 開啟高亮組件solrQuery.addHighlightField("id");// 高亮字段solrQuery.setHighlightSimplePre("<em>");// 標記,高亮關鍵字前綴solrQuery.setHighlightSimplePost("</em>");// 后綴//獲取查詢結果QueryResponse response = cloudSolrClient.query(solrQuery);//獲取查詢到的文檔SolrDocumentList docs = response.getResults();//查詢到的條數long cnt = docs.getNumFound();System.out.println("查詢到的條數\t"+cnt);//獲取查詢結果for(SolrDocument doc :docs) {String id = doc.get("id").toString();System.out.printf("%s\r\n",id);String phoneType = doc.get("phoneType").toString();System.out.printf("%s\r\n",phoneType);}} }
轉載于:https://www.cnblogs.com/youzhongmin/p/8716850.html
總結
以上是生活随笔為你收集整理的solr搭建分布式搜索引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NVisionXR_iOS教程八 ——
- 下一篇: BrowserLog——使用Chrome