Solr入门教程
歡迎來到我的博客: http://chenb.in
1 Solr基礎介紹
1.1 什么是Solr
Solr是Apache開源的、基于Lucene的Java Api的封裝,是一款成熟穩定的搜索引擎。
1.2 Solr的優缺點
優點:
大量的用戶群體與成熟的社區
支持多種格式索引,例如HTML、CSV、JSON、XML等
誕生于與2004年,多年的沉淀使得Solr成熟、穩定
如果不考慮建索引的同時進行查詢,查詢速度更快,像我目前接手的法律查詢軟件,法律的錄入不是十分頻繁,那么用Solr就會比較合適
缺點:
1.3 使用版本
1.4 安裝
下載安裝包 https://mirrors.bfsu.edu.cn/apache/lucene/solr/7.7.3/solr-7.7.3.tgz
到安裝路徑下解壓 tar -zxvf solr-7.7.3.tgz(tgz相當于是tar.gz)
1.5 目錄布局
| bin | 包含啟動等一些重要腳本 |
| contrib | Solr的附加插件 |
| dist | Solr的jar包 |
| docs | Solr的在線文檔 |
| example | 演示案例 |
| licenses | Solr使用的第三方庫的許可證 |
| server | Solr的核心應用,包含管理UI界面等 |
1.6 常用命令
bin/solr start 啟動(macOS與Linux均以下述方式啟動,Windows需要在加個.cmd如:bin/solr.cmd start)
bin/solr stop 關閉
bin/solr restart 重啟
bin/solr status 查看狀態
Solr有單機運行與集群運行兩種方式,本文主要以單機運行為主了,默認啟動端口為8983,若想指定端口可以在后面加上 -p 參數,如
- bin/solr start -p 端口號
- bin/solr restart -p 端口號
此時可以在瀏覽器輸入 http://localhost:8983/ 來打開admin UI界面:
2 core配置
2.1 什么是core
是一個Lucene實例,其中包含Solr所有的配置文件,我們需要創建一個core來完成索引與分析等操作
一個Solr可以有多個core,如果需要core之間也是可以互相通信的
2.2 core的創建
create命令創建
./solr create -c new_core1
admin UI上直接創建
a. 此步執行之前,需要先將solr的默認配置文件夾復制到solr/server/solr/下,并重命名(下例中取名為new_core2),參考命令: cp -r ~/Library/solr-7.7.3/server/solr/configsets/_default/conf/ ~/Library/solr-7.7.3/server/solr/new_core2/
b. 重啟
c.
注意項:
2.3 core的刪除
? ./solr delete -c my_core
3 schema配置
3.1 什么是schema
在講schema之前需要先了解一個概念 index(索引)
這里的索引和數據庫中的索引不同,可以簡單理解為Solr中的一張表,與Elasticsearch中的索引概念類似,舉個例子:
我有學生表和課程表,我有一條很頻繁的查詢是某某學生某某課程考了多少分,那我就需要從數據庫把這兩張表的內容查詢出來處理完后再傳回客戶端,而solr會把這兩張表中需要的字段提取出來,存放到solr中去,而存放的數據結構就是索引,這相當于是數據庫里的一張表
而schema就是Solr如何建立索引的說明書,例如字段的數據類型以及分詞方式等
3.2 schema配置
在Solr5.5之前的配置文件叫schema.xml,這些需要手動配置,密密麻麻的XML看著還挺頭疼的,之后出現了的配置文件叫做managed-schema,他的配置方式是使用schemaAPI來實時配置,并且配置之后無須重啟即可生效,更加適合生產環境,也更好操作
常見的配置內容:
field: 相當于Java類的屬性字段,可以給他配上其他屬性,比如是否需要顯示,是否可以用做索引字段等,有明確的定義的普通字段
a. name: 查詢時字段
b. type: 字段類型
c. indexed: 是否為索引字段
d. stored: 是否存儲
e. multiValued: 是否允許多值
dynamicField: 與field類似,是未明確定義名字的字段,常用屬性與field了類似
copyField:
- 可以將field字段拷貝到其他字段
- 可以將dynamicField字段拷貝到其他字段
- 可以將多個field字段拷貝到多值字段
- 可以將多個dynamicField字段拷貝到多值字段
fieldType: 一般用來定義分詞器,然后讓field/dynamicField通過type引用
analyzer: fieldType的子屬性,常用于設置分詞器
3.3 ik分詞器配置
步驟:
下載jar包:https://search.maven.org/search?q=com.github.magese
把jar包放置到(以我的為例)solr-7.7.3/server/solr-webapp/webapp/WEB-INF/lib下
到solr-7.7.3/server/solr/new_core/managed-schema下最底下粘貼以下內容:
<!-- ik分詞器 --><fieldType name="text_ik" class="solr.TextField"><analyzer type="index"><tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" conf="ik.conf"/><filter class="solr.LowerCaseFilterFactory"/></analyzer><analyzer type="query"><tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/><filter class="solr.LowerCaseFilterFactory"/></analyzer></fieldType>重啟Solr
測試分詞器
Field Value(Index)與Field Value(Query)是Solr在索引時與在查詢時會怎么處理數據,可以根據這兩個來幫助設計分析器、分詞器和過濾器(本文采用了ik分詞器,上述三者ik分詞器已經自己設計好了,關于這三者具體內容見下文)
4 分析器、分詞器、過濾器
Solr主要通過分析器(Analyzer)、標記器(Tokenizers)、過濾器(Filter)來分解和處理文本。
在介紹之前,先講述一個概念文檔(Document),之前說index相當于數據庫里的一張表,而文檔就可以理解為表里的一條數據。
4.1 概述
當對一個文檔進行索引時,每個field的數據都會經過分析,分詞過濾等操作,例如,將一句話分詞若干個單詞和詞組,然后去掉空格與語氣詞等多余的詞以及進行同義詞替換等,例如:
what a beautiful girl,在這里what和a這樣的詞以及空格會被去掉,最終的處理結果為beautiful和girl或者是beautiful girl
除了索引會做這些工作,查詢時也會做這些工作,并且為了保證索引與查詢可以正確匹配,二者的處理規則往往也是相同的,例如:
ABCD索引分詞為AB CD,查詢時分詞為A BCD,這樣匹配的結果就為0
analyzer包含著兩個部分,Tokenizers與Filter。Tokenizers就是將句子拆分成單個詞,而Filter就是對分詞的結果中比如中文里的"的"、“呀”、“啊”,英文里的"am"、“is”、"are"這類對句子主體意思關系不大的詞去掉
solr有自帶了一些分詞器,如果你需要使用中文分詞器就需要自己配置,參考3.3
4.2 使用方式
analyzer為fieldType下的一個子元素,主要作用是檢查字段的文本并生成令牌流
最簡單的使用方式如下:
<fieldType name="nametext" class="solr.TextField"><analyzer class="org.apache.lucene.analysis.core.WhitespaceAnalyzer"/> </fieldType>其中analyzer通過一個全限定Java類名引入,且命名的類必須繼承自org.apache.lucene.analysis.Analyzer
對于簡單的文章,這樣一個分析器類就足夠完成任務,但是通常情況下是需要更為復雜的分析
Solr提供了大量的Tokenizers與Filter來協助分析,例如:
<fieldType name="nametext" class="solr.TextField"><analyzer><tokenizer class="solr.StandardTokenizerFactory"/><filter class="solr.StandardFilterFactory"/><filter class="solr.LowerCaseFilterFactory"/><filter class="solr.StopFilterFactory"/><filter class="solr.EnglishPorterFilterFactory"/></analyzer> </fieldType>org.apache.solr.analysis 包中的類可以在這里用簡寫的 solr. 前綴來代替
上述中,analyzer未使用指定的分析器類,而用許多類組合到一起,成為該字段的分析器,任何使用"nametext"fieldType字段進行索引或查詢時,都會經過從StandardTokenizerFactory到EnglishPorterFilterFactory的過濾
分析發生在兩種情況下:分別是索引時與查詢時
在索引時:當一個字段被創建時,分析得到的令牌流將被添加到一個索引中,并為該字段定義一組術語(包括位置、大小等)。
在查詢時:分析正在搜索的值,并將結果的條件與存儲在字段索引中的條件進行匹配。
如果想要在不同階段使用不同的分析器,可以指定type屬性:如下:
<fieldType name="nametext" class="solr.TextField"><analyzer type="index"><tokenizer class="solr.StandardTokenizerFactory"/><filter class="solr.LowerCaseFilterFactory"/><filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/><filter class="solr.SynonymFilterFactory" synonyms="syns.txt"/></analyzer><analyzer type="query"><tokenizer class="solr.StandardTokenizerFactory"/><filter class="solr.LowerCaseFilterFactory"/></analyzer> </fieldType>在索引時,文本被標記化,并設置為小寫,未列在keepwords.txt中的會被丟棄,保留下來的會根據syns.txt進行同義詞替換
在查詢時,就只是將文本標記后設置為小寫
5 Solr的使用
5.1 根據文件導入數據
Solr可以導入多種格式的文件來作為index,在solr-7.7.3/example/exampledocs中有各個格式的測試案例,本文隨機采用一種演示
可以通過bin目錄下的post命令導入index
輸入 bin/post -h 可查看具體使用方式
示例: bin/post -c 導入的核心名 導入的文件(可多個)
下面演示一下往new_core里導入book.csv與book.json文件:
5.2 添加/更新文檔
添加/更新內容(根據是否有id判斷為添加還是更新操作):
{"id":"1234567890","cat":["book"],"name":["BeanChan is a handsome man"],"price":[799],"inStock":[true],"author":["BeanChan"],"series_t":"A man has a pretty face","sequence_i":1,"genre_s":"fantasy" }5.3 刪除文檔
需基于XML方式
<!--刪除所有索引--> <delete><query>*:*</query> </delete> <commit /><!-- 根據 id 進行刪除--> <delete><id>1</id> </delete> <commit />5.4 查詢數據
| q | 主要查詢參數 |
| fq | 過濾器查詢參數 |
| start | 起始頁 |
| rows | 查詢行數 |
| sort | 排序方式 |
| fl | 結果集字段列表 |
| wt | 展示格式(下圖以csv格式展示) |
下圖為例:
6 整合springboot
引入pom依賴
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-solr</artifactId> </dependency>配置yml
spring:data:solr:host: http://127.0.0.1:8983/solr配置entity
package beanchan.cn.springbootdemo.entity;import lombok.Data; import org.apache.solr.client.solrj.beans.Field; import org.springframework.data.solr.core.mapping.SolrDocument;/*** @Classname SolrEntity* @Author ChenBin* @Description* @CreateDate 2021/2/20 2:00 PM*/ @SolrDocument(collection = "new_core") @Data public class SolrEntity {@Fieldprivate String id;@Field("name")private String name; }配置dao
package beanchan.cn.springbootdemo.dao;import beanchan.cn.springbootdemo.entity.SolrEntity; import org.springframework.data.solr.repository.SolrCrudRepository; import org.springframework.stereotype.Repository;/*** @Classname solaDao* @Author ChenBin* @Description* @CreateDate 2021/2/20 3:25 PM*/ @Repository public interface SolrDao extends SolrCrudRepository<SolrEntity,String> {}配置service及其實現類
package beanchan.cn.springbootdemo.service;import beanchan.cn.springbootdemo.entity.SolrEntity; import org.apache.solr.client.solrj.SolrServerException;import java.io.IOException; import java.util.List;/*** @InterfaceName ISolrService* @Author ChenBin* @Description* @CreateDate 2021/2/20 3:33 PM*/ public interface ISolrService {void add();void update();void delete();List<SolrEntity> quertList(String keyword) throws IOException, SolrServerException; } package beanchan.cn.springbootdemo.service.impl;import beanchan.cn.springbootdemo.dao.SolrDao; import beanchan.cn.springbootdemo.entity.SolrEntity; import beanchan.cn.springbootdemo.service.ISolrService; 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.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.springframework.stereotype.Service;import javax.annotation.Resource; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map;/*** @Classname ISolrServiceImpl* @Author ChenBin* @Description* @CreateDate 2021/2/20 3:34 PM*/ @Service public class ISolrServiceImpl implements ISolrService {@ResourceSolrDao solrDao;@Resourceprivate SolrClient solrClient;@Overridepublic void add() {SolrEntity solrEntity = new SolrEntity();solrEntity.setId("001");solrEntity.setName("阿姆斯特朗回旋加速噴氣式阿姆斯特朗炮制作圖紙");solrDao.save(solrEntity);}@Overridepublic void update() {SolrEntity solrEntity = new SolrEntity();solrEntity.setId("001");solrEntity.setName("阿姆斯特朗回旋加速噴氣式阿姆斯特朗炮使用手冊");solrDao.save(solrEntity);}@Overridepublic void delete() {solrDao.deleteById("001");}@Overridepublic List<SolrEntity> quertList(String keyword) throws IOException, SolrServerException {SolrQuery query = new SolrQuery();//設置查詢條件query.setQuery("name:阿姆斯特朗");//按照時間排序// query.addSort("create_time", SolrQuery.ORDER.desc);//開始頁query.setStart(0);//一頁顯示多少條query.setRows(10);//開啟高亮//query.setHighlight(true);//設置高亮字段//query.addHighlightField("demoName");//前綴//query.setHighlightSimplePre("<font color='red'>");//后綴//query.setHighlightSimplePost("</font>");//執行查找QueryResponse response = solrClient.query("new_core",query);SolrDocumentList results = response.getResults();//獲取查詢到的數據總量long numFound = results.getNumFound();if (numFound <= 0) {//如果小于0,表示未查詢到任何數據,返回nullreturn null;} else {List<SolrEntity> list = new ArrayList<>();//遍歷結果集for (SolrDocument doc : results) {//得到每條數據的map集合Map<String, Object> map = doc.getFieldValueMap();//添加到listfor (String key : map.keySet()) {System.out.println("KEY:" + key + ",VALUE:" + map.get(key).toString());}SolrEntity demo = new SolrEntity();demo.setId(map.get("id").toString());demo.setName(map.get("name").toString());list.add(demo);}return list;}}}配置Controller
package beanchan.cn.springbootdemo.controller;import beanchan.cn.springbootdemo.entity.SolrEntity; import beanchan.cn.springbootdemo.service.ISolrService; import org.apache.solr.client.solrj.SolrServerException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource; import java.io.IOException; import java.util.List;/*** @Classname searchController* @Author ChenBin* @Description* @CreateDate 2021/2/20 10:57 AM*/ @RestController public class SearchController {@ResourceISolrService solrService;/*** 新增** @return flag*/@RequestMapping("add")public String add() {solrService.add();return "success";}@RequestMapping("update")public String update() {solrService.update();return "success";}@RequestMapping("delete")public String delete() {solrService.delete();return "success";}@RequestMapping("queryList")public String queryList() {List<SolrEntity> solrEntities = null;try {solrEntities = solrService.quertList("");} catch (IOException | SolrServerException e) {e.printStackTrace();}if (solrEntities != null)return solrEntities.toString();return "empty";} }測試還請自行實驗,下圖簡單展示一下
總結
- 上一篇: 425 Failed to establ
- 下一篇: 算法题:科学计数法