Solr
1.1 需求
1.2 实现方式
在一些大型门户网站、电子商务网站等都需要站内搜索功能,使用传统的数据库查询方式实现搜索无法满足一些高级的搜索需求,比如:搜索速度要快、搜索结果按相关度排序、搜索内容格式不固定等,这里就需要使用全文检索技术实现搜索功能。
1.2.1使用Lucene实现
单独使用Lucene实现站内搜索需要开发的工作量较大,主要表现在:索引维护、索引性能优化、搜索性能优化等,因此不建议采用。
1.2.2使用Solr实现
基于Solr实现站内搜索扩展性较好并且可以减少程序员的工作量,因为Solr提供了较为完备的搜索引擎解决方案,因此在门户、论坛等系统中常用此方案。
2 solr简介
2.1 Solr基本概况
Solr(Search On Lucene Replication) 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化。
Solr可以独立运行,运行在Jetty、Tomcat等这些Servlet容器中,Solr 索引的实现方法很简单,用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr根据xml文档添加、删除、更新索引 。Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr 返回Xml、json等格式的查询结果进行解析,组织页面布局。Solr不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
官方网站:http://lucene.apache.org/solr/features.html
2.2 Solr的历史
l 2004年 CNET 开发 Solar,为 CNET 提供站内搜索服务
l 2006年1月捐献给 Apache ,成为 Apache 的孵化项目
l 一年后 Solr 孵化成熟,发布了1.2版,并成为 Lucene 的子项目
l 2010年6月,solr 发布了的1.4.1版,这是1.4的 bugfix 版本,1.4.1的solr使用的lucene是2.9版本的
l solr 从1.4.x版本以后,为了保持和lucene同步的版本,solr直接进入3.0版本。
2.3 Solr运行结构图
Solr的目标是打造一款企业级的搜索引擎系统,它是一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。
3.Solr的环境准备
3.1 Solr的下载
从Solr官方网站(http://lucene.apache.org/solr/)下载Solr4.10.3,根据Solr的运行环境,Linux下需要下载solr-4.10.3.tgz,windows下需要下载solr-4.10.3.zip。
Solr使用指南可参考:https://wiki.apache.org/solr/FrontPage。
3.2 Solr开发包的目录结构
bin:solr的运行脚本
contrib:solr的一些扩展jar包,用于增强solr的功能。
dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。
docs:solr的API文档
example:solr工程的例子目录:
l example/solr:
该目录是一个标准的SolrHome,它包含一个默认的SolrCore
l example/multicore:
该目录包含了在Solr的multicore中设置的多个Core目录。
l example/webapps:
该目录中包括一个solr.war,该war可作为solr的运行实例工程。
licenses:solr相关的一些许可信息
3.3 Solr的运行环境
solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(java写的Servlet容器),本教程使用Tocmat作为Servlet容器,相关环境如下:
l Solr:4.10.3
l Jdk环境:1.7.0_72(solr4.10 不能使用jdk1.7以下)
l Web服务器(servlet容器):Tomcat 7X
l Mysql:5X
4 安装和配置
4.1 安装
l 第一步:安装tomcat
l 第二步:将以下的war包,拷贝到tomcat的webapps目录下
l 第三步:解压缩war包,解压缩之后,将war包删掉
l 第四步:添加solr的扩展服务包
将以上jar包,添加到以下目录
l 第五步:添加log4j.properties 将以下目录的文件进行拷贝
复制到以下目录
l 第六步:在web.xml中指定solrhome的目录
4.2 solrhome
SolrHome是Solr运行的主目录,该目录中包括了多个SolrCore目录。SolrCore目录中包含了运行Solr实例所有的配置文件和数据文件,Solr实例就是SolrCore。
一个SolrHome可以包括多个SolrCore(Solr实例),每个SolrCore提供单独的搜索和索引服务。(类似于MySQL数据库服务器可以创建多个数据库)
4.2.1 solrhome的目录结构
把内容拷贝到自己创建的solrhome目录中。
SolrCore目录:
SolrCore创建成功。
5 启动和访问
- 启动TomCat
- 访问服务器地址http://localhost:8080/solr
Solr本地安装成功!
5.1 管理界面介绍
Dashboard
仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。
Logging
Solr运行日志信息。
Cloud
Cloud即SolrCloud,即Solr云(集群),当使用Solr Cloud模式运行时会显示此菜单.
Core Admin
Solr Core的管理界面。在这里可以添加SolrCore实例。
java properties
Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。
Tread Dump
显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。
5.2 Core selector(重点)
选择一个SolrCore进行详细操作,如下:
5.2.1 Analysis
通过此界面可以测试索引分析器和搜索分析器的执行情况。
注:solr中,分析器是绑定在域的类型中的。在schame.xml可以进行配置。
5.2.2 Dataimport
可以定义数据导入处理器,从关系数据库将数据导入到Solr索引库中。
默认没有配置,需要手工配置。(不用了)
5.2.3 Document
通过/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。
通过此菜单可以创建索引、更新索引、删除索引等操作,界面如下:
l overwrite="true" : solr在做索引的时候,如果文档已经存在,就用xml中的文档进行替换
l commitWithin="1000" : solr 在做索引的时候,每个1000(1秒)毫秒,做一次文档提交。为了方便测试也可以在Document中立即提交, </doc>后添加“<commit/>”
5.2.4 Query
通过/select执行搜索索引,必须指定“q”查询条件方可搜索。
查询结果视图
5.3 多SolrCore(collection)的配置
5.3.1 好处
- 一个solr工程对外通过SorlCore 提供服务,每个SolrCore相当于一个数据库,这个功能就相当于一个mysql可以运行多个数据库。
- 将索引数据分SolrCore存储,方便对索引数据管理维护。
- SolrCloud集群需要使用多core。
- 在进行solrcloud的时候,必须配置多solrcore
- 每个solrcore之间是独立的,都可以单独对外提供服务。不同的业务模块可以使用不同的solrcore来提供搜索和索引服务。
5.3.2 步骤
l 复制原来的core目录为collection2,目录结构如下:
l 第二步:修改solrcore目录下的core.properties
这样多solrcore就配置完成了,在Web显示可以验证
5.4 配置中文分词器
选用ikanalyzer进行中文分词
l 第一步:将ikanalyzer的jar包拷贝到以下目录
l 第二步:将ikanalyzer的扩展词库的配置文件拷贝到目录
l 第三步:在schema.xml中添加一个自定义的fieldType,使用中文分析器。(solrcore中)
|
<!-- IKAnalyzer--> <fieldType name="text_ik" class="solr.TextField"> <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/> </fieldType> |
l 第四步:定义field,指定field的type属性为text_ik
|
<!--IKAnalyzer Field--> <field name="content_ik" type="text_ik" indexed="true" stored="true" /> |
l 第五步:重启tomcat, 测试中文分词奏效.
6 solr基础使用
6.3 配置域(field)
本案例中的products表的数据进行索引,所以需要先定义对应的Field域。
要使用solr实现电商网站中商品搜索。电商中商品信息在mysql数据库中存储了,将mysql数据库中数据在solr中创建索引。需要在solr的schema.xml文件定义商品Field。
在schame.xml中配置域
定义Field域
|
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> |
l Name:域的名称
l Type:域的类型
l Indexed:是否索引
l Stored:是否存储
l Required:是否必须
l multiValued:是否是多值,存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),商品的图片(多个,大图和小图)
先确定定义的商品document的field有哪些?
可以根据mysql数据库中商品表的字段来确定:
- Products表的结构:
需要往索引库添加的字段有:
pid、name、catalog、catalog_name、price、description、picture
- FieldType:
经分析,由于中文分词器已经配置完FieldType,所以目前FieldType已经满足需要,无需配置。
- Field:
Pid:
由于pid在products表中是唯一键,而且在solr的shema.xml中已有一个id的唯一键配置,所以不需要再重新定义pid域。
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
Name:
<!-- 商品名称 -->
<field name="product_name" type="text_ik" indexed="true" stored="true"/>
此处 <field>标签中name属性值的配置,并不是数据库表中的字段值,而是在data-config.xml中配置的对应。
Catalog、catalog_name:
<!-- 商品分类ID -->
<field name="product_catalog" type="string" indexed="true" stored="true"/>
<!-- 商品分类名称 -->
<field name="product_catalog_name" type="string" indexed="true" stored="false"/>
Price:
<!-- 商品价格 -->
<field name="product_price" type="float" indexed="true" stored="true"/>
Description:
<!-- 商品描述 -->
<field name="product_description" type="text_ik" indexed="true" stored="false"/>
Picture:
<!-- 商品图片地址 -->
<field name="product_picture" type="string" indexed="false" stored="true"/>
|
<!--product--> <!-- 商品名称 --> <field name="product_name" type="text_ik" indexed="true" stored="true"/> <!-- 商品分类ID --> <field name="product_catalog" type="string" indexed="true" stored="true"/> <!-- 商品分类名称 --> <field name="product_catalog_name" type="string" indexed="true" stored="false"/> <!-- 商品价格 --> <field name="product_price" type="float" indexed="true" stored="true"/> <!-- 商品描述 --> <field name="product_description" type="text_ik" indexed="true" stored="false"/> <!-- 商品图片地址 --> <field name="product_picture" type="string" indexed="false" stored="true"/>
<!-- 目标域 --> <field name="product_keywords" type="text_ik" indexed="true" stored="true" multiValued="true"/>
<!--将商品名称添加到目标域 --> <copyField source="product_name" dest="product_keywords"/>
<!--将商品描述添加到目标域 --> <copyField source="product_description" dest="product_keywords"/> |
注意:其实应该在配置完域后,再把数据库的中的数据导入到索引库!!!(重启tomcat,执行导入操作)
导入完成后,执行查询,将会看到数据库中的全部数据都被导入到了索引库中。
而且,在
中,可以看到如此界面。
7 solrj的使用
7.1 什么是solrj
solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,SolrJ通常在嵌入在业务系统中,通过SolrJ的API接口操作Solr服务,如下图:
Solrj和图形界面操作的区别就类似于数据库中你使用jdbc和mysql客户端的区别一样。
7.2 solrj的环境准备
l Solr:4.10.3
l Jdk环境:1.7.0_72(solr4.10 不能使用jdk1.7以下)
l Ide环境:eclipse (MyEclipse)
7.3 solrj环境搭建
7.3.1 新建Java工程
7.3.2 导入Solr所需要的Jar包
l Solrj的依赖包和核心包
l Solr服务的依赖包
7.4 代码实现
7.4.1 添加\修改索引
核心步骤:
1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
2、 创建SolrInputDocument对象,然后通过它来添加域。
3、 通过HttpSolrServer对象将SolrInputDocument添加到索引库。
4、 提交。
- 代码实现:
|
@Test public void addDocument() throws Exception { // 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。 // 参数:solr服务器的访问地址 HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr/"); // 2、 创建SolrInputDocument对象,然后通过它来添加域。 SolrInputDocument document = new SolrInputDocument(); // 第一个参数:域的名称,域的名称必须是在schema.xml中定义的 // 第二个参数:域的值 // 注意:id的域不能少 document.addField("id", " "); document.addField("name", "使用solrJ添加的文档,这是一个测试。"); document.addField("description", "文档的内容,这是一个测试。"); // 3、 通过HttpSolrServer对象将SolrInputDocument添加到索引库。 server.add(document); // 4、 提交 server.commit(); System.out.println("索引添加或者更新完成!"); } |
- 测试查询:
7.4.2 删除索引
7.4.2.1 根据ID删除
- 代码实现:
|
@Test public void deleteDocument() throws Exception { // 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。 // 参数:solr服务器的访问地址 HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr/"); // 根据ID删除 server.deleteById("c0001"); // 提交 server.commit(); System.out.println("索引删除成功!"); } |
- 测试删除:
7.4.2.2 根据其他条件删除
- 代码实现:
|
@Test public void deleteDocumentByQuery() throws Exception { // 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。 // 参数:solr服务器的访问地址 HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr/"); // 根据ID删除 server.deleteByQuery("name:使用solrJ添加的文档,这是一个测试。"); // 全部删除 // server.deleteByQuery("*:*"); // 提交 server.commit(); System.out.println("索引删除成功!"); } |
7.4.3 查询索引
7.4.3.1 简单查询
|
@Test public void search01() throws Exception { // 创建HttpSolrServer HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr"); // 创建SolrQuery对象 SolrQuery query = new SolrQuery(); // 输入查询条件 query.setQuery("product_name:笔记本"); // 执行查询并返回结果 QueryResponse response = server.query(query); // 获取匹配的所有结果 SolrDocumentList list = response.getResults(); // 匹配结果总数 long count = list.getNumFound(); System.out.println("匹配结果总数:" + count); for (SolrDocument doc : list) { System.out.println(doc.get("id")); System.out.println(doc.get("product_name")); System.out.println(doc.get("product_catalog")); System.out.println(doc.get("product_price")); System.out.println(doc.get("product_picture")); System.out.println("====================="); } } |
7.4.3.2 复杂查询
7.4.3.2.1 Solr查询语法
- q - 查询关键字,必须的,如果查询所有使用*:*。
请求的q是字符串
- fq - (filter query)过虑查询,作用:在q查询符合结果中同时是fq查询符合的,例如::
请求fq是一个数组(多个值)
也可以使用“*”表示无限,例如:
20以上:product_price:[20 TO *]
20以下:product_price:[* TO 20]
[] 表示包含首位,{}表示不包含首位。
- sort - 排序,格式:sort=<field name>+<desc|asc>[,<field name>+<desc|asc>]… 。示例:
- start - 分页显示使用,开始记录下标,从0开始
- rows - 指定返回结果最多有多少条记录,配合start来实现分页。
实际开发时,知道当前页码和每页显示的个数最后求出开始下标。
- fl - 指定返回那些字段内容,用逗号或空格分隔多个。
显示商品图片、商品名称、商品价格
- df-指定一个搜索Field *******
也可以在SolrCore目录 中conf/solrconfig.xml文件中指定默认搜索Field,指定后就可以直接在“q”查询条件中输入关键字。
- wt - (writer type)指定输出格式,可以有 xml, json, php, phps, 后面 solr 1.3增加的,要用通知我们,因为默认没有打开。
- hl 是否高亮 ,设置高亮Field,设置格式前缀和后缀。
7.4.3.2.2 Solr复杂查询代码实现
|
@Test public void search02() throws Exception { // 创建HttpSolrServer HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr"); // 创建SolrQuery对象 SolrQuery query = new SolrQuery();
// 输入查询条件 query.setQuery("product_name:小黄人"); // query.set("q", "product_name:小黄人");
// 设置过滤条件 // 如果设置多个过滤条件的话,需要使用query.addFilterQuery(fq) query.setFilterQueries("product_price:[1 TO 10]");
// 设置排序 query.setSort("product_price", ORDER.asc); // 设置分页信息(使用默认的) query.setStart(0); query.setRows(10);
// 设置显示的Field的域集合 query.setFields("id,product_name,product_catalog,product_price,product_picture");
// 设置默认域 query.set("df", "product_keywords");
// 设置高亮信息 query.setHighlight(true); query.addHighlightField("product_name"); query.setHighlightSimplePre("<span style=’color:red’>"); query.setHighlightSimplePost("</span>");
// 执行查询并返回结果 QueryResponse response = server.query(query); // 获取匹配的所有结果 SolrDocumentList list = response.getResults(); // 匹配结果总数 long count = list.getNumFound(); System.out.println("匹配结果总数:" + count);
// 获取高亮显示信息 Map<String, Map<String, List<String>>> highlighting = response .getHighlighting(); for (SolrDocument doc : list) { System.out.println(doc.get("id"));
List<String> list2 = highlighting.get(doc.get("id")).get( "product_name"); if (list2 != null) System.out.println("高亮显示的商品名称:" + list2.get(0)); else { System.out.println(doc.get("product_name")); }
System.out.println(doc.get("product_catalog")); System.out.println(doc.get("product_price")); System.out.println(doc.get("product_picture")); System.out.println("====================="); } } |