企业级搜索solr应用

一 。solr简介

    solr是以lucene为内核开发的企业级搜索应用  应用程序可以通过http请求方式来提交索引,查询索引,提供了比lucene更丰富的查询语言,是

一个高性能,高可用环境全文搜索引擎

二 。solr安装配置

     1》下载solr安装包  solr所有版本 (http://archive.apache.org/dist/lucene/solr/) 

           这里下载 solr-5.5.4

     2》安装 解压将solr-5.5.4\server\solr-webapp下的webapp 拷贝到tomcat\webapps目录下 改名为solr 启动tomcat

       直接访问 出现404  找到tomcat/logs/localhost.2017-08-17.log 日志  出现以下异常

 

java.lang.NoClassDefFoundError: Failed to initialize Apache Solr: Could not find necessary SLF4j logging jars. 
If using Jetty, the SLF4j logging jars need to go in the jetty lib/ext directory. For other containers, 
the corresponding directory should be used. For more information, see: http://wiki.apache.org/solr/SolrLogging
	at org.apache.solr.servlet.CheckLoggingConfiguration.check(CheckLoggingConfiguration.java:27)
	at org.apache.solr.servlet.BaseSolrFilter.<clinit>(BaseSolrFilter.java:30)

可用看到缺少SLF4j包 应该去 应该去 解压包 /server/lib/ext下找到并拷贝到 tomcat/solr/lib目录下  然后重启

    继续访问 出现以下错误 

java.lang.NoSuchMethodError: javax.servlet.ServletInputStream.isFinished()Z
	org.apache.solr.servlet.SolrDispatchFilter.consumeInputFully(SolrDispatchFilter.java:284)
	org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:274)
	org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:208)

    明显是Servlet版本不一致  tomcat6不支持solr5.54 加大tomcat版本 tomcat7也不支持 换成tomcat8  启动后访问 依然错误:

org.apache.solr.common.SolrException: Error processing the request. CoreContainer is either not initialized or shutting down.
	org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:217)
	org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:208)

   是因为需要配置solrhome和solrhome的配置环境

 

   3》配置solrhome

     找到 tomcat\solr\WEB-INF\web.xml 编辑  找到以下这段(配置solrhome)  去掉注释 将第二个参数配置为本地任意一个目录即可 

    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>D:\learn\solr-5.5.4\home</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>

   找到solr解压包/server/solr目录拷贝所有文件到 以上web.xml指定的路径D:\learn\solr-5.5.4\home下 重启tomcat 访问

   http://localhost:8080/solor/index.html  或者 http://localhost:8080/solr/admin.html  

  

   4》配置core(core类似于数据库可以插入多个document(数据库表行)每个document拥有多个 field 数据库的列)

   solrhome下新建一个core目录  比如mycore

   拷贝 solr解压包下\server\solr\configsets\basic_configs到新建目录 mycore中

  进入solr管理网页 点击 core admin 添加该core

 

  点击Add core后 成功后 检查 mycore目录 发现多了 core.properties和data两个资源

 登陆solr管理网站发现 列表中多了mycore

 4》配置文件理解

    core/conf目录下的两个配置文件非常重要 

    managed-schema 主要用于配置 可以提交到该core的所有field定义,field的类型定义,唯一标识符等

    常用配置如下:

定义字段 _version_ 类型为long  indexed="true" 会进行分词索引  stored="true"表示存储到磁盘
<field name="_version_" type="long" indexed="true" stored="true"/>
定义字段 id required="true" 表示所有的document必须添加id字段 multiValued="false" 表示是否是多值字段
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 
定义动态字段 所以_i结尾的字段都可以写入到当前的core
<dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
定义唯一标识符的字段
<uniqueKey>id</uniqueKey>
定义字段类型的别名
<fieldType name="string" class="solr.StrField" sortMissingLast="true" />

  solrconfig.xml 主要用于配置solor的主要配置信息 比如lucene版本 缓存 数据目录 请求路径映射 等 

表示lucene版本
<luceneMatchVersion>5.5.4</luceneMatchVersion>
表示数据目录 默认是data目录
<dataDir>${solr.data.dir:}</dataDir> 
自动提交配置
<autoCommit> 
       当超过15000ms后自动提交所有数据
       <maxTime>${solr.autoCommit.maxTime:15000}</maxTime> 
       是否马上就可以查询到
       <openSearcher>false</openSearcher> 
</autoCommit>
表示当路径为 /select时查询所有的数据
<requestHandler name="/select" class="solr.SearchHandler">
    <!-- default values for query parameters can be specified, these
         will be overridden by parameters in the request
      -->
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <int name="rows">10</int>
     </lst>
</requestHandler>

 尝试在界面上添加数据和查询数据

 添加数据

 

 

 查询结果

 

查询的参数列表

  q表示查询的条件  字段名:值的格式 多个条件组合查询可以使用 字段:字段值 && 字段1:字段值1  也可以使用大写的AND或者OR

  fq表示filter query 过滤条件 和q是and的关系支持各种逻辑运算符 (参考https://cwiki.apache.org/confluence/display/solr/The+Standard+Query+Parser)

  sort表示排序 的字段  字段名 asc|desc 

  start 表示从第几行开始  rows表示查询的总行数

  fl表示查询显示的列 比如只需要查询 name_s,sex_i 这两列 使用,隔开

  df表示默认的查询字段 一般不设置

  Raw Query Parameters表示原始查询字段 可以使用 start=0&rows=10这种url的方式传入参数

  wt(write type)表示写入的格式 可以使用json和xml

  shards 多核同时搜索 solrhome拷贝mycore为mycore1  管理平台添加core   设置参数为 路径,路径来设置需要搜索的核

 

String shards = "localhost:8080/solr/mycore,localhost:8080/solr/mycore1"; 
query.set("shards", shards);

 

  其他参考(https://cwiki.apache.org/confluence/display/solr/Common+Query+Parameters)

 5》配置中文分词器

   默认solr 没有使用中文分词器  所有搜索的词 都是整个句子就是一个词 搜索时 将单词全部写入才能搜索或者使用* 需要配置中文分词器

目前比较好用的分词器 是IK  2012年停更 只支持到 Lucene4.7 所有 solr5.5 需要lucene5支持  需要修改部分源码来支持solr5.5

 找到 IKAnalyzer类   需要重写  protected TokenStreamComponents createComponents(String fieldName) 方法

 找到 IKTokenizer类 需要重写构造方法  public IKTokenizer(Reader in, boolean useSmart) 为  public IKTokenizer(boolean useSmart) {

 在任意项目中 使用maven 引用lucene5 和ik

 

<dependency>
		  <groupId>org.apache.lucene</groupId>
		  <artifactId>lucene-core</artifactId>
		  <version>5.3.1</version>
		</dependency>
		<dependency>
			<groupId>com.janeluo</groupId>
			<artifactId>ikanalyzer</artifactId>
			<version>2012_u6</version>
			<exclusions>
				<exclusion>
					<groupId>org.apache.lucene</groupId>
		 			 <artifactId>lucene-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

在项目中 添加完整的包名和类名 和 ik中一致 拷贝源代码


 

代码修改对应的方法即可

IKAnalyzer

/**

 *
 */
package org.wltea.analyzer.lucene;

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;

/**
 */
public final class IKAnalyzer extends Analyzer {

  private boolean useSmart;

  public boolean useSmart() {
    return useSmart;
  }

  public void setUseSmart(boolean useSmart) {
    this.useSmart = useSmart;
  }

  /**

   */
  public IKAnalyzer() {
    this(false);
  }

  /**
   */
  public IKAnalyzer(boolean useSmart) {
    super();
    this.useSmart = useSmart;
  }

  /**这里就去掉了 Reader的一个参数
   */
  @Override
  protected TokenStreamComponents createComponents(String fieldName) {
    Tokenizer _IKTokenizer = new IKTokenizer(this.useSmart());
    return new TokenStreamComponents(_IKTokenizer);
  }

}

IKTokenizer

/**
 * 
 */
package org.wltea.analyzer.lucene;

import java.io.IOException;
import java.io.Reader;

import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;

import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;


public final class IKTokenizer extends Tokenizer {


  private IKSegmenter _IKImplement;


  private final CharTermAttribute termAtt;

  private final OffsetAttribute offsetAtt;

  private final TypeAttribute typeAtt;

  private int endPosition;

  //去掉了其中Reader的第一个构造参数
  public IKTokenizer(boolean useSmart) {
    super();//去掉super中的构造参数
    offsetAtt = addAttribute(OffsetAttribute.class);
    termAtt = addAttribute(CharTermAttribute.class);
    typeAtt = addAttribute(TypeAttribute.class);
    _IKImplement = new IKSegmenter(input, useSmart);
  }

 
  @Override
  public boolean incrementToken() throws IOException {

    clearAttributes();
    Lexeme nextLexeme = _IKImplement.next();
    if (nextLexeme != null) {

      termAtt.append(nextLexeme.getLexemeText());
   
      termAtt.setLength(nextLexeme.getLength());
      
      offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
   
      endPosition = nextLexeme.getEndPosition();
  
      typeAtt.setType(nextLexeme.getLexemeTypeString());

      return true;
    }

    return false;
  }

  /*
   * (non-Javadoc)
   * @see org.apache.lucene.analysis.Tokenizer#reset(java.io.Reader)
   */
  @Override
  public void reset() throws IOException {
    super.reset();
    _IKImplement.reset(input);
  }

  @Override
  public final void end() {
    // set final offset
    int finalOffset = correctOffset(this.endPosition);
    offsetAtt.setOffset(finalOffset, finalOffset);
  }
}

将编译好的class文件替换原始jar包即可

将solrhome下 配置文件managed-schema 添加一个字段类型 使用ik分词器

<fieldType name="text_ik" class="solr.TextField" >
      <analyzer type="index" isMaxWordLength="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>   
      <analyzer type="query" isMaxWordLength="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/> 
    </fieldType>

不能修改 StrField 不支持自定义分词器

<fieldType name="string" class="solr.StrField" sortMissingLast="true" >
</fieldType>

 

然后将对应需要进行中文分词的字段使用 text_ik该字段类型 比如

<dynamicField name="*_s"  type="text_ik"  indexed="true"  stored="true" />

重启 或者 cloud环境下重新生成collection 插入数据即可实现中文分词  通过某些中文关键字搜索

 6》添加业务拓展词库 

ik默认支持拓展词库(连续词不用在分次,比如爱他美不使用就回拆分为 爱和他和美)和拓展停止词
在类路径添加配置文件IKAnalyzer.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">words/cb.txt;</entry>
    <entry key="ext_stopwords">words/stopwords.txt</entry>
</properties>

新建words目录添加cb.txt和stopwords.txt
假设cb.txt有几个新词是:爱他美,格力  cb.txt内容(一个词占一行,公司系统内部正式库中可以通过sql导出一份词库):

爱他美
格力

停止词表示遇到这样的词直接忽略 比如的 是这样的词
项目测试
添加依赖:

dependencies>
        <!-- https://mvnrepository.com/artifact/com.janeluo/ikanalyzer -->
        <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>

    </dependencies>

添加测试类

package cn.gvt;

import org.wltea.analyzer.cfg.Configuration;
import org.wltea.analyzer.cfg.DefaultConfig;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @author jiaozi<liaomin @ gvt861.com>
 * @since JDK8
 * Creation time:2019/5/7 18:33
 */
public class TestMain {
    public static void main(String[] args) throws IOException {
        String s = "JAVA一级结构";
        queryWords(s);
    }

    public static void queryWords(String query) throws IOException {
        Configuration cfg = DefaultConfig.getInstance();
        System.out.println(cfg.getMainDictionary()); // 系统默认词库
        System.out.println(cfg.getQuantifierDicionary());
        List<String> list = new ArrayList<String>();
        IKSegmenter ikSeg = null;
        Scanner scanner=new Scanner(System.in);
        while(true) {
            System.out.print("请输入您的词:");
            String next=scanner.nextLine();
            if(ikSeg==null){
                ikSeg = new IKSegmenter(new StringReader(next.trim()), true);// true 用智能分词 ,false细粒度
            }else{
                ikSeg.reset(new StringReader(next.trim()));
            }
            for (Lexeme lexeme = ikSeg.next(); lexeme != null; lexeme = ikSeg.next()) {
                System.out.print(lexeme.getLexemeText() + "|");
            }
            System.out.println();
        }

    }
}

运行:

请输入您的词:爱他美我爱你
加载扩展词典:words/cb.txt
加载扩展停止词典:words/stopwords.txt
爱他美|我爱你|

集成到solr中,将words目录和IKAnalyzer.cfg.xml丢到tomcat下sor/WEB-INF/classes目录即可

分析是否成功使用分词


 7》初始全量导入数据库数据 

  假设存在表 news表示 其中 有以下数据:

进入solr所在服务器 搜索 dataimport相关jar包
 

solr@localhost:/opt/solr$ find / -name *import*.jar
/opt/solr/dist/solr-dataimporthandler-5.5.5.jar
/opt/solr/dist/solr-dataimporthandler-extras-5.5.5.jar

将这两个jar包拷贝到  solr启动应用 webapp/lib目录下 

cp /opt/solr/dist/solr-dataimporthandler-5.5.5.jar /opt/solr/server/solr-webapp/webapp/WEB-INF/lib
cp /opt/solr/dist/solr-dataimporthandler-extras-5.5.5.jar /opt/solr/server/solr-webapp/webapp/WEB-INF/lib

同时将mysql的驱动包 丢到该目录下  

编辑core的conf/solrconfig.xml 添加

<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">  
      <lst name="defaults">  
         <str name="config">data-config.xml</str>  
      </lst>  
 </requestHandler>
 

在solrconfig.xml同一目录下 添加 data-config.xml(配置连接的数据库以及查询的sql语句  )

 <?xml version="1.0" encoding="UTF-8"?>  
<dataConfig>  
    <dataSource name="source1" type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://192.168.1.3:3306/test" user="root" password="123456" batchSize="-1" />  
<document>  
          <entity name="book" pk="newid"  dataSource="source1"   
                query="select * from  mynew" >
            <field column="newid" name=""/>  
            <field column="newtitle" name="title_ik"/>  
        </entity>
</document>  
</dataConfig>  

访问solrweb管理界面  http://ip:

 

三。solr客户端

  solr提供的solrj java客户端可以使用java来添加和查询索引 

使用maven引入solrj的依赖库

 

<!-- https://mvnrepository.com/artifact/org.apache.solr/solr-solrj -->
<dependency>
    <groupId>org.apache.solr</groupId>
    <artifactId>solr-solrj</artifactId>
    <version>5.5.3</version>
</dependency>

使用客户端操作添加和查询索引的代码

 

public class TestCrud {
	//请求的url
	public static final String url="http://localhost:8080/solr/mycore";
	/**
	 * 测试写入数据到solr
	 * @throws IOException 
	 * @throws SolrServerException 
	 */
	@Test
	public void testWriteDoc() throws SolrServerException, IOException{
		HttpSolrClient hsc=new HttpSolrClient(url);
		SolrInputDocument sid=new SolrInputDocument();
		sid.addField("id", 1);
		sid.addField("name_s","张三");
		hsc.add(sid);
		hsc.commit();
		hsc.close();
	}
	/**
	 * 测试从solr读取数据
	 * @throws IOException 
	 * @throws SolrServerException 
	 */
	@Test
	public void testReadDoc() throws SolrServerException, IOException{
		HttpSolrClient hsc=new HttpSolrClient(url);
		SolrQuery sq=new SolrQuery();
		sq.setQuery("name_s:*");
		sq.set("sort", "id asc");
		sq.setStart(0);
		sq.setRows(1);
		SolrDocumentList sdl=hsc.query(sq).getResults();
		for(SolrDocument sd:sdl){
			System.out.println(sd.getFieldValue("name_s"));
		}
		hsc.close();
	}
	/**
	 * 测试通过id删除
	 * @throws IOException 
	 * @throws SolrServerException 
	 */
	@Test
	public void testDelDoc() throws SolrServerException, IOException{
		HttpSolrClient hsc=new HttpSolrClient(url);
		hsc.deleteById("1");
		hsc.commit();
		hsc.close();
	}
}

使用javabean的方式操作

javabean定义

 

import org.apache.solr.client.solrj.beans.Field;
public class UserInfo {
	public UserInfo() {
	}
	@Field
	private String id;
	@Field
	private String name_s;
	@Field
	private int age_i;
}

执行代码如下:

 

/**
	 * 测试写入数据到solr
	 * @throws IOException 
	 * @throws SolrServerException 
	 */
	@Test
	public void testWriteDoc() throws SolrServerException, IOException{
		HttpSolrClient hsc=new HttpSolrClient(url);
		UserInfo ui=new UserInfo();
		ui.setId("2");
		ui.setName_s("李四");
		ui.setAge_i(100);
		hsc.addBean(ui);
		hsc.commit();
		hsc.close();
	}
	/**
	 * 测试从solr读取数据
	 * @throws IOException 
	 * @throws SolrServerException 
	 */
	@Test
	public void testReadDoc() throws SolrServerException, IOException{
		HttpSolrClient hsc=new HttpSolrClient(url);
		SolrQuery sq=new SolrQuery();
		sq.setQuery("name_s:*");
		sq.set("sort", "id asc");
		sq.setStart(0);
		sq.setRows(1);
		List<UserInfo> sdl=hsc.query(sq).getBeans(UserInfo.class);
		for(UserInfo sd:sdl){
			System.out.println(sd.getName_s());
		}
		hsc.close();
	}

 

四。solr集群安装

  1》集群方式:

   solr集群目前有两种方式 主从模式和solrcloud模式(推荐方式)

solrcloud通过zookeeper管理集群 通过将索引切片的方式分发到不同的后端服务器中 后端服务器 可以通过一主多从的方式来实现高可用  一主多从

通过leader的管理方式 leader负责写入  从机负责分摊并发读取  leader挂掉后 从机选举出新的leader继续进行管理

 2》solrcloud集群概念

   solrcloud分为逻辑层和物理层 

   逻辑层:

      》Cluster(表示zookeeper集群) 用于管理solrcloud的实例collection

      》Collection 表示一个solrcloud的实例 能够被切分为多个片

      》Shards (片) 一个Collection 可以被切分为多个片 片的个数决定了并发量的大小 每个片拥有多个备份 其中包括leader 负责写入  replica负责容灾和读请求

                   

  物理层:

      》 Cluster由多个solr 节点(物理机器)组成  每个节点对应linux的后台进程

      》 Node(节点) 每个节点由多个Core组成   

      》 Core 每个片在该节点的拷贝都属于一个core  可能每个片都有一份拷贝在当前机器上 当前机器 可能有多个core

      》Replica 是每个切片的一份拷贝  必须使用使用相同的配置 该配置需要写入到zookeeper中 

      

 3》solrcloud集群实现(伪集群)

   》》拷贝三份tomcat  分别修改server.xml tomcat端口  (8080,8081,8082)

   》》同单机安装拷贝solr应用到webapps目录下 修改web.xml 分别指向不同的solrhome

           比如我的配置  

 

tomcat18080D:\learn\solr-5.5.4\home
tomcat28081D:\learn\solr-5.5.4\home1
tomcat38082D:\learn\solr-5.5.4\home2

   》》拷贝 solr解压包下\server\solr到新建目录D:\learn\solr-5.5.4\home中 同时拷贝到hom1和home2     

            solr解压包下\server\solr\configsets\basic_configs到新建目录D:\learn\solr-5.5.4\home(只拷贝home 不拷贝home1和home2) 改名为

           collections1 

   》》tomcat/bin下的 catalina.cmd添加  

tomcat1下设置为:

 

set JAVA_OPTS=-Dsolr.solr.home=D:/learn/solr-5.5.4/home -Dbootstrap_confdir=D:/learn/solr-5.5.4/home/collection1/conf 
-Dcollection.configName=myconf -DnumShards=3 -DzkHost=localhost:2181

其他tomcat 设置为 

 

 

set JAVA_OPTS=-Dsolr.solr.home=D:/learn/solr-5.5.4/home  -DzkHost=localhost:2181

  》》这里假设本机开启了zookeeper 端口是2181 当然可以开启多个zookeeper地址使用,隔开(参考http://blog.csdn.net/liaomin416100569/article/details/71642091)

 

  依次启动 zookeeper  和所有的tomcat  访问 http://localhost:8080/solr/admin.html

  》》使用命令创建collection (其他操作参考 https://cwiki.apache.org/confluence/display/solr/Collections+API)

 

http://localhost:8081/solr/admin/collections?action=CREATE&name=collection1&numShards=3&replicationFactor=3&maxShardsPerNode=3&collection.configName=myconf

 

  》》查看zookeeper信息和集群分片信息

 查看zookeeper中写入的数据

   

查看分片到哪些节点

 

五。solr一些其他高级查询(参考代码)

 

package cn.et.solor;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.client.solrj.response.PivotField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.common.params.GroupParams;
import org.apache.solr.common.util.NamedList;
import org.junit.Test;
/**
 * 支持一些高级特性 比如高亮 分类 分组 mtl(相似)
{"id":"1","country_s":"美国","provice_s":"加利福尼亚州","city_s":"旧金山","age_i":"30","name_s":"John","desc_s":"John is come from austrina  John,s Dad is Johh Super"}
{"id":"2","country_s":"美国","provice_s":"加利福尼亚州","city_s":"好莱坞","age_i":"40","name_s":"Mike","desc_s":"Mike is come from austrina  Mike,s Dad  is Mike Super"}
{"id":"3","country_s":"美国","provice_s":"加利福尼亚州","city_s":"圣地牙哥","age_i":"50","name_s":"Cherry","desc_s":"Cherry is come from austrina  Cherry,s Dad  is Cherry Super"}
{"id":"4","country_s":"美国","provice_s":"德克萨斯州","city_s":"休斯顿","age_i":"60","name_s":"Miya","desc_s":"Miya is come from austrina  Miya,s Dad  is Miya Super"}
{"id":"5","country_s":"美国","provice_s":"德克萨斯州","city_s":"大学城","age_i":"70","name_s":"fubos","desc_s":"fubos is come from austrina  fubos,s Dad  is fubos Super"}
{"id":"6","country_s":"美国","provice_s":"德克萨斯州","city_s":"麦亚伦","age_i":"20","name_s":"marry","desc_s":"marry is come from austrina  marry,s Dad  is marry Super"}
{"id":"7","country_s":"中国","provice_s":"湖南省","city_s":"长沙市","age_i":"18","name_s":"张三","desc_s":"张三来自长沙市 是公务员一名"}
{"id":"8","country_s":"中国","provice_s":"湖南省","city_s":"岳阳市","age_i":"15","name_s":"李四","desc_s":"李四来自岳阳市 是一名清洁工"}
{"id":"9","country_s":"中国","provice_s":"湖南省","city_s":"株洲市","age_i":"33","name_s":"李光四","desc_s":"李光四 老家岳阳市 来自株洲 是李四的侄子"}
{"id":"10","country_s":"中国","provice_s":"广东省","city_s":"深圳市","age_i":"67","name_s":"王五","desc_s":"王五来自深圳市  是来自深圳的一名海关缉私精英"}
{"id":"11","country_s":"中国","provice_s":"广东省","city_s":"广州市","age_i":"89","name_s":"王冠宇","desc_s":"王冠宇是王五的儿子"}
 */
public class TestCloud {
	/**
	 * 连接solrcloud
	 * @return
	 */
	public CloudSolrClient getCloudSolrClient(){
		String zkHost="localhost:2181";
	    CloudSolrClient csc=new CloudSolrClient(zkHost);
	    csc.setDefaultCollection("collection1");//集合名称
	    return csc;
	}
	/**
	 * solrcloud保存 修改 删除和单机相同
	 */
	@Test
	public void save() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		UserInfo ui=new UserInfo();
		ui.setId("4");
		ui.setName_s("王五");
		ui.setAge_i(100);
		csc.addBean(ui);
		csc.commit();
		csc.close();
	}
	/**
	 * solrcloud 删除
	 */
	//@Test
	public void delete() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		csc.deleteByQuery("*:*");
		csc.commit();
		csc.close();
	}

	/**
	 * solrcloud高亮显示
	 * 必须设置中文分词器
	 */
	@Test
	public void queryHign() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		SolrQuery sq=new SolrQuery();
		sq.setQuery("desc_s:王五");
		sq.addHighlightField("desc_s");
		sq.setHighlight(true);
		sq.setHighlightSimplePre("<font color=red>");
		sq.setHighlightSimplePost("</font>");
		QueryResponse qr=csc.query(sq);
		List<UserInfo> userInfo=qr.getBeans(UserInfo.class);
		Map<String, Map<String, List<String>>> highlighting = qr.getHighlighting();
		System.out.println(highlighting);
		for(UserInfo ui:userInfo){
			System.out.println(ui.getName_s());
		}
		System.out.println(userInfo.size());
		csc.commit();
		csc.close();
	}
	
	/**
	 * Facet 面 用于对搜索的结果进行分类 
	 *  比如按国家分类   addFacetField 表示按某些字段进行分类是普通分类  结果为:
	 *   country_s
			美国:6
			中国:5
	    sq.addFacetQuery("age_i:[1 TO 20]");
		sq.addFacetQuery("age_i:[21 TO 50]");
		sq.addFacetQuery("age_i:[51 TO *]");
	    可以将多个范围值 添加到FacetQuery可以获取到这些Query的统计数量 比如
		 {age_i:[1 TO 20]=3, age_i:[20 TO 50]=5, age_i:[50 TO *]=5}	
		其他 参考 https://wiki.apache.org/solr/SimpleFacetParameters#facet.query_:_Arbitrary_Query_Faceting	
	 */
	@Test
	public void queryFacet() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		SolrQuery sq=new SolrQuery();
		
		sq.setFacet(true);
		//按字段分类 相同的归于一类
		sq.addFacetField("country_s");
		//特殊分类 添加范围
		sq.addFacetQuery("age_i:[1 TO 20]");
		sq.addFacetQuery("age_i:[21 TO 50]");
		sq.addFacetQuery("age_i:[51 TO *]");
		//这只facet字段分类的前缀
		sq.setFacetPrefix("");
		//根据 count 数量 升序和降序 也可以根据索引 
		sq.setFacetSort("count asc");
		sq.setQuery("*:*");
		QueryResponse qr=csc.query(sq);
		List<FacetField> ff=qr.getFacetFields();
		//获取到范围分类的对应统计数量
		System.out.println(qr.getFacetQuery());
		//获取到根据字段分类的对应统计数量
		for(FacetField ftmp:ff){
			System.out.println(ftmp.getName());
			List<Count> cou=ftmp.getValues();
			for (Count count : cou){
	            System.out.println(count.getName()+":"+ count.getCount());
	        }
		}
		csc.commit();
		csc.close();
	}
	
	/**
	 * Facet  参考https://wiki.apache.org/solr/SimpleFacetParameters#Pivot_.28ie_Decision_Tree.29_Faceting
	 *  可以按照多维度来进行分类  
	 *    比如按照国家分类后  再按照省份分类(国家和省份字段不要使用中文分词器 否则分类被拆成很多类别)
	 *    
	 *  结果一般为:
	 *    美国6:
	 *      加利福尼亚州3  
	 *      德克萨斯州3
	 *    中国5:
	 *      湖南省3
	 *      广东省2

	 */
	@Test
	public void queryFacetPivot() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		SolrQuery sq=new SolrQuery();
		sq.setFacet(true);
		//按国家和省份进行二维分类  同一个字符串使用,隔开
		sq.addFacetPivotField("country_s,provice_s");
		sq.setQuery("*:*");
		QueryResponse qr=csc.query(sq,SolrRequest.METHOD.POST);
		NamedList<List<PivotField>> ff=qr.getFacetPivot();
		//获取到根据字段分类的对应统计数量
		for(Map.Entry<String,List<PivotField>> me:ff){
			List<PivotField> lpf=me.getValue();
			for(PivotField pf:lpf){
				System.out.println("一级分类:"+pf.getValue()+pf.getCount()+"---->");
				List<PivotField> clpf=pf.getPivot();
				for(PivotField cpf:clpf){
					System.out.println("二级分类:"+cpf.getValue()+cpf.getCount());
				}
			}
		}
		csc.commit();
		csc.close();
	}
	/**
	 * 分组是分类的升级 同时可以获取到分组下的一部分元素(https://cwiki.apache.org/confluence/display/solr/Result+Grouping)
	   分组的字段的数据如果是集群环境 要求数据被写入到一个分片中 否则无法分组查询
	 */
	@Test
	public void queryGroup() throws IOException, SolrServerException{
		CloudSolrClient csc=getCloudSolrClient();
		SolrQuery sq=new SolrQuery();
		//sq.setParam("shards.tolerant", true);
		sq.setParam(GroupParams.GROUP,true);
		sq.setParam(GroupParams.GROUP_FIELD, "country_s");
		sq.setParam("group.ngroups", true);
		//sq.setParam(GroupParams.GROUP_LIMIT, "5");
		sq.setQuery("*:*");
		sq.setRows(10);
		QueryResponse qr=csc.query(sq);
		GroupResponse ff=qr.getGroupResponse();
		
		for(GroupCommand me:ff.getValues()){
			System.out.println(me.getName());
			 List<Group> groups=me.getValues();
			 System.out.println(groups);
			
		}
		csc.commit();
		csc.close();
	}
}

六。springboot集成solr

添加springboot的maven依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-solr</artifactId>
		</dependency>
	</dependencies>

添加实体类 

@SolrDocument(solrCoreName="mycore")
public class News {

	@Id
	@Field
	private String id;
	@Field(value="title_ik")
	private String title ;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
}

设置solr的主机信息 (application.yml) 

spring: 
  data: 
    solr: 
      host: http://192.168.1.238:8983/solr
server: 
  port: 8888      

springboot提供了基于spring jpa类似的语法操作solr 比如

package cn.ps.dao;

import java.util.List;

import org.springframework.data.repository.query.Param;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;

import cn.ps.entity.News;

public interface NewDao extends SolrCrudRepository<News, String> {
	/**
	 * 名字的规范 可以使用 findByTitle表示按照title来查 等价于  title:?0
	 *   比如findByNameAndPopularity 就是两个条件组合来查 name:?0  AND Popularity:?1
	 *   具体比如范围 大小 in等参考 springdatasolr  query-methods章节
	 *   https://docs.spring.io/spring-data/solr/docs/2.1.14.RELEASE/reference/html/#solr.query-methods
	 * @param title
	 * @return
	 */
	public List<News> findByTitle(String title);
	
	/**
	 * 如果不是特定的方法名可以使用 Query中指定查询条件 
	 * @Query中可以制定filters 查看的列 fields
	 * @param title
	 * @return
	 */
	@Query(value="title_ik:?0")
	public List<News> queryT(@Param("title")String title);
}

同时提供 SolrTemlate直接操作底层的solrj相关api
在配置类 实例化 
 

@Bean
	 public SolrTemplate solrTemplate(SolrClient client) {
	    return new SolrTemplate(client);
	 }

控制层装配 并使用

@Autowired
	private SolrTemplate solrTemplate;
	@Autowired
	private NewDao newDao;
	
	@RequestMapping("/query")
	public Iterable<News> query() {
		Iterable<News> findAll = newDao.findAll();
		return findAll;
	}
	
	
	@RequestMapping("/queryAnno")
	public List<News> queryAnno(String title) {
		return newDao.queryT(title);
	}
	
	@RequestMapping("/queryHign")
	public List<News> queryHign(String title) {
		SimpleHighlightQuery query = new SimpleHighlightQuery(new SimpleStringCriteria("title_ik:"+title));
		HighlightOptions highlightOptions = new HighlightOptions();
		highlightOptions.setSimplePrefix("<font color=red>");
		highlightOptions.setSimplePostfix("</font>");
		query.setHighlightOptions(highlightOptions);
		HighlightPage<News> page1 = solrTemplate.queryForHighlightPage(query, News.class);
		//�����б� Ҫ�
1 概述 4 1.1 企业搜索引擎方案选型 4 1.2 Solr的特性 4 1.2.1 Solr使用Lucene并且进行了扩展 4 1.2.2 Schema(模式) 5 1.2.3 查询 5 1.2.4 核心 5 1.2.5 缓存 5 1.2.6 复制 6 1.2.7 管理接口 6 1.3 Solr服务原理 6 1.3.1 索引 6 1.3.2 搜索 7 1.4 源码结构 8 1.4.1 目录结构说明 8 1.4.2 Solr home说明 9 1.4.3 solr的各包的说明 10 1.5 版本说明 11 1.5.1 1.3版本 11 1.5.2 1.4版本 12 1.6 分布式和复制 Solr 架构 13 2 Solr的安装与配置 13 2.1 在Tomcat下Solr安装 13 2.1.1 安装准备 13 2.1.2 安装过程 14 2.1.3 验证安装 15 2.2 中文分词配置 15 2.2.1 mmseg4j 15 2.2.2 paoding 19 2.3 多核(MultiCore)配置 22 2.3.1 MultiCore的配置方法 22 2.3.2 为何使用多core ? 23 2.4 配置文件说明 23 2.4.1 schema.xml 24 2.4.2 solrconfig.xml 25 3 Solr应用 29 3.1 SOLR应用概述 29 3.1.1 Solr应用模式 29 3.1.2 SOLR的使用过程说明 30 3.2 一个简单的例子 30 3.2.1 Solr Schema 设计 30 3.2.2 构建索引 30 3.2.3 搜索测试 31 3.3 搜索引擎的规划设计 32 3.3.1 定义业务模型 32 3.3.2 定制索引服务 34 3.3.3 定制搜索服务 34 3.4 搜索引擎配置 34 3.4.1 Solr Schema 设计(如何定制索引的结构?) 34 3.5 如何进行索引操作? 36 3.5.1 基本索引操作 36 3.5.2 批量索引操作 37 3.6 如何进行搜索 39 3.6.1 搜索语法 39 3.6.2 排序 42 3.6.3 字段增加权重 42 3.6.4 Solr分词器、过滤器、分析器 42 3.6.5 Solr高亮使用 46 4 SolrJ的用法 46 4.1 搜索接口的调用实例 46 4.2 Solrj的使用说明 47 4.2.1 Adding Data to Solr 47 4.2.2 Directly adding POJOs to Solr 49 4.2.3 Reading Data from Solr 51 4.3 创建查询 51 4.4 使用 SolrJ 创建索引 52 4.5 Solrj包的结构说明 53 4.5.1 CommonsHttpSolrServer 53 4.5.2 Setting XMLResponseParser 53 4.5.3 Changing other Connection Settings 53 4.5.4 EmbeddedSolrServer 54 5 Solr的实际应用测试报告 54 5.1 线下压力测试报告 54 5.2 线上环境运行报告 54 6 solr性能调优 55 6.1 Schema Design Considerations 55 6.1.1 indexed fields 55 6.1.2 stored fields 55 6.2 Configuration Considerations 55 6.2.1 mergeFactor 55 6.2.2 mergeFactor Tradeoffs 56 6.3 Cache autoWarm Count Considerations 56 6.4 Cache hit rate(缓存命中率) 56 6.5 Explicit Warming of Sort Fields 56 6.6 Optimization Considerations 56 6.7 Updates and Commit Frequency Tradeoffs 56 6.8 Query Response Compression 57 6.9 Embedded vs HTTP Post 57 6.10 RAM Usage Considerations(内存方面的考虑) 57 6.10.1 OutOfMemoryErrors 57 6.10.2 Memory allocated to the Java VM 57 7 FAQ 58 7.1 出现乱码或者查不到结果的排查方法: 58
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值