您的位置:首页 > 其它

浅尝Solr~~

2017-04-12 17:53 127 查看
由于最近项目组有需求,大致意思是做一个对数据全面的统一搜索。于是乎,就研究了一哈Solr

什么是Solr?

Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。
Solr是一个高性能,采用Java5开发,Solr基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。


为什么选择Solr?

除了Solr,ES(Elasticsearch),也是个不错的选择对于搜索引擎,并且,目前的应用是广于Solr的。
ES是一个实时的分布式搜索和分析引擎。具体特性,我也不甚了解,主要特点,分布式引擎,实时搜索,高性能采集。显然,在大数据方面上,ES是个不错选择。但在对索引进行搜索上,Solr是远远优于ES的。我目前的项目,数据级在万以内,并且目前不打算分布式部署搜索引擎,所以我选择了Solr。


Solr部署

本次部署的话,是另开了个项目,采用了Jetty做容器。以下是Jetty的配置。(我萌萌哒的项目经理配的,我只是搬运工~)


public class JettySolrStart {
private static Server server;

public static void main(String[] args) throws Exception {
// change the default file encoding to utf-8
// need add the -Dfile.encoding=utf-8 to command line in deploy
// environment
int port=8983;
int listenerport=8984;
String path="/";
String rootdir="./webroot";
if (args!=null && args.length>0){
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
}
}
if (args!=null && args.length>1){
try{
listenerport=Integer.parseInt(args[1]);
}catch(Exception e){
}
}

if (args!=null && args.length>2){
path=args[2].trim();
}

if (args!=null && args.length>3){
rootdir=args[3].trim();
}

System.setProperty("file.encoding", "utf-8");
System.setProperty("solr.solr.home",  rootdir+"/WEB-INF/solr");
//System.setProperty("solr.solr.home",  rootdir);
server = new Server();

QueuedThreadPool threadPool = new QueuedThreadPool();
server.setThreadPool(threadPool);
Connector connector = new SelectChannelConnector();
connector.setPort(port);
server.setConnectors(new Connector[] { connector });
WebAppContext context = new WebAppContext(rootdir, path);
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection();
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(),
requestLogHandler });
contexts.addHandler(context);
server.setHandler(handlers);

server.setStopAtShutdown(true);
server.setSendServerVersion(true);

Thread monitor = new MonitorThread(listenerport);
monitor.start();
server.start();
server.join();
}

private static class MonitorThread extends Thread {

private ServerSocket socket;

public MonitorThread(int listenerport) {
setDaemon(true);
setName("StopMonitor");
try {
socket = new ServerSocket(listenerport, 1, InetAddress
.getByName("127.0.0.1"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void run() {
System.out.println("*** running jetty 'stop' thread");
Socket accept;
try {
accept = socket.accept();
BufferedReader reader = new BufferedReader(
new InputStreamReader(accept.getInputStream()));
reader.readLine();
System.out.println("*** stopping jetty embedded server");
server.stop();
accept.close();
socket.close();
System.exit(0);
} catch (Exception e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
}
}
}


public class JettySolrStop {

public static void main(String[] args) throws Exception {
int port=8984;
if (args!=null && args.length>0){
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){

}
}

Socket s = new Socket(InetAddress.getByName("127.0.0.1"), port);
OutputStream out = s.getOutputStream();
System.out.println("*** sending jetty stop request");
out.write(("\r\n").getBytes());
out.flush();
s.close();
}

}


类似于很多的Jetty配置,大家需要关注的点是2个端口设置,

int port=8983;
int listenerport=8984;


8983是启动端口 8984是监听端口,另外还要关注的是对Solr目录的读取,

System.setProperty("solr.solr.home",  rootdir+"/WEB-INF/solr");


这个目录结构:


以上文件大家可以在Solr的JAR包里获取,http://lucene.apache.org/solr/ 里下载即可,到这里大致部署是ok了。

Solr索引采集

这个点是我僵了最久了的~ 后来在看了N篇博客后,我大致有了思路。我采取,定时任务去跑数据库全量与增量导入,同时在任何基础请求进行后进行采集索引。

这里重点讲下前一点。基础的solr-solrj-4.7.1.jar是不支持数据库导入索引的,需要引入



接下来是对data-config.xml进行配置:

<?xml version="1.0" encoding="UTF-8" ?>

<dataConfig>
<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/test" user="test" password="test"/>
<document name="company">
<entity name="users" query="select * from users">
<field column="UserID"   name="UserID"/>
<field column="Number"   name="Number"/>
<field column="RealName" name="RealName"/>
<field column="Leavel" name="Leavel"/>
<field column="Degrees" name="Degrees"/>
<field column="GraduateSchool" name="GraduateSchool"/>
<field column="Professional" name="Professional"/>
<field column="HouseholdAdd" name="HouseholdAdd"/>
<field column="Residenc" name="Residenc"/>

<entity name="tq_sys_userHasRoles" query="select roleId from tq_sys_userHasRoles where userId='${users.UserId}'">
<field column="roleId" name="roleId"/>

<entity name="tq_sys_roles" query="select roleName,orgId from tq_sys_roles where id='${tq_sys_userHasRoles.roleId}'">
<field column="roleName" name="roleName"/>
<field column="orgId" name="orgId"/>

<entity name="tq_sys_organizations" query="select orgName from tq_sys_organizations where id='${tq_sys_roles.orgId}'">
<field column="orgName" name="orgName"/>
</entity>
</entity>
</entity>
</entity>
</document>
</dataConfig>


这是部分配置,可看出Solr的数据库导入是支持多表关联查询,然后封装成一个entity的。一个document下可以存在多个entity。(!!!这里的SQ语句复杂程度直接影响导入索引的速度)。

接下来是schema.xml,这文件比较繁杂,截取部分配置用到的吧。

<!-- users -->
<field name="UserId"    type="int"      indexed="true"  stored="true"  multiValued="false"/>
<field name="number"    type="int"      indexed="true"  stored="true"  multiValued="false"/>
<field name="RealName"  type="text_ik"   indexed="true"  stored="true"  multiValued="false"/>
<field name="Leavel"    type="string"   indexed="true"  stored="true"  multiValued="false"/>
<field name="Degrees" type="string"      indexed="true"  stored="true"  multiValued="false"/>
<field name="GraduateSchool"   type="text_ik"      indexed="true"  stored="true"  multiValued="false"/>
<field name="Professional"  type="text_ik"   indexed="true"  stored="true"  multiValued="false"/>
<field name="HouseholdAdd"  type="text_ik"   indexed="true"  stored="true"  multiValued="false"/>
<field name="Residenc"  type="text_ik"   indexed="true"  stored="true"  multiValued="false"/>
<field name="roleId"  type="int"   indexed="true"  stored="true"  multiValued="false"/>
<field name="roleName"  type="string"   indexed="true"  stored="true"  multiValued="false"/>
<field name="orgName"  type="string"   indexed="true"  stored="true"  multiValued="false"/>
<field name="orgId"  type="int"   indexed="true"  stored="true"  multiValued="false"/>


显然name与data-config.xml查出的字段名一一对应 ,solr支持各种type,普遍的string、int等都是可用,也支持自己构造type(例如 text_ik)。

介绍下另外3个属性吧,indexed是否可以查询,stored是否可以内容存储 ,multiValued是否复合索引。

这样配置完,可以打开solr的界面看下效果了。



点开dataimport



这里的clean是清空之前所有索引!!!慎勾!!!然后点击execute 你就可以在Query模块查到你所导入的索引啦~(≧▽≦)/~啦啦啦!

另一个数据实时支持,在任何基础请求进行后进行采集索引。这方面的话,我就贴下代码吧,也是比较简单的。

List<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
SolrInputDocument solrDocument = new SolrInputDocument();  solrDocument.addField("id", userInfo.getId().toString());   docs.add(solrDocument);
SolrUtil.addOrUpdate(docs,GetHttpSolrServer.getInstance());


Solr调用

呃~既然有索引了那接下来就好办了。由于Solr项目跟主项目分离了,我是采取了发送HTTP请求的方式调用Solr。这方面SolrJ提供了HttpSolrServer十分好用。不废话贴代码吧。

HttpSolrServer solr = SolrUtil.getSolrConnection();
SolrQuery params = new SolrQuery();
params.set("qt", "/select");
params.set("q", "RealName:"+q);
params.set("wt", "json");
params.setRows(Integer.MAX_VALUE);
params.setHighlight(true);                //开启高亮
params.setHighlightFragsize(200);          //返回的字符个数
params.setHighlightRequireFieldMatch(true);
params.setHighlightSimplePost("<em>");    //前缀
params.setHighlightSimplePre("</em>");    //后缀
//高亮字段
params.addHighlightField("RealName");
QueryResponse query = solr.query(params);
QueryResponse req = solr.query(params);
SolrDocumentList results = query.getResults();


在最后得到K-V数据处理方面我也是困惑了很久。

为了给前端大哥相对好处理的数据集,我采用了封装成对象,然后根据数据动态映射set方法。(这方面涉及method/invoke的知识,网上还是蛮多的,大家可以自行研究下。)

这里眼尖的 有可能看到了高亮,solr对高亮是有很健壮的支持的,以及之前的分词。我贴下我的配置代码

<!-- Highlighting defaults -->
<!-- hl是指定是否使用高亮;hl.fl,指定对哪些域进行高亮,对多个域进行高亮的话,好像是用逗号隔开;
f.name.hl.fragsize是指摘要的长度,默认0代表不做摘要。而hl.simple.pre和hl.simple.post则是指定高亮时显示的格式,默认是<em></em> -->
<str name="hl">on</str>
<str name="hl.fl">content features title name</str>
<str name="hl.encoder">html</str>
<str name="hl.simple.pre"><font color="red"></str>
<str name="hl.simple.post"></b></str>
<str name="f.title.hl.fragsize">100</str>
<str name="f.title.hl.alternateField">title</str>
<str name="f.name.hl.fragsize">0</str>
<str name="f.name.hl.alternateField">name</str>
<str name="f.content.hl.snippets">3</str>
<str name="f.content.hl.fragsize">200</str>
<str name="f.content.hl.alternateField">content</str>
<strname="f.content.hl.maxAlternateFieldLength">750</str>


新建一个IKAnalyzer.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典
<entry key="ext_dict">ext.dic;</entry>
-->
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic;</entry>

</properties>


以及

<!-- 分词 -->
<fieldType name="text_ik" class="solr.TextField">
<analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>


下班了写的有点急 有什么不对欢迎大家指出来~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: