浅尝Solr~~

来源:互联网 发布:百度人工智能研究 编辑:程序博客网 时间:2024/05/29 18:37

由于最近项目组有需求,大致意思是做一个对数据全面的统一搜索。于是乎,就研究了一哈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">&lt;font color=&quot;red&quot;&gt;</str>       <str name="hl.simple.post">&lt;/b&gt;</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>        

下班了写的有点急 有什么不对欢迎大家指出来~

1 0