一、Cluster集羣:Cluster是一組Solr節點,邏輯上做爲一個單元進行管理,整個集羣必須使用同一套schema和SolrConfig。java
二、Node節點:一個運行Solr的JVM實例。node
三、Collection:在SolrCloud集羣中邏輯意義上的完整的索引,經常被劃分爲一個或多個Shard,這些Shard使用相同的Config Set,若是Shard數超過一個,那麼索引方案就是分佈式索引。SolrCloud容許客戶端用戶經過Collection名稱引用它,這樣用戶不須要關心分佈式檢索時須要使用的和Shard相關參數。算法
四、Core: 也就是Solr Core,一個Solr中包含一個或者多個Solr Core,每一個Solr Core能夠獨立提供索引和查詢功能,Solr Core的提出是爲了增長管理靈活性和共用資源。SolrCloud中使用的配置是在Zookeeper中的,而傳統的Solr Core的配置文件是在磁盤上的配置目錄中。apache
五、Config Set: Solr Core提供服務必須的一組配置文件,每一個Config Set有一個名字。最小須要包括solrconfig.xml和schema.xml,除此以外,依據這兩個文件的配置內容,可能還須要包含其它文件,如中文索引須要的詞庫文件。Config Set存儲在Zookeeper中,能夠從新上傳或者使用upconfig命令進行更新,可以使用Solr的啓動參數bootstrap_confdir進行初始化或更新。json
六、Shard分片: Collection的邏輯分片。每一個Shard被分紅一個或者多個replicas,經過選舉肯定哪一個是Leader。bootstrap
七、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。服務器
八、 Leader: 贏得選舉的Shard replicas,每一個Shard有多個Replicas,這幾個Replicas須要選舉來肯定一個Leader。選舉能夠發生在任什麼時候間,可是一般他們僅在某個Solr實例發生故障時纔會觸發。當進行索引操做時,SolrCloud會將索引操做請求傳到此Shard對應的leader,leader再分發它們到所有Shard的replicas。網絡
九、Zookeeper: Zookeeper提供分佈式鎖功能,這對SolrCloud是必須的,主要負責處理Leader的選舉。Solr能夠之內嵌的Zookeeper運行,也可使用獨立的Zookeeper,而且Solr官方建議最好有3個以上的主機。架構
在SolrCloud模式下Collection是訪問Cluster的入口,這個入口有什麼用呢?好比說集羣裏面有好多臺機器,那麼訪問這個集羣經過哪一個地址呢,必須有一個接口地址,Collection就是這個接口地址。可見Collection是一個邏輯存在的東西,所以是能夠跨Node的,在任意節點上均可以訪問Collection。Shard其實也是邏輯存在的,所以Shard也是能夠跨Node的; 1個Shard下面能夠包含0個或者多個Replication,但1個Shard下面能且只能包含一個Leader若是Shard下面的Leader掛掉了,會從Replication裏面再選舉一個Leader。 併發
圖中所示爲擁有4個Solr節點的集羣,索引分佈在兩個Shard裏面,每一個Shard包含兩個Solr節點,一個是Leader節點,一個是Replica節點,此外集羣中有一個負責維護集羣狀態信息的Overseer節點,它是一個總控制器。集羣的全部狀態信息都放在Zookeeper集羣中統一維護。從圖中還能夠看到,任何一個節點均可以接收索引建立或者更新的請求,而後再將這個請求轉發到索引文檔所應該屬於的那個Shard的Leader節點,Leader節點更新結束完成後,最後將版本號和文檔轉發給同屬於一個Shard的replicas節點。
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內部協調數據分發(內部使用一致性哈希)。
索引存儲細節:
當Solr客戶端發送add/update請求給CloudSolrServer,CloudSolrServer會鏈接至Zookeeper獲取當前SolrCloud的集羣狀態,並會在/clusterstate.json 和/live_nodes中註冊watcher,便於監視Zookeeper和SolrCloud,這樣作的好處有如下兩點:
一、CloudSolrServer獲取到SolrCloud的狀態後,它可直接將document發往SolrCloud的leader,從而下降網絡轉發消耗。
二、註冊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值的獲取提出瞭如下兩個要求:
一、hash計算速度必須快,由於hash計算是分佈式建索引的第一步。
二、 hash值必須能均勻的分佈於每個shard,若是有一個shard的document數量大於另外一個shard,那麼在查詢的時候前一個shard所花的時間就會大於後一個,SolrCloud的查詢是先分後彙總的過程,也就是說最後每個shard查詢完畢纔算完畢,因此SolrCloud的查詢速度是由最慢的shard的查詢速度決定的。
基於以上兩點,SolrCloud採用了MurmurHash 算法以提升hash計算速度和hash值的均勻分佈。
更新索引:
一、 Leader接受到update請求後,先將update信息存放到本地的update log,同時Leader還會給document分配新的version,對於已存在的document,若是新的版本高就會拋棄舊版本,最後發送至replica。
二、一旦document通過驗證以及加入version後,就會並行的被轉發至全部上線的replica。SolrCloud並不會關注那些已經下線的replica,由於當他們上線時候會有recovery進程對他們進行恢復。若是轉發的replica處於recovering狀態,那麼這個replica就會把update放入updatetransaction 日誌。
三、當leader接受到全部的replica的反饋成功後,它纔會反饋客戶端成功。只要shard中有一個replica是active的,Solr就會繼續接受update請求。這一策略實際上是犧牲了一致性換取了寫入的有效性。這其中有一個重要參數:leaderVoteWait參數,它表示當只有一個replica時候,這個replica會進入recovering狀態並持續一段時間等待leader的從新上線。若是在這段時間內leader沒有上線,那麼他就會轉成leader,其中可能會有一些document丟失。固然可使用majority quorum來避免這個狀況,這跟Zookeeper的leader選舉策略同樣,好比當多數的replica下線了,那麼客戶端的write就會失敗。
四、索引的commit有兩種,一種是softcommit,即在內存中生成segment,document是可見的(可查詢到)可是沒寫入磁盤,斷電後數據會丟失。另外一種是hardcommit,直接將數據寫入磁盤且數據可見。
一、用戶的一個查詢,能夠發送到含有該Collection的任意Solr的Server,Solr內部處理的邏輯會轉到一個Replica。
二、此Replica會基於查詢索引的方式,啓動分佈式查詢,基於索引的Shard的個數,把查詢轉爲多個子查詢,並把每一個子查詢定位到對應Shard的任意一個Replica。
三、每一個子查詢返回查詢結果。
四、最初的Replica合併子查詢,並把最終結果返回給用戶。
通常狀況下,增長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。
一、搭建zookeeper集羣步驟:須要三臺zookeeper、分別是zk一、zk二、zk3,對應的端口分別爲192.168.121.12:2181:192.168.121.12:2182:192.168.121.14:2181。
二、搭建solrcloud集羣步驟:
1)搭建四個單機版的solr服務對應的jetty,端口分別是192.168.121.12:8080、192.168.121.12:808一、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的集合配置文件的名稱
三、啓動全部的solr服務、啓動全部的zookeeper服務。
四、訪問部署的solr集羣中任意的端口服務。
五、建立索引集合指令:
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:引用的集合配置文件的名稱。
六、刪除索引集合指令:
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 中配置了,否則就報錯 // 建立文檔2 cloudSolrClient.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); } } }