搜索引擎技术solr

  • Solr的简介
  • 工作原理
  • Docker安装Solr
  • SpringBoot整合Solr


Solr的简介

  • Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。
  • Solr是Apache软件基金会下的子项目之一。

工作原理

  • 为什么用solr,首先它会用它的方法帮我们的文章建立索引,索引就代表着快的意思,其次,它会把我们的文章进行分割成很多部分,也就是分词,它会建立这几部分和文章的对应关系,只有我们搜索的时候输入的是其中一部分,马上就能找到这部分对应的文章,想表达的意思就是还是快,所以用它。
  • solr是基于Lucence开发的企业级搜索引擎技术,而lucence的原理是倒排索引。那么什么是倒排索引呢?接下来我们就介绍一下lucence倒排索引原理。
  • 假设有两篇文章1和2:
    文章1的内容为:老超在卡子门工作,我也是。
    文章2的内容为:小超在鼓楼工作。
    由于lucence是基于关键词索引查询的,那我们首先要取得这两篇文章的关键词。如果我们把文章看成一个字符串,我们需要取得字符串中的所有单词,即分词。分词时,忽略”在“、”的“之类的没有意义的介词,以及标点符号可以过滤。
  • 我们使用Ik Analyzer实现中文分词,分词之后结果为:
    文章1:

两个solr可以公有一个索引data吗_docker


文章2:

两个solr可以公有一个索引data吗_docker_02


接下来,有了关键词后,我们就可以建立倒排索引了。上面的对应关系是:“文章号”对“文章中所有关键词”。倒排索引把这个关系倒过来,变成: “关键词”对“拥有该关键词的所有文章号”。文章1、文章2经过倒排后变成:

两个solr可以公有一个索引data吗_搜索引擎_03


通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:

a.字符位置,即记录该词是文章中第几个字符(优点是关键词亮显时定位快);

b.关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene中记录的就是这种位置。

加上出现频率和出现位置信息后,我们的索引结构变为:

两个solr可以公有一个索引data吗_搜索引擎_04

Docker安装Solr

  • 搜索Solr镜像
    docker search solr
  • 拉取Solr镜像
    docker pull solr
  • 创建一个solr容器
    docker run -d --name mysolr -p 8983:8983 solr
    -d:后台运行
    –name:别名
    -p:指定宿主机和容器的端口要映射
    设置防火墙 vim /usr/lib/firewalld/services/ssh.xml
    重启防火墙: systemctl restart firewalld
    浏览器访问:http://服务器IP:8983
  • 配置solr的solrcore
    docker exec -it mysolr bin/solr create_core -c mycollection
    命令说明:给mysolr发送一个指令,执行当前路径下的bin/solr命令,创建一个mycollection的solrcore
  • 查看solr数据卷在宿主机中对应的位置
    docker inspect mysolr
  • 配置中文分词器
  1. 进入到容器中 docker exec -it mysolr /bin/bash
  2. solr自带的中文分词器路径: /opt/solr/contrib/analysis-extras/lucene-libs/

两个solr可以公有一个索引data吗_java_05


lucene-analyzers-smartcn-8.1.1.jar

3. 在宿主机映射的路径(Mounts中的路径)中配置分词器:;

/var/lib/docker/volumes/8ffb9396c028481dedc3db2eda495f62a84875239b06127b2bffce7f14d2d16f/_data/data/mycollection/conf

4. vim solrconfig.xml

两个solr可以公有一个索引data吗_docker_06


5. vim managed-schema

<fieldType name="mytext_id" class="solr.TextField">
                  <analyzer type="index">
                      <tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
                  </analyzer>
                  <analyzer type="query">
                      <tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
                  </analyzer>
            </fieldType>

Mytext_id是分词器的名称。
org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory:是刚才配置的中文分词器
query:是查询的时候也要分词
重启solr容器 docker restart mysolr,

  • 配置solr字段域
    vim managed-schema:
<!-- 创建自定义的字段 -->
<field name="gname" type="mytext_id" indexed="true" stored="true"/>
<field name="ginfo" type="mytext_id" indexed="true" stored="true"/>
<field name="gimage" type="string" indexed="false" stored="true"/>
<field name="gprice" type="pfloat" indexed="false" stored="true"/>
<field name="gsave" type="pint" indexed="false" stored="true"/>

name:索引库中的字段名称
Indexed:当前字段是否添加到是索引,添加到索引才能进行搜索。
Stored:是否储存到索引库
name:储存到索引库的字段名称
Type:使用哪个分词器进行分词。
重启solr容器 docker restart mysolr,
注意:这里启动mysolr必须要开启防火墙,否则启动失败

SpringBoot整合Solr

  • 添加依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-solr</artifactId>
   <version>2.1.7.RELEASE</version>
</dependency>
  • 配置application.properties
dubbo.application.name=search_server
dubbo.registry.address=zookeeper://192.168.189.137:2181
dubbo.protocol.port=-1
dubbo.registry.register=true

spring.data.solr.host=http://192.168.189.137:8983/solr/mycollection
  • 索引库CRUD
@Autowired
private SolrClient solrClient;

@Test
public void add() throws IOException, SolrServerException {
   SolrInputDocument document = new SolrInputDocument();

document.addField("gname","华为荣耀20");
document.addField("ginfo","4800万超广角AI四摄 3200W美颜自拍 麒麟Kirin980全网通版8GB+128GB 幻夜黑 移动联通电信4G全面屏");
document.setField("gimage","a1.png");
document.setField("gprice",1399.00 );
document.setField("gsave",100);
document.setField("id",1);
solrClient.add(document); // 如果id存在就更新
solrClient.commit();

}
@Test
public void delete() throws IOException, SolrServerException {
   solrClient.deleteById("a7b2ef4b-2b25-4eb5-b8dd-b6a255d8ca22");
   solrClient.commit();
}
@Test
public void getById() throws IOException, SolrServerException {
   SolrDocument solrDocument = solrClient.getById("9b518e0b-e45e-4f24-a943-bc5b11d2aebf");

   // 1.属性的键值对
   Map<String, Object> fieldValueMap = solrDocument.getFieldValueMap();
   
   // 2.所有的属性名称
   Set<String> fieldNames = (Set<String>)solrDocument.getFieldNames();
   
   // 3.根据属性名称获取属性值
   Object gname = solrDocument.getFieldValue("gname");
   
   // 4.根据属性名称获取属性值,是个数组
   Collection<Object> values = solrDocument.getFieldValues("gname");
   
   // 5.根据属性名称获取属性值
   Object ginfo = solrDocument.get("ginfo");
}
@Test
   public void query() throws IOException, SolrServerException {

      SolrQuery solrQuery = new SolrQuery();
//     solrQuery.setQuery("*:*");
//    solrQuery.setQuery("gname:华为");
      solrQuery.setQuery("gname:华为 || ginfo:手机");

      QueryResponse queryResponse = solrClient.query(solrQuery);
      SolrDocumentList results = queryResponse.getResults();
      for(SolrDocument sd:results){
         System.out.println(sd.get("gname"));
         System.out.println(sd.get("ginfo"));
      }
   }
  • 关键字高亮
public List<Goods> selectByKey(String keyword) {
    SolrQuery solrQuery = null;
    List<Goods> goodsList = new ArrayList<Goods>();
    if(!StringUtils.isEmpty(keyword)){
        String  queryStr ="gname:%s || ginfo:%s";
        solrQuery = new SolrQuery(String.format(queryStr,keyword,keyword));
    }else{
        solrQuery = new SolrQuery("*:*");
    }

    // 处理高亮
    solrQuery.setHighlight(true); // 开启高亮
    solrQuery.setHighlightSimplePre("<font color ='red'>"); // 前缀
    solrQuery.setHighlightSimplePost("</font>"); // 后缀

    solrQuery.addHighlightField("ginfo"); // 设置高亮字段,可以设置多个

//        solrQuery.setStart(1); // 起始行
//        solrQuery.setRows(10); // 偏移量
//        solrQuery.setSort("id", SolrQuery.ORDER.asc); // 排序

    try {
        QueryResponse queryResponse = solrClient.query(solrQuery);
        SolrDocumentList results = queryResponse.getResults(); // 获取没有高亮的结果

        // 获取有高亮的结果<有高亮结果的ID,<高亮字段,高亮内容>>
        Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();

        for(SolrDocument sd:results){
            Goods goods = new Goods();

            String id = sd.getFieldValue("id").toString();
            String gname = sd.getFieldValue("gname").toString();
            String ginfo = sd.getFieldValue("ginfo").toString();

            goods.setGname(gname);
            goods.setGinfo(ginfo);

            if(highlighting.containsKey(id)){
                // 条件成立说明这条记录有高亮内容
                Map<String, List<String>> listMap = highlighting.get(id);
                if(listMap.containsKey("ginfo")){ // 判断这条记录中gname是否有高亮
                    String hightLiGinfo = listMap.get("ginfo").get(0); // 获取高亮内容
                    goods.setGinfo(hightLiGinfo);
                }
            }
            goodsList.add(goods);
        }
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return goodsList;
}
  • 页面显示高亮
<p class="miaoshu" th:utext="${goods.ginfo}"></p>