搜索引擎大家用的比较多的应该是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配置consul host集群 springboot zk集群配置_apache

标题

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配置consul host集群 springboot zk集群配置_apache_02

springboot配置consul host集群 springboot zk集群配置_SolrCloud_03

全部启动成功后即可看见以上信息,状态有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资源,然后将

放进去即可,接着nano solrhome/solr.xml编辑这个文件,

springboot配置consul host集群 springboot zk集群配置_SolrCloud_04

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界面会见到下图

点击Add创建Collection

springboot配置consul host集群 springboot zk集群配置_SpringBoot_05

上面截图中有一个下拉选择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;
	}
}