Solr 是一種可供企業使用的、基於 Lucene 的搜索服務器,它支持層面搜索、命中醒目顯示和多種輸出格式。在這篇文章中,我將介紹 Solr 的部署和使用的基本操做,但願能讓初次使用的朋友們少踩一些坑。html
下載地址:https://lucene.apache.org/solr/downloads.htmljava
本文中使用的 Solr 版本:7.7.2,由於我是用的是 Windows 系統,因此主要介紹的是 Windows 下的部署方法。python
Solr 內置了 Jetty,因此不須要任何安裝任何 Web 容器便可運行。直接經過命令行就能夠啓動。mysql
啓動 Solr:git
.\solr.cmd start
中止 Solr:github
.\solr.cmd stop -all
首先在 server\solr
文件夾中建立一個新的目錄,而後將 server\solr\configsets\_default
下的 conf
目錄複製到剛剛建立的文件夾。web
在瀏覽器中打開 http://localhost:8983/solr/
點擊左側的 Core Admin
添加 Core。sql
name
和 instanceDir
都改爲剛剛建立的目錄名稱。shell
建立好以後便可在左側的 Core Selector
中找到這個 Core。數據庫
如今一個 Core 就建立好了,在 Core 的面板裏能夠對其進行一些基本操做。
Solr 的 Api 是支持經過調用接口添加數據的,可是在實際使用中咱們都是從數據庫中同步數據,因此咱們須要爲 Solr 配置數據源。
在 solrconfig.xml
文件中找到以下內容:
<!-- Request Handlers http://wiki.apache.org/solr/SolrRequestHandler Incoming queries will be dispatched to a specific handler by name based on the path specified in the request. If a Request Handler is declared with startup="lazy", then it will not be initialized until the first request that uses it. -->
添加一個 requestHandler
節點:
<requestHandler name="/dataimport" class="solr.DataImportHandler"> <lst name="defaults"> <str name="config">data-config.xml</str> </lst> </requestHandler>
data-config.xml 文件的大體結構以下:
稍後會對 data-config.xml 文件進行詳細介紹。
從微軟官網下載 SQL Server 的 Microsoft SQL Server JDBC 驅動程序 4.1 驅動,複製到 server\solr-webapp\webapp\WEB-INF\lib
目錄下。
這裏須要注意的是把在下載的文件重命名爲 sqljdbc4.jar
,我以前沒有更名死活加載不上。
使用 com.microsoft.sqlserver.jdbc.SQLServerDriver
驅動配置數據源:
<dataSource name="postData" driver="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://127.0.0.1:1433;SelectMethod=Cursor;DatabaseName=post;useLOBs=false;loginTimeout=60" user="charlestest" password="12345678" />
下載:mysql-connector-java-6.0.6.jar 複製到 server\solr-webapp\webapp\WEB-INF\lib
目錄下。
從 dist
目錄複製 solr-dataimporthandler-7.7.2.jar
到 server/solr-webapp/webapp/WEB-INF/lib
中。
配置 data-config.xml
:
<dataConfig> <dataSource name="postsData" type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/posts?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC" user="root" password="12345678" batchSize="-1" /> <document name="posts"> <entity name="Post" dataSource="postData" pk="Id" transformer="DateFormatTransformer,HTMLStripTransformer" rootEntity="true" query="SELECT Id, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count FROM wp_posts" deltaQuery="SELECT Id, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count FROM wp_posts post_modified >'${dataimporter.last_index_time}' " > <field column="Id" /> <field column="post_author" /> <field column="post_date" dateTimeFormat='yyyy-MM-dd HH:mm:ss'/> <field column="post_date_gmt" dateTimeFormat='yyyy-MM-dd HH:mm:ss'/> <field column="post_content" /> <field column="post_title" /> <field column="post_excerpt" /> <field column="post_status" /> <field column="comment_status" /> <field column="ping_status" /> <field column="post_password" /> <field column="post_name" /> <field column="to_ping" /> <field column="pinged" /> <field column="post_modified" dateTimeFormat='yyyy-MM-dd HH:mm:ss'/> <field column="post_modified_gmt" dateTimeFormat='yyyy-MM-dd HH:mm:ss'/> <field column="post_content_filtered" /> <field column="post_parent" /> <field column="guid" /> <field column="menu_order" /> <field column="post_type" /> <field column="post_mime_type" /> <field column="comment_count" /> <entity name="PostAuthor" dataSource="authordata" pk="Id" query="SELECT Id, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name FROM wp_users where id=${Post.post_author}"> <field column="Id" /> <field column="user_login"/> <field column="user_pass"/> <field column="user_nicename"/> <field column="user_email"/> <field column="user_url"/> <field column="user_registered"/> <field column="user_activation_key"/> <field column="user_status"/> <field column="display_name"/> </entity> </entity> </document> </dataConfig>
entity 中的一些經常使用屬性:
dataSource 中 batchSize 屬性的做用是能夠在批量導入的時候限制鏈接數量。
配置完成後從新加載一下 Core。
將 contrib\analysis-extras\lucene-libs
目錄中的 lucene-analyzers-smartcn-7.7.2.jar
複製到 server\solr-webapp\webapp\WEB-INF\lib
目錄下,不然會報錯。
在 managed-shchema
中添加以下代碼:
<!-- 配置中文分詞器 --> <fieldType name="text_cn" 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>
把須要使用中文分詞的字段類型設置成 text_cn
:
<field name="Remark" type="text_cn" indexed="true" stored="true" multiValued="false"/>
Solr 複製模式,是一種在分佈式環境下用於同步主從服務器的一種實現方式,因以前提到的基於 rsync 的 SOLR 不一樣方式部署成本太高,被 Solr 1.4 版本所替換,取而代之的就是基於 HTTP 協議的索引文件傳輸機制,該方式部署簡單,只需配置一個文件便可。Solr 索引同步的是 Core 對 Core,以 Core 爲基本同步單元。
主服務器 solrconfig.xml
配置:
<requestHandler name="/replication" class="solr.ReplicationHandler"> <lst name="master"> <!-- 執行 commit 操做後進行 replicate 操做一樣的設置'startup', 'commit', 'optimize'--> <str name="replicateAfter">commit</str> <!-- 執行 startup 操做後進行 replicate 操做 --> <str name="replicateAfter">startup</str> <!-- 複製索引時也同步如下配置文件 --> <str name="confFiles">schema.xml,stopwords.txt</str> <!-- 每次 commit 以後,保留增量索引的週期時間,這裏設置爲 5 分鐘。 --> <str name="commitReserveDuration">00:05:00</str> <!-- 驗證信息,由用戶自定義用戶名--> <!-- <str name="httpBasicAuthUser">root</str> --> <!-- 驗證信息,由用戶自定義密碼 --> <!-- <str name="httpBasicAuthPassword">password</str> --> </lst> <!-- <lst name="slave"> <str name="masterUrl">http://your-master-hostname:8983/solr</str> <str name="pollInterval">00:00:60</str> </lst> --> </requestHandler>
從服務器 solrconfig.xml
配置:
<requestHandler name="/replication" class="solr.ReplicationHandler"> <lst name="slave"> <!-- 主服務器的同步地址 --> <str name="masterUrl">http://192.168.1.135/solr/posts</str> <!-- 從服務器同步間隔,即每隔多長時間同步一次主服務器 --> <str name="pollInterval">00:00:60</str> <!-- 壓縮機制,來傳輸索引,可選 internal|external,internal:內網,external:外網 --> <str name="compression">internal</str> <!-- 設置鏈接超時(單位:毫秒) --> <str name="httpConnTimeout">50000</str> <!-- 若是設置同步索引文件過大,則應適當提升此值。(單位:毫秒) --> <str name="httpReadTimeout">500000</str> <!-- 驗證用戶名,須要和 master 服務器一致 --> <!-- <str name="httpBasicAuthUser">root</str> --> <!-- 驗證密碼,須要和 master 服務器一致 --> <!-- <str name="httpBasicAuthPassword">password</str> --> </lst> </requestHandler>
Solr 主從同步是經過 Slave 週期性輪詢來檢查 Master 的版本,若是 Master 有新版本的索引文件,Slave 就開始同步複製。
index.時間戳
格式的文件夾名稱,例如:index.20190614133600008)。舊的索引文件還存放在原來的文件夾中,同步過程當中出錯不會影響到 Slave,若是同步過程當中有請求訪問,Slave 會使用舊的索引。咱們項目中 6.7G 的索引文件(279 萬條記錄),大概只用了 12 分鐘左右就同步完成了,平均每秒的同步速度大約在 10M 左右。
注意事項: 若是主從的數據源配置的不一致,極可能致使從服務器沒法同步索引數據。
SolrJ 是 Solr 的官方客戶端,文檔地址:https://lucene.apache.org/solr/7_7_2/solr-solrj/。
使用 maven 添加:
<!-- https://mvnrepository.com/artifact/org.apache.solr/solr-solrj --> <dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId> <version>7.7.2</version> </dependency>
查詢索引文檔:
String keyword = "蘋果"; Map<String, String> queryParamMap = new HashMap<String, String>(); queryParamMap.put("q", "*:*"); queryParamMap.put("fq", keyword); MapSolrParams queryParams = new MapSolrParams(queryParamMap); QueryResponse queryResponse = client.query("posts", queryParams); SolrDocumentList results = queryResponse.getResults();
添加和更新索引文檔:
// 經過 屬性 添加到索引中 SolrInputDocument doc = new SolrInputDocument(); doc.addField("id", "10000"); doc.addField("post_title", "test-title"); doc.addField("post_name", "test-name"); doc.addField("post_excerpt", "test-excerpt"); doc.addField("post_content", "test-content"); doc.addField("post_date", "2019-06-18 14:56:55"); client.add("posts", doc); // 經過 Bean 添加到索引中 Post post = new Post(); post.setId(10001); post.setPost_title("test-title-10001"); post.setPost_name("test-name"); post.setPost_excerpt("test-excerpt"); post.setPost_content("test-content"); post.setPost_date(new Date()); client.addBean("posts", post); client.commit("posts");
具體代碼能夠參考我 GitHub 中的示例,這裏就不詳細列出了。
SolrNet:https://github.com/mausch/SolrNet
經過 Nuget 添加 SolrNet:
Install-Package SolrNet
首先定義一個索引對象 PostDoc
:
/// <summary> /// 文章 doc。 /// </summary> [Serializable] public class PostDoc { [SolrUniqueKey("id")] public int Id { get; set; } [SolrField("post_title")] public string Title { get; set; } [SolrField("post_name")] public string Name { get; set; } [SolrField("post_excerpt")] public string Excerpt { get; set; } [SolrField("post_content")] public string Content { get; set; } [SolrField("post_date")] public DateTime PostDate { get; set; } }
在項目的 Startup
類中初始化 SolrNet:
SolrNet.Startup.Init<PostDoc>("http://localhost:8983/solr/posts");
添加或更新文檔操做:
// 同步添加文檔 solr.Add( new PostDoc() { Id = 30001, Name = "This SolrNet Name", Title = "This SolrNet Title", Excerpt = "This SolrNet Excerpt", Content = "This SolrNet Content 30001", PostDate = DateTime.Now } ); // 異步添加文檔(更新) await solr.AddAsync( new PostDoc() { Id = 30001, Name = "This SolrNet Name", Title = "This SolrNet Title", Excerpt = "This SolrNet Excerpt", Content = "This SolrNet Content Updated 30001", PostDate = DateTime.Now } ); // 提交 ResponseHeader responseHeader = await solr.CommitAsync();
刪除文檔操做:
// 使用文檔 Id 刪除 await solr.DeleteAsync("300001"); // 直接刪除文檔 await solr.DeleteAsync(new PostDoc() { Id = 30002, Name = "This SolrNet Name", Title = "This SolrNet Title", Excerpt = "This SolrNet Excerpt", Content = "This SolrNet Content 30002", PostDate = DateTime.Now }); // 提交 ResponseHeader responseHeader = await solr.CommitAsync();
搜索並對結果進行排序,在不傳入分頁參數的狀況下 SolrNet 會返回全部知足條件的結果。
// 排序 ICollection<SortOrder> sortOrders = new List<SortOrder>() { new SortOrder("id", Order.DESC) }; // 使用查詢條件並排序 SolrQueryResults<PostDoc> docs = await solr.QueryAsync("post_title:索尼", sortOrders);
使用字段篩選的另外一種方式:
// 使用條件查詢 SolrQueryResults<PostDoc> posts = solr.Query(new SolrQueryByField("id", "30000"));
分頁查詢並對高亮關鍵字:
SolrQuery solrQuery = new SolrQuery("蘋果"); QueryOptions queryOptions = new QueryOptions { // 高亮關鍵字 Highlight = new HighlightingParameters { Fields = new List<string> { "post_title" }, BeforeTerm = "<font color='red'><b>", AfterTerm = "</b></font>" }, // 分頁 StartOrCursor = new StartOrCursor.Start(pageIndex * pageSize), Rows = pageSize }; SolrQueryResults<PostDoc> docs = await solr.QueryAsync(solrQuery, queryOptions); var highlights = docs.Highlights;
高亮關鍵字須要在返回結果中單獨獲取,docs.Highlights
是一個 IDictionary<string, HighlightedSnippets>
對象,每一個 key
對應文檔的 id
,HighlightedSnippets
中也是一個 Dictionary
,存儲高亮處理後的字段和內容。
PySolr:https://github.com/django-haystack/pysolr
使用 pip
安裝 pysolr:
pip install pysolr
簡單的操做:
# -*- coding: utf-8 -*- import pysolr SOLR_URL = 'http://localhost:8983/solr/posts' def add(): """ 添加 """ result = solr.add([ { 'id': '20000', 'post_title': 'test-title-20000', 'post_name': 'test-name-20000', 'post_excerpt': 'test-excerpt-20000', 'post_content': 'test-content-20000', 'post_date': '2019-06-18 14:56:55', }, { 'id': '20001', 'post_title': 'test-title-20001', 'post_name': 'test-name-20001', 'post_excerpt': 'test-excerpt-20001', 'post_content': 'test-content-20001', 'post_date': '2019-06-18 14:56:55', } ]) solr.commit() results = solr.search(q='id: 20001') print(results.docs) def delete(): """ 刪除 """ solr.delete(q='id: 20001') solr.commit() results = solr.search(q='id: 20001') print(results.docs) def update(): """ 更新 """ solr.add([ { 'id': '20000', 'post_title': 'test-title-updated', 'post_name': 'test-name-updated', 'post_excerpt': 'test-excerpt-updated', 'post_content': 'test-content-updated', 'post_date': '2019-06-18 15:00:00', } ]) solr.commit() results = solr.search(q='id: 20000') print(results.docs) def query(): """ 查詢 """ results = solr.search('蘋果') print(results.docs) if __name__ == "__main__": solr = pysolr.Solr(SOLR_URL) add() delete() update() query()
須要注意的是在使用 solr.add()
和 solr.delete
方法之後須要執行一下 solr.commit()
方法,不然文檔的變動不會提交。
若是想獲取添加或更新是否成功能夠經過判斷 solr.commit()
方法返回結果,solr.commit()
方法的返回結果是一個 xml 字符串:
<xml version="1.0" encoding="UTF-8"> <response> <lst name="responseHeader"> <int name="status">0</int> <int name="QTime">44</int> </lst> </response> </xml>
status
的值若是是 0 就表示提交成功了。
經過簡單使用和測試,就會發現搜索結果並非很精準,好比搜索「微軟」這個關鍵字,搜索出來的數據中有徹底不包含這個關鍵字的內容,因此要想讓搜索結果更加準確就必須對 Sorl 進行調優,Solr 中還有不少高級的用法,例如設置字段的權重、自定義中文分詞詞庫等等,有機會我會專門寫一篇這樣的文章來介紹這些功能。
我在 sql
目錄裏提供了數據庫腳本,方便你們建立測試數據,數據是之前作的一個小站從網上抓取過來的科技新聞。
項目地址:https://github.com/weisenzcharles/SolrExample