Solr實現全文搜索
- Solr
- Apache Solr特點
- 搜索引擎
- 搜索引擎組件
- 搜索引擎工作流程
- 分詞技術
- 中文分詞算法
- 基于字符串匹配
- 基于統計及機器學習的分詞方式
- IKAnalyzer
- 部署Solr并安裝IKAnalyzer
- Solr分析功能
- 修改managed-schema配置業務系統字段
- 復制配置到容器
- 重啟容器
- SpringBoot整合Solr
- 創建搜索服務接口
- 創建搜索服務提供者
- 創建搜索服務消費者
Solr
- Solr是一個可擴展的,可部署,搜索,存儲引擎,優化搜索大量以文本為中心的數據庫Solr是開源搜索平臺,用于構建搜索應用程序建立在Lucene(全文搜索引擎)之上Solr是企業級的,快速的和高度可擴展的,使用Solr構建的應用程序可以提供高性能,但是非常復雜Solr可以和Hadoop一起使用:由于Hadoop處理大量數據,Solr可以從大的數據源中找到所需信息.Solr不僅限于搜索,也可以用于存儲.和其它NoSQL數據庫一樣,是一種非關系數據存儲和處理技術
Apache Solr特點
Solr是Lucene的JAVA API包裝,使用Solr,就可以使用Lucene的所有功能
- RESTful API: 要與Solr通信,可以使用RESTful服務與Solr通信,可以使用XML,JSON,CSV等格式的文件作為輸入文檔,并以相同的文件格式獲取結果
- 全文搜索: Solr提供了全文搜索所需的所有功能:令牌,短語,拼寫檢查,通配符,自動完成
- 企業準備: 根據企業或組織的需要,Solr可以部署在任何類型的系統:獨立,分布式,云
- 靈活可擴展: 通過擴展Java類并進行相關配置,可以定制Solr組件
- NoSQL數據庫: Solr可以用作大數量級的NoSQL數據庫,可以沿著集群分布搜索任務
搜索引擎
- 搜索引擎:搜索引擎是龐大的互聯網資源數據庫,如網頁,新聞組,程序,圖像等有助于在網上定位信息用戶可以通過以關鍵字或短語的形式將查詢傳遞到搜索引擎中來搜索信息,然后搜索引擎搜索其數據庫并向用戶返回相關鏈接
搜索引擎組件
搜索引擎有三個組件:
- Web爬蟲: 一個收集網絡信息的軟件組件
- 數據庫: Web上的所有信息都存儲在數據庫中,包含大量的Web資源
- 搜索接口: 這個組件是用戶和數據庫之間的接口,幫助用戶搜索數據庫
搜索引擎工作流程
- 獲取原始內容: 任何搜索應用程序的第一步是收集要進行搜索的目標內容
- 構建文檔: 從原始內容構建文檔,讓搜索應用程序可以很容易的理解和解釋
- 分析文檔: 在索引開始之前,將對文檔進行分析
- 索引文檔: 當文檔被構建和分析后,下一步是對文檔建立索引,以便可以基于特定鍵而不是文檔的全部內容來檢索該文檔.索引類似于在書開始頁或末尾處的目錄索引,其中常見單詞以頁碼顯示,使得這些單詞可以快速追蹤,而不是搜索整本書
- 用于搜索的用戶接口: 當索引數據庫就緒,應用程序就可以執行搜索操作.為了幫助用戶進行搜索,應用必須提供用戶接口,用戶可以在用戶接口中輸入文本并啟動搜索過程
- 構建查詢: 當用戶做出搜索文本的請求,應用程序應該使用該文本準備查詢對象,然后可以使該查詢對象來查詢索引數據庫以獲得相關細節
- 搜索查詢: 使用查詢對象,檢查索引數據庫以獲取相關詳細信息和內容文檔
- 渲染結果: 當收到所需結果,應用程序應決定如何使用用戶界面向用戶顯示搜索結果
分詞技術
- 分詞技術: 搜索引擎針對用戶提交查詢的關鍵詞串進行的查詢處理后,根據用戶的關鍵詞串用各種匹配方法進行分詞的一種技術
中文分詞算法
基于字符串匹配
- 基于字符串匹配:即掃描字符串,如果發現字符串的子串和詞相同,就算匹配這類分詞通常會加入一些啟發式規則:正向/反向最大匹配,長詞優先等
- 基于字符串匹配算法優點:速度快都是O(n)時間復雜度實現簡單效果尚可
- 基于字符串匹配算法缺點:對歧義和未登錄詞處理不好
- ikanalyzer,paoding等就是基于字符串匹配的分詞
基于統計及機器學習的分詞方式
- 基于統計及機器學習的分詞方式:基于人工標注的詞性和統計特征,對中文進行建模. 即根據觀測到的數據(標注好的語料)對模型參數進行估計.即 訓練在分詞階段再通過模型計算各種分詞出現的概率,將概率最大的分詞結果作為最終結果常見的序列標注模型:HMM,CRF
- 基于統計及機器學習的分詞方式優點:可以很好地處理歧義和未登錄問題效果比基于字符串匹配算法更好
- 基于統計及機器學習的分詞方式缺點:需要大量的人工標注數據較慢的分詞速度
IKAnalyzer
- IKAnalyzer是一個開源的,基于Java語言開發的輕量級中文分詞工具包
- 基于文本匹配,不需要投入大量的人力進行訓練和標注
- 可以自定詞典,方便加入特定領域的詞語,能分出多粒度的結果
部署Solr并安裝IKAnalyzer
- 創建/usr/local/Docker/solr/ikanalyzer目錄
/usr/local/docker/solr 用于存放docker-compose.yml配置文件
/usr/local/docker/solr/ikanalyzer 用于存放Dockerfile鏡像配置文件
- docker-compose.yml
version: '3.1'
services:
solr:
build: ikanalyzer
restart: always
container_name: solr
ports:
- 8983:8983
volumes:
- ./solrdata:/opt/solrdata
- Dockerfile(在/usr/local/docker/solr/ikanalyzer中需要有文件:ik-analyzer-solr5-5.x.jar,solr-analyzer-ik-5.1.0.jar,ext.dic,stopword.dic,IKAnalyzer.cfg.xml,managed-schema)
FROM solr
# 創建Core
WORKDIR /opt/solr/server/solr
RUN mkdir ik_core
WORKDIR /opt/solr/server/solr/ik_core
RUN echo 'name=ik_core' > core.properties
RUN mkdir data
RUN cp -r ../configsets/sample_techproducts_configs/conf/ .
# 安裝中文分詞
WORKDIR /opt/solr/server/solr-webApp/webapp/WEB-INF/lib
ADD ik-analyzer-solr5-5.x.jar .
ADD solr-analyzer-ik-5.1.0.jar .
WORKDIR /opt/solr/server/solr-webapp/webapp/WEB-INF
ADD ext.dic .
ADD stopword.dic .
ADD IKAnalyzer.cfg.xml .
# 增加分詞配置
COPY managed-schema /opt/solr/server/solr/ik_core/conf
WORKDIR /opt/solr
- 構建鏡像: 在/usr/local/docker/solr中執行命令
docker-compose up -d
Solr分析功能
修改managed-schema配置業務系統字段
- Solr中自帶的相同字段無需再添加,其它字段需要手動添加Solr字段(通過編輯managed-schema配置文件來手動添加Solr字段)
<!-- 字段域 -->
<field name="tb_item_cid" type="plong" indexed="true" stored="true" />
<field name="tb_item_cname" type="text_ik" indexed="true" stored="true" />
<field name="tb_item_title" type="text_ik" indexed="true" stored="true" />
<field name="tb_item_sell_point" type="text_ik" indexed="true" stored="true" />
<field name="tb_item_desc" type="text_ik" indexed="true" stored="true" />
<!-- 復制域:Solr的搜索優化功能,,將多個字段復制到一個域,提高查詢效率 -->
<field name="tb_item_keywords" type="text_ik" indexed="true" stored="false" multiValued="true" />
<copyField source="tb_item_cname" dest="tb_item_keywords">
<copyField source="tb_item_title" dest="tb_item_keywords">
<copyField source="tb_item_sell_point" dest="tb_item_keywords">
<copyField source="tb_item_desc" dest="tb_item_keywords">
復制配置到容器
docker cp managed-schema solr:/opt/solr/server/solr/ik_core/conf
重啟容器
docker-compose restart
- 在Solr的Web界面可以進行CRUD操作
SpringBoot整合Solr
創建搜索服務接口
- 創建myshop-service-search-api項目,該項目只負責定義定義接口
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>myshop-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../myshop-dependencies/pom.xml</relativePath>
</parent>
<artifactId>myshop-service-search-api</artifacteId>
<packaging>jar<packaging>
</project>
- 在項目中創建SearchService接口
package com.oxford.myshop.service.search.api;
public interface SearchService {
List<TbItemResult> search(String query,int page,int rows);
}
- 創建TbItemResult用于返回Solr結果集
package com.oxford.myshop.service.search.domain;
import java.io.Serializable;
public class TbItemResult implements Serializable {
private long id;
private long tbTtemCid;
private String tbItemCname;
private String tbItemTitle;
private String tbItemSellPoint;
private String tbItemDesc;
public long getId(){
return id;
}
public void setId(long id){
this.id=id;
}
public long getTbTtemCid(){
return tbTtemCid;
}
public void setTbTtemCid(long tbTtemCid){
this.tbTtemCid=tbTtemCid;
}
public String getTbItemCname(){
return tbItemCname;
}
public void setTbItemCname(String tbItemCname){
this.tbItemCname=tbItemCname;
}
public String getTbItemTitle(){
return tbItemTitle;
}
public void setTbItemTitle(String tbItemTitle){
this.tbItemTitle=tbItemTitle;
}
public String getTbItemSellPoint(){
return tbItemSellPoint;
}
public void setTbItemSellPoint(String tbItemSellPoint){
this.tbItemSellPoint=tbItemSellPoint;
}
public String getTbItemDesc(){
return tbItemDesc;
}
public void setTbItemDesc(String tbItemDesc){
this.tbItemDesc=tbItemDesc;
}
}
創建搜索服務提供者
- 創建myshop-service-search-provider服務提供者項目
- MyShopServiceSearchProviderApplication
package com.oxford.myshop.service.search.provider;
@EnableHystrix
@EnableHystrixDashboard
@SpringBootApplication(scanBasePackages="com.oxfrod.myshop")
@MapperScan(basePackages="com.oxford.myshop.service.search.provider.mapper")
public class MyShopServiceSearchProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MyShopServiceSearchProviderApplication.class,args);
Main.main(args);
}
}
- 在項目中創建TbItemResultMapper接口用于查詢MySQL中的數據,用于插入到Solr數據庫中
package com.oxford.myshop.service.search.provider.mapper;
@Respository
public interface TbItemResultMapper {
List<TbItemResult> selectAll();
}
Spring的四大注解:
1. @Controller
2. @Service
3. @Component
4. @Repository
- 在resource中創建mapper包用于創建TbContentCategoryMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.oxford.myshop.service.search.provider.mapper.TbItemResultMapper">
<resultMap id="BaseResultMap" type="com.oxford.myshop.service.search.domainTbItemResult">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="tb_item_cid" jdbcType="BIGINT" property="tbItemCid" />
<result column="tb_item_cname" jdbcType="VARCHAR" property="tbItemCname" />
<result column="tb_item_title" jdbcType="VARCHAR" property="tbItemTitle" />
<result column="tb_item_sell_point" jdbcType="VARCHAR" property="tbItemSellPoint" />
<result column="tb_item_desc" jdbcType="VARCHAR" property="tbItemDesc" />
</reslutMap>
<select id="selectAll" resultMap="BaseResultMap">
select
a.id,
a.title as tb_item_title,
a.sell_point as tb_item_sell_point,
a.cid as tb_item_cid,
b.name as tb_item_cname,
c.item_desc as tb_item_desc
from
tb_item as a
left join tb_item_cat as b
on a.cid=b.id
left join tb_item_desc as c
on a.id=c.item_id
</select>
</mapper>
初始化Solr:
public void initSolr() {
List<TbItemResult> tbItemResult=tbItemResultMapper.selectAll();
try{
SolrInputDocument document=null;
for(TbItemResult tbItemResult:tbItemResults){
document=new SolrInputDocument();
document.addFiled("id",tbItemResult.getId());
document.addFiled("tb_item_cid",tbItemResult.getTbItemCid());
document.addFiled("tb_item_cname",tbItemResult.getTbItemCname());
document.addFiled("tb_item_title",tbItemResult.getTbItemTitle());
document.addFiled("tb_item_sell_point",tbItemResult.getTbItemSellPoint());
document.addFiled("tb_item_desc",tbItemResult.getTbItemDesc());
solrClient.add(document);
solrClient.commit();
}
}catch(SolrServerException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
搜索Solr:
public void searchSolr(){
SolrQuery query=new SolrQuery();
// 設置查詢條件
query.setQuery("手機");
// 分頁查詢
query.setStart(0);
query.setRows(10);
// 設置查詢的默認域
query.set("df","tb_item_keywords");
// 設置高亮顯示
query.setHighlight(true);
query.addHighlightField("tb_item_title");
query.setHighlightSimplePre("<span style='color:red;'>");
query.setHighlightSimplePost("</span>");
// 開始查詢
try{
QueryResponse queryResponse=solrClient.query(query);
SolrDocumentList results=queryResponse.getResults();
// 獲取高亮
Map<String,Map<String,List<String>>> highlighting=queryResponse.getHighlighting();
for(SolrDocument result:results){
List<String> strings=highlighting.get(result.get("id")).get(result.get("tb_item_title"))
if(strings!=null&&strings.size()>0){
String title=strings.get(0);
System.out.println(title);
}
}
}catch(SolrServerException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
- 創建SearchServiceImpl實現SearchService接口
package com.oxford.myshop.service.search.provider.api.impl;
@Service(version="${services.versions.search.v1}")
public class SearchServiceImpl implements SearchService{
@Autowired
private SolrClient solrClient;
@Override
public List<TbItemResult> search(String query,int page,int rows){
List<TbItemResult> searchResults=Lists.newArrayList();
SolrQuery query=new SolrQuery();
// 設置查詢條件
query.setQuery("手機");
// 分頁查詢
query.setStart((page-1)*rows);
query.setRows(rows);
// 設置查詢的默認域
query.set("df","tb_item_keywords");
// 設置高亮顯示
query.setHighlight(true);
query.addHighlightField("tb_item_title");
query.setHighlightSimplePre("<span style='color:red;'>");
query.setHighlightSimplePost("</span>");
// 開始查詢
try{
QueryResponse queryResponse=solrClient.query(query);
SolrDocumentList results=queryResponse.getResults();
// 獲取高亮
Map<String,Map<String,List<String>>> highlighting=queryResponse.getHighlighting();
for(SolrDocument solrDocument:solrDocuments){
TbItemResult result=new TbResult();
result.setId(Long.parseLong(String.valueOf(solrDocument.get("id"))));
result.setTbItemCid(Long.parseLong(String.valueOf(solrDocument.get("tb_item_cid"))));
result.setTbItemCname((String)solrDocument.get("tb_item_cname"));
result.setTbItemTitle((String)solrDocument.get("tb_item_title"));
result.setTbItemSellPoint((String)solrDocument.get("tb_item_sell_point"));
result.setTbItemDesc((String)solrDocument.get("tb_item_desc"));
String tbItemTitle="";
List<String> list=highlighting.get(result.get("id")).get(result.get("tb_item_title"))
if(list!=null&&lsit.size()>0){
String title=list.get(0);
}else{
tbItemTitle=(String)solrDocument.get("tb_item_title");
}
result.setTbItemTitle(tbItemTitle);
searchResults.add(result);
}
}catch(SolrServerException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
return searchResults;
}
}
創建搜索服務消費者
- 創建搜索服務消費者myshop-service-search-consumer對Solr數據庫中的數據進行檢索
- MyShopServiceSearchConsumerApplication
package com.oxford.myshop.service.search.consumer;
@EnableHystrix
@EnableHystrixDashboard
@SpringBootApplication(scanBasePackages="com.oxford.myshop",exclude=DataSourceAutoConfiguration.class)
public class MyShopServiceSearchConsumerApplication{
public static void main(String[] args){
SpringApplication.run(MyShopServiceSearchConsumerApplication.class,args);
Main.main(args);
}
}
- SearchController
package com.oxford.myshop.service.search.consumer.controller;
@RestController
public class SearchController{
@Reference(version="${services.versions.search.v1}")
private SearchService searchService;
@RequestMapping(value="search/{query}/{page}/{rows}",method=RequestMethod.GET)
public List<TbItemResult> search(
@PathVariable(required=true) String query,
@PathVariable(required=true) int page,
@PathVariable(required=true) int rows
){
return searchService.search(query,page,rows)
}
}