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