搜索引擎大家用的比较多的应该是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
标题
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端口。
全部启动成功后即可看见以上信息,状态有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编辑这个文件,
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
上面截图中有一个下拉选择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;
}
}