01_SolrCloud搭建与使用

SolrCloud 是Solr 提供的分布式搜索方案

Posted by silhouette on June 23, 2020

一、SolrCloud 概述

1.1 什么是 SolrCloud

单点的 Solr 服务的问题:

  • 能存储的数据量有限,如果是海量数据,无法存储
  • 容易出现单点故障
  • 对高并发的处理能力差

如果我们的需要处理海量数据、应对高并发请求,并且让我们的服务实现高可用。那么久必须搭建SolrCloud了。反之,如果数据量很小,请求并发低的情况下,是不需要 SolrColud 的,单点 Solr 久够了。

SolrCloud(solr云):是Solr 提供的分布式搜索方案,可以解决海量数据的分布式全文检索,因为搭建了集群,因此具备了高可用的特性,同时对数据进行主从备份,避免了单点故障问题。可以做到数据的快速恢复。并且可以动态的添加新的节点,再对数据进行平衡。

SolrCloud 是基于 Solr 和 Zookeeper 的分布式搜索方案,它的主要思想是使用 Zookeeper 作为集群的配置信息中心。它的几个特色功能:

  1. 集中式的配置信息
  2. 自动容错
  3. 近实时搜索
  4. 查询时自动负载均衡

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 集群的搭建

  1. Zookeeper需要安装 JDK 环境

  2. 把 Zookeeper 的压缩包上传到服务器并解压缩

  3. 把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
    
  4. 在每个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
    
  5. 修改每个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
    
  6. 启动每个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 ../../
    
  7. 修改批处理文件权限

    $ chmod u+x start-zookeeper-all.sh 
    
  8. 启动zookeeper并查看 zookeeper 的状态

1.3.3 Solr集群的搭建

  1. 创建四个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
    
  2. 部署 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
    
  3. 为每个 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
    
  4. 修改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 进行关联
    
  5. 配置 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 做相似配置
    
  6. 让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
    
  7. 修改 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"
    
  8. 启动每个 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 ../../
    
  9. 修改权限并启动tomcat脚本

    $  chmod u+x start-tomcat-all.sh   
    $ ./start-tomcat-all.sh 
    
  10. 访问集群

  11. 创建新的 Collection 进行分片处理。

    http://192.168.0.108:8180/solr/admin/colections?action=CREATE&name=collection2&numShards=2&replicationFactor=2
    
    • 请求返回结果

    • 效果图

  12. 删除不用的 Collection。

    http://192.168.0.108:8180/solr/admin/collections?action=DELETE&name=collection1
    
    • 请求返回结果

1..4 通过管理界面查看和操作SolrCloud

  • SolrCloud的操作命令

    • Solr采用的是类似WebService的API接口,采用Http方式进行访问,因此,其操作命令都是一些URL联接及对应参数
    1. 创建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
      
    2. 删除Collection命令

      http://192.168.0.108:8180/solr/admin/collections?action=DELETE&name=collection1
      
    3. 查询所有的Collection

       http://192.168.0.108:8180/solr/admin/collections?action=LIST
      
    4. 显示集群的状态

      http://192.168.0.108:8180/solr/admin/collections?action=CLUSTERSTATUS
      
    5. 分裂shard

      http://192.168.0.108:8180/solr/admin/collections?action=SPLITSHARD&collection=myCollection2
      &shard=shard2
      
    6. 删除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, " 导入索引库失败" );
        }
    }