一、SolrCloud 概述
1.1 什么是 SolrCloud
单点的 Solr 服务的问题:
- 能存储的数据量有限,如果是海量数据,无法存储
- 容易出现单点故障
- 对高并发的处理能力差
如果我们的需要处理海量数据、应对高并发请求,并且让我们的服务实现高可用。那么久必须搭建SolrCloud了。反之,如果数据量很小,请求并发低的情况下,是不需要 SolrColud 的,单点 Solr 久够了。
SolrCloud(solr云):是Solr 提供的分布式搜索方案,可以解决海量数据的分布式全文检索,因为搭建了集群,因此具备了高可用的特性,同时对数据进行主从备份,避免了单点故障问题。可以做到数据的快速恢复。并且可以动态的添加新的节点,再对数据进行平衡。
SolrCloud 是基于 Solr 和 Zookeeper 的分布式搜索方案,它的主要思想是使用 Zookeeper 作为集群的配置信息中心。它的几个特色功能:
- 集中式的配置信息
- 自动容错
- 近实时搜索
- 查询时自动负载均衡
1.2 Solr 集群的系统架构

1.2.1 物理结构
三个 Solr 实例(每个实例包括两个 Core),组成一个 SolrCloud。
1.2.2 逻辑结构
索引集合包括两个 Shard (shard1 和 shard2),shard1和shard2 分别由三个Core 组成,其中一个 Leader 连个 Replication,Leader是由 Zookeeper 选举产生,Zookeeper控制每个shard 上三个 Core 的索引数据一致,解决高可用问题。
用户发起索引请求分别从 shard1 和shard2 上获取,解决高并发问题。
1.2.3 SolrCloud 结构简介
为了实现海量数据的存储,我们会把索引进行分层(Shard),把分片后的数据存储到不同的 Solr 节点。为了保证节点数据的高可用,避免单点故障,我们又会对每一个 Shard 进行复制,产生很多副本(Replication),每一个副本对于一个 Solr 节点中的一个 core。用户访问的时候,可以访问任何一个会被自动分配到任何一个可用副本进行查询,这样久实现了负载均衡。

- Collection:在SolrCloud集群中逻辑意义上的完整的索引。一般会包含多个Shard(分片),如果大于1个分片,那么就是分布式存储,它们使用相同的配置信息
- Core:每个 Core 是 Solr 中一个独立运行单元,提供索引和搜索服务。一个shard 需要由一个 Core 或多个 Core 组成。由于 Collection由多个 shard组成所以collection一般由多个 core组成。
- Shard: Collection的逻辑分片。每个Shard被化成一个或者多个replication(副本),通过选举确定哪个是Leader(主),其它为从
- Replica: Shard的一个副本,存储在Solr集群的某一台机器中(就是一个节点),对应这台Solr的一个Core。
- Leader: 赢得选举的Shard replication。每个Shard有多个Replication,这几个Replication需要选举来确定一个Leader。选举可以发生在任何时间,但是通常他们仅在某个Solr实例发生故障时才会触发。当索引documents时,SolrCloud会传递它们到此Shard对应的leader,leader再分发它们到全部Shard的replication。
- Master&Slave:Master 是 master-slave 结构中的主结点(通常说主服务器),Slave 是 master -slave 结构 中的从结点(通常说从服务器或备服务器)。同一个 Shard 下 master 和 slave 存储的数据是 一致的,这是为了达到高可用目的。
1.3 SolrCloud 的搭建
1.3.1 需要实现的solr 集群结构

Zookeeper 作为集群的管理工具。主要起到:集群管理:容错、负载均衡。配置文件的集中管理和集群的入口。要实现 Zookeeper 高可用,需要搭建集群,建议是奇数节点。需要三个 Zookeeper服务器。
搭建 solr集群需要七台服务器:三个zookeep节点和四个tomcat阶段。
1.3.2 Zookeeper 集群的搭建
-
Zookeeper需要安装 JDK 环境
-
把 Zookeeper 的压缩包上传到服务器并解压缩
-
把zookeep复制三分
$ cd /home/silhouette $ mkdir solr-cloud $ cp -r zookeeper-3.4.5 /home/silhouette/solr-cloud/zookeeper01 $ cp -r zookeeper-3.4.5 /home/silhouette/solr-cloud/zookeeper02 $ cp -r zookeeper-3.4.5 /home/silhouette/solr-cloud/zookeeper03 -
在每个zookeeper 目录下创建data目录,并在该目录下创建一个 myid 文件,内容就是每个实例的id。例如 1、2、3….
$ mkdir /home/silhouette/solr-cloud/zookeeper01/data $ echo 1 >> /home/silhouette/solr-cloud/zookeeper01/data/myid $ mkdir /home/silhouette/solr-cloud/zookeeper02/data $ echo 2 >> /home/silhouette/solr-cloud/zookeeper02/data/myid $ mkdir /home/silhouette/solr-cloud/zookeeper03/data $ echo 3 >> /home/silhouette/solr-cloud/zookeeper03/data/myid -
修改每个zookeeper 的配置文件。
# 把 conf 目录下的 zoo_sample.cfg 文件改名为 zoo.cfg $ cp /home/silhouette/solr-cloud/zookeeper01/conf/zoo_sample.cfg /home/silhouette/solr-cloud/zookeeper01/conf/zoo.cfg $ cp /home/silhouette/solr-cloud/zookeeper02/conf/zoo_sample.cfg /home/silhouette/solr-cloud/zookeeper02/conf/zoo.cfg $ cp /home/silhouette/solr-cloud/zookeeper03/conf/zoo_sample.cfg /home/silhouette/solr-cloud/zookeeper03/conf/zoo.cfg ## 修改配置文件,修改内容如下: # 1.修改保存数据的目录 dataDir=/home/silhouette/solr-cloud/zookeeper01/data # 2.修改客户端连接zookeeper的端口保证每个实例都不冲突 clientPort=2181 # 3.集群中节点列表。1、2、3表示节点ID,IP后的第一个端口为zookeeper中的内容部通信端口,第二个位投票端口,每个端口都不能重复 server.1=192.168.0.108:2001:3001 server.2=192.168.0.108:2002:3002 server.3=192.168.0.108:2003:3003 -
启动每个zookeeper实例
$ cd /home/silhouette/solr-cloud $ vim start-zookeeper-all.sh # 添加批处理指令 cd zookeeper01/bin ./zkServer.sh start cd ../../ cd zookeeper02/bin ./zkServer.sh start cd ../../ cd zookeeper03/bin ./zkServer.sh start cd ../../ -
修改批处理文件权限
$ chmod u+x start-zookeeper-all.sh -
启动zookeeper并查看 zookeeper 的状态

1.3.3 Solr集群的搭建
-
创建四个tomcat实例。每个tomcat运行在不同的端口。
$ cp -r /home/silhouette/apache-tomcat-8.5.38 /home/silhouette/solr-cloud/tomcat01 $ cp -r /home/silhouette/apache-tomcat-8.5.38 /home/silhouette/solr-cloud/tomcat02 $ cp -r /home/silhouette/apache-tomcat-8.5.38 /home/silhouette/solr-cloud/tomcat03 $ cp -r /home/silhouette/apache-tomcat-8.5.38 /home/silhouette/solr-cloud/tomcat04 # 修改tomcat端口号:8180、8280、8380、8480 $ vim /home/silhouette/solr-cloud/tomcat01/conf/server.xml -
部署 solr 的 war 包。把单机版的solr 复制到集群的tomcat中
$ cp -r /home/silhouette/apache-tomcat-8.5.38/webapps/solr tomcat01/webapps/solr $ cp -r /home/silhouette/apache-tomcat-8.5.38/webapps/solr tomcat02/webapps/solr $ cp -r /home/silhouette/apache-tomcat-8.5.38/webapps/solr tomcat03/webapps/solr $ cp -r /home/silhouette/apache-tomcat-8.5.38/webapps/solr tomcat04/webapps/solr -
为每个 solr 实例创建一个对应的solrhome。
$ cp -r /home/silhouette/solr-index solrhome01 $ cp -r /home/silhouette/solr-index solrhome02 $ cp -r /home/silhouette/solr-index solrhome03 $ cp -r /home/silhouette/solr-index solrhome04 -
修改solr 的web.xml文件,将solrhome 关联起来
$ vim tomcat01/webapps/solr/WEB-INF/web.xml ## 将solrhome 关联起来 <env-entry> <env-entry-name>solr/home</env-entry-name> <env-entry-value>/home/silhouette/solr-cloud/solrhome01/solr</env-entry-value> <env-entry-type>java.lang.String</env-entry-type> </env-entry> #将其他的tomcat也将solrhome 进行关联 -
配置 solrCloud 相关的配置
# 每个 solrhome 下都有一个 solr.xml,把其中的 ip 及端口号配置好。注意:此处的端口号是tomcat的启动端口号 $ vim solrhome01/solr/solr.xml # 修改内容如下 <solrcloud> <str name="host">192.168.0.108</str> <int name="hostPort">8180</int> <str name="hostContext">${hostContext:solr}</str> <int name="zkClientTimeout">${zkClientTimeout:30000}</int> <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool> </solrcloud> #将其他的solrhome 做相似配置 -
让zookeeper 统一管理配置文件。需要把 solrhome/solr/collection1/conf 目录上传到 zookeeper。上传任意 solrhome 中的配置文件即可。
# Solr提供的访问Zookeeper的脚本文件 : # /home/silhouette/solr-4.10.2/example/scripts/cloud-scripts/zkcli.sh # 指定Zookeeper: # -zkhost 192.168.0.109:2181,192.168.0.112:2181,192.168.0.114:2181 # -cmd upconfig: 指定操作的命令。这里是上传配置 # 指定要上传的配置文件目录,我们上传Solr的样例中的配置 # confdir /home/silhouette/solr-index/collection1/conf # -confname solrconf :指定注册到Zookeeper中后配置文件目录名称 $ sh /home/silhouette/solr-4.10.2/example/scripts/cloud-scripts/zkcli.sh -zkhost 192.168.0.108:2181,192.168.0.108:2182,192.168.0.108:2183 -cmd upconfig -confdir /home/silhouette/solr-cloud/solrhome01/solr/collection1/conf -confname solrconf -
修改 tomcat/bin 目录下的 catalina.sh 文件,关联 solr 和 zookeeper。
$ vim tomcat01/bin/catalina.sh ## 把此配置添加到配置文件中 JAVA_OPTS="-DzkHost=192.168.11.192:2181,192.168.11.192:2182,192.168.11.192:2183" -
启动每个 tomcat 实例。要保证 zookeeper 集群是启动状态。
$ vim start-tomcat-all.sh # 编写批处理文件启动 tomcat cd tomcat01/bin ./startup.sh cd ../../ cd tomcat02/bin ./startup.sh cd ../../ cd tomcat03/bin ./startup.sh cd ../../ cd tomcat04/bin ./startup.sh cd ../../ -
修改权限并启动tomcat脚本
$ chmod u+x start-tomcat-all.sh $ ./start-tomcat-all.sh -
访问集群

-
创建新的 Collection 进行分片处理。
http://192.168.0.108:8180/solr/admin/colections?action=CREATE&name=collection2&numShards=2&replicationFactor=2-
请求返回结果

-
效果图

-
-
删除不用的 Collection。
http://192.168.0.108:8180/solr/admin/collections?action=DELETE&name=collection1-
请求返回结果

-
1..4 通过管理界面查看和操作SolrCloud
-
SolrCloud的操作命令
- Solr采用的是类似WebService的API接口,采用Http方式进行访问,因此,其操作命令都是一些URL联接及对应参数
-
创建core命令(在页面的Url中执行)
#参数说明: # name:指明collection名称 # numShards:指明分片数 # replicationFactor:指明副本数 # maxShardsPerNode: 每个节点最大分片数(默认为1) # property.schema:指定使用的schema.xml,这个文件必须在zookeeper上。 # property.config:指定使用的solrconfig.xml,这个文件必须在zookeeper上。 http://192.168.0.109:8080/solr/admin/collections?action=CREATE&name=myCollection2&numShards=2& replicationFactor=2&maxShardsPerNode=8&property.schema=schema.xml&property.config=solrconfig.xml -
删除Collection命令
http://192.168.0.108:8180/solr/admin/collections?action=DELETE&name=collection1 -
查询所有的Collection
http://192.168.0.108:8180/solr/admin/collections?action=LIST -
显示集群的状态
http://192.168.0.108:8180/solr/admin/collections?action=CLUSTERSTATUS -
分裂shard
http://192.168.0.108:8180/solr/admin/collections?action=SPLITSHARD&collection=myCollection2 &shard=shard2 -
删除shard
http://192.168.0.108:1080/solr/admin/collections?action=DELETESHARD&collection=myCollection2 &shard=shard2
-
SolrCloud测试
-
创建索引:在一台Solr上创建的索引,从其它solr服务上可以查询到

-
查询索引:从任意一台Solr上查询索引,选择任意的一个分片,都会返回一个完整的结果

-
1.5 使用SolrJ访问SolrCloud
与单机Solr相比,API仅仅是在创建SolrServer时发生了改变,其它没有变化。单机采用的是:HttpSolrServer,SolrCloud采用的是:CloudSolrServer。
-
Maven的依赖
<dependencies> <dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId> <version>4.10.2</version> </dependency> <!-- Solr底层会使用到slf4j日志系统 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.22</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- Junit单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> -
添加或修改数据
@Test public void testWrite() throws Exception{ // 创建SolrServer CloudSolrServer server = new CloudSolrServer("192.168.0.109:2181,192.168.0.112:2181,192.168.0.114:2181"); // 指定要访问的Collection名称 server.setDefaultCollection("collection1"); // 创建Document对象 SolrInputDocument document = new SolrInputDocument(); // 添加字段 document.addField("id", "1"); document.addField("title", "solrcloud写入的数据"); // 添加Document到Server server.add(document); // 提交 server.commit(); } -
删除数据
@Test public void testDelete()throws Exception{ // 创建SolrServer CloudSolrServer server = new CloudSolrServer("192.168.0.109:2181,192.168.0.112:2181,192.168.0.114:2181"); // 指定要访问的Collection名称 server.setDefaultCollection("collection1"); // 根据ID删除 server.deleteById("1"); // 提交 server.commit(); } -
查询数据
@Test public void testSearch() throws Exception { // 创建SolrServer CloudSolrServer server = new CloudSolrServer("192.168.0.109:2181,192.168.0.112:2181,192.168.0.114:2181"); // 指定要访问的Collection名称 server.setDefaultCollection("collection1"); // 查找数据 QueryResponse response = server.query(new SolrQuery("title:solrcloud")); // 以document形式解析数据 SolrDocumentList documentList = response.getResults(); // 遍历 for (SolrDocument solrDocument : documentList) { System.out.println(solrDocument.getFieldValue("id")); System.out.println(solrDocument.getFieldValue("title")); } }
1.6 Spring 整合 Solr与SolrCloud
-
配置文件:applicationContext-solr.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"> <!-- 配置一个链接单机版的solrServer --> <bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer"> <constructor-arg name="baseURL" value="http://192.168.0.108:8080/solr/"/> </bean> <!-- 配置集群版的solrServer --> <!-- <bean id="cloudSolrServer" class="org.apache.solr.client.solrj.impl.CloudSolrServer"> <constructor-arg name="zkHost" value="192.168.0.108:2181,192.168.0.108:2182,192.168.0.108:2183"/> 配置默认的collection <property name="defaultCollection" value="collection2"/> </bean> --> </beans> -
测试使用
@Autowired private SolrServer solrServer; @Autowired private CloudSolrServer cloudSolrServer; @Override public BuyResult importItemIndex() { try { // 从数据库查询数据列表 List<SearchItem> searchItemList = searchItemMapper.getSearchItemList(); // 将数据导入索引库 for (SearchItem searchItem : searchItemList) { // 创建文档对象 SolrInputDocument doc = new SolrInputDocument(); // 设置域 doc.addField("id", searchItem.getId()); doc.addField("item_title", searchItem.getTitle()); doc.addField("item_price", searchItem.getPrice()); doc.addField("item_image", searchItem.getImage()); doc.addField("item_sell_point", searchItem.getSell_point()); doc.addField("item_category_name", searchItem.getCategory_name()); // 将文档加入索引库 solrServer.add(doc); } // 提交 solrServer.commit(); return BuyResult.ok(); } catch (Exception e){ e.printStackTrace(); return BuyResult.build(500, " 导入索引库失败" ); } }