solr的基本使用

来源:互联网 发布:mac制作铃声 编辑:程序博客网 时间:2024/05/19 11:45

最近负责的系统要接入solr,原因嘛,就是那些前辈们以前对数据库的操作,比如全表扫描,循环全表扫描等,随着库中数据的增大,导致系统动不动就罢工,全部改动需要时间,紧急解决方案就是用solr部分替代原数据库。。。

先说说什么是solr,我从百度上复制了一点定义:

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

多了我就不复制了,大家可以网上搜搜看,我这里把我如何用到solr,已经用到了solr中的哪些地方和如何使用展示出来,solr的功能非常强大,但是我用到的到不是很多。

下面我按照需求-思路-实现来讲解solr的使用,这是我平生第一次使用solr,不到之处还望多多指正:


需求原因:系统数据量太大,根据系统业务逻辑需要,sql已经没有多少优化空间,并发量太大和过大的数据量导致查询响应速度缓慢,页面加载缓慢,用户体验非常不好。

需求:系统原来的逻辑是:redis->DB,修改为redis->solr->DB

这样设计的原因:

1、首先同等并发量和数据量的情况下,solr检索出结果集的速度远大于关系型数据库(具体原因涉及到检索排序算法<solr是基于倒排序算法的> 等多种原因)

2、从solr服务器中读取数据,就避免了访问访问DB,除非solr服务器出现问题,这个概率不是很大

当然主要原因还是第一个,查询速度快,用户体验会上升


开发思路:既然系统要接入solr,自己首先得弄清楚原理,为什么solr可以当作数据库来使用。

solr是企业级搜索服务器,提供对数据的存储,数据索引创建等一系列功能。比如对一条数据创建索引的时候,同时将这个数据保存到solr服务器,就可以根据索引数据查询到整条数据,知道solr可以当作容器一样存放数据,除此之外,solr还提供了跟DB类似的功能-对数据的增删改查,知道这些,就知道为什么可以“替代”DB了吧。

代码开发阶段:

一、开发环境的搭建:

solr是独立的服务器,所以我们需要搭建环境,我使用的应用服务器是tomcat,将solr接到tomcat服务器中即可,至于怎么配置的,我这里不想详细说明了,网上配置这个东西的就像配置JDK环境变量一样多,大家自己动手,丰衣足食,我会上传一个我配置好solr功能的tomcat服务器,大家可以直接下载,地址:http://download.csdn.net/detail/duyunduzai/7286411

二、代码开发:

我代码中是将solr封装成一个接口,只需要调用对索引增删改查的接口即可。使用的是solrj系类功能,下面会针对代码详细讲述。

1、创建接口

/** *  * 前面文章中说过了,是对系统的优化,假如之前数据库中保存的数据是用户的信息 * User:username,age,email,custNo; * */public interface ISolrUserService {    /**     *  一句话功能描述:创建用户索引数据     */    public boolean createUserIndex(User user);    /**     * 一句话功能描述:批量创建用户索引数据     */    public boolean createUserIndex(List<User> userLists);    /**     * 一句话功能描述:批量删除用户索引数据     */    public boolean deleteUserIndex(List<String> custNos);    /**     * 一句话功能描述:查询用户数据     */    public QueryResult<SearchUserDTO> queryUser(SearchPage page);}

上面的代码是一个接口,其中包含的是对用户数据索引的创建、删除和查询,User类是用户信息类,包含四个属性:username,age,email,custNo,创建对应的实体类:

public class User {private String custNo;private String username;private String email;private int age;     // 省略get和set方法}


第一个代码片段中的QueryResult是我写的查询结果类,SearchPage是一个查询条件类,我先把代码贴出来给大家看看:

/** *  * 功能描述: 查询结果基类 */public class QueryResult<T> implements Serializable {    private static final long serialVersionUID = 1L;    private List<T>           datas;    private Boolean           isLastPage;    private Integer           totalDataCount;    private int               pageNumber       = 1;    private int               pageSize         = 10;    private Integer           pageCount;    private int               indexNumber;    /**     * @param totalDataCount 总数据件数     * @param pageSize 每页显示条数     * @param pageNumber 当前的页数     */    public QueryResult(int totalDataCount, int pageSize, int pageNumber) {        super();        this.totalDataCount = totalDataCount;        this.pageSize = pageSize;        this.pageNumber = pageNumber;        if (this.pageNumber < 1) {            this.pageNumber = 1;        }        if (this.totalDataCount <= 0) {            return;        }        // 如果查询页数大于总页数,则取最后一页        if (this.totalDataCount <= (this.pageNumber - 1) * this.pageSize) {            this.pageNumber = (this.totalDataCount + this.pageSize - 1) / this.pageSize;        }        this.indexNumber = (this.pageNumber - 1) * this.pageSize;        // 总页数        this.pageCount = (this.totalDataCount + this.pageSize - 1) / this.pageSize;        // 是否为最后一页        this.isLastPage = (this.pageNumber == this.pageCount ? true : false);    }    public QueryResult() {        super();    }    /**     * 返回的数据集     * @return the datas     */    public List<T> getDatas() {        return datas;    }    /**     * @param datas the datas to set     */    public void setDatas(List<T> datas) {        this.datas = datas;    }    /**     * 满足查询条件的总记录数, null 意味着未知。注:只在查询第一页时返回正确的总记录数,其它页码时,返回-1     * @return the totalDataCount     */    public Integer getTotalDataCount() {        return totalDataCount;    }    /**     * @param totalDataCount the totalDataCount to set     */    public void setTotalDataCount(Integer totalDataCount) {        this.totalDataCount = totalDataCount;    }    /**     * 页码,从1开始     * @return the pageNumber     */    public int getPageNumber() {        return pageNumber;    }    /**     * @param pageNumber the pageNumber to set     */    public void setPageNumber(int pageNumber) {        this.pageNumber = pageNumber;    }    /**     * 满足查询条件的总页数, null 意味着未知。注:只在查询第一页时返回正确的总记录数,其它页码时,返回-1     *      * @return the pageCount     */    public Integer getPageCount() {        return pageCount;    }    /**     * @param pageCount the pageCount to set     */    public void setPageCount(Integer pageCount) {        this.pageCount = pageCount;    }    /**     * 每页大小,缺省为10条记录/页     * @return the pageSize     */    public int getPageSize() {        return pageSize;    }    /**     * @param pageSize the pageSize to set     */    public void setPageSize(int pageSize) {        this.pageSize = pageSize;    }    /**     * 标志是否最后一页,True: 是最后一页,False: 不是,null:未知     * @return the lastPage     */    public Boolean getIsLastPage() {        return isLastPage;    }    /**     * @param lastPage the lastPage to set     */    public void setIsLastPage(Boolean lastPage) {        this.isLastPage = lastPage;    }    /**     * 计算开始数     * @return the lastPage     */    public int getIndexNumber() {        return indexNumber;    }}

上面是查询结果基类,还有一个类是查询条件类

public class SearchPage implements Serializable {    private static final long serialVersionUID = 1L;    /** 页码 */    private int               pageNumber       = 1;    /** 每页记录数 */    private int               pageSize         = 10;    /** 总记录数 */    private int               totalCount;    /** 排序字段 */    private String[]          orderType;    /**     * 查询关键字     */    private String            keyword;    /** 多条件查询 */    private String            selectParam;    /** 默认查询字段 */    private String            field;    private int               Start            = 0;// 省略get和set    }

上面贴了这么多代码,其实都是跟solr无关的代码,不过可以完整展现我的代码逻辑,方便大家给我指正

接下来是对isolrUserService接口的实现类,在实现类中就是具体的实现如何对数据创建索引,删除索引等操作

@Servicepublic class SolrUserServiceImpl implements IsolrUserService {    private static final Logger   LOGGER = LoggerFactory.getLogger(SolrUserServiceImpl.class);    private static HttpSolrServer httpSolrServer;/** httpServer 是用来连接solr服务器,这里采用单例模式设计 */    private static HttpSolrServer getHttpSolrServer() {        if (httpSolrServer == null) {/** 用户(User)数据solr服务地址 */            httpSolrServer = new HttpSolrServer("http://127.0.0.1:8983/solr/user");/** 设置solr查询超时时间 */            httpSolrServer.setSoTimeout(1000);/** 设置solr连接超时时间 */            httpSolrServer.setConnectionTimeout(1000);/** solr最大连接数 */            httpSolrServer.setDefaultMaxConnectionsPerHost(1000);/** solr最大重试次数 */            httpSolrServer.setMaxRetries(1);/** solr所有最大连接数 */            httpSolrServer.setMaxTotalConnections(100);/** solr是否允许压缩 */            httpSolrServer.setAllowCompression(false);            /** solr是否followRedirects */            httpSolrServer.setFollowRedirects(true);        }        return httpSolrServer;    }    @Override    public boolean createUserIndex(User user) {        // 获取solr服务        SolrServer solrServer = getHttpSolrServer();        try {            // 创建索引,因为solr创建索引的时候,在参数类中的属性上面需要注解@Field,//所以,要将user类转换成可以创建索引的类,我单独创建了一个类,对应User,// SearchUserDTO.java,跟User类属性一样,只是在各个属性上面添加@Field注解            solrServer.addBean(toSearchUser(user));// 提交创建。就相当于DB中的commit            solrServer.commit();            return true;        } catch (IOException e) {            LOGGER.error("", e);        } catch (SolrServerException e) {            LOGGER.error("", e);        }        return false;    }    private SearchUserDTO toSearchUser(User user) {        SearchUserDTO userDTO = new SearchUserDTO();// 此方法是将user中属性的值复制到userDTO属性,这个方法是复制类中属性名一样的属性值        BeanUtils.copyProperties(user, userDTO);        return userDTO;    }    @Override    public boolean createUserIndex(List<User> users) {        if (users != null && users.size() > 0) {            List<SearchUserDTO> datas = new ArrayList<SearchUserDTO>(users.size());            for (User user : users) {                datas.add(toSearchUser(user));            }            SolrServer solrServer = getHttpSolrServer();            try {                // 批量创建评价回复索引数据                solrServer.addBeans(datas);                solrServer.commit();                return true;            } catch (IOException e) {// 如果创建失败的话,可以回滚// solrServer.collback();                LOGGER.error("", e);            } catch (SolrServerException e) {                LOGGER.error("", e);            }        }        return false;    }    @Override    public boolean deleteUserIndex(List<String> custNos) {        SolrServer solrServer = getHttpSolrServer();        try {// 根据唯一性标识删除索引            solrServer.deleteById(custNos);// 删除该核下所有索引// solrServer.delete("*:*");            solrServer.commit();            return true;        } catch (IOException e) {            LOGGER.error("", e);        } catch (SolrServerException e) {            LOGGER.error("", e);        }        return false;    }    @Override    public QueryResult<SearchUserDTO> queryUser(SearchPage pager) {        QueryResult<SearchUserDTO> queryResult = new QueryResult<SearchUserDTO>();        QueryResponse response = null;        // 设置默认查询条件,格式为:field:keyword,比如:"custNo:1234"        String searchParam = pager.getField() + ":" + pager.getKeyword();        SolrServer server = getHttpSolrServer();        SolrQuery query = new SolrQuery(searchParam);        // 设置限制条件查询,假如同时查询username为zhangsan的用户,这里查询条件格式我就暂不多说了,下面会和配置文件一起来说一下        // query.setFilterQueries("username:zhangsan");        query.setFilterQueries(pager.getSelectParam());        query.setStart(pager.getStart()); // 起始位置,用于分页,solrj中默认是每页10条数据        query.setRows(pager.getPageSize()); // 每页文档数        try {            response = server.query(query);        } catch (SolrServerException e) {            LOGGER.error("查询索引出现问题", e);        }        if (response != null) {            SolrDocumentList list = response.getResults();            List<SearchUserDTO> datas = new ArrayList<SearchUserDTO>();            setSearchUserDTOData(datas, list);            queryResult.setTotalDataCount(new Long(list.getNumFound()).intValue());            queryResult.setPageNumber(pager.getPageNumber());            queryResult.setPageSize(pager.getPageSize());            queryResult.setDatas(datas);        }        return queryResult;    }    private void setSearchUserDTOData(List<SearchUserDTO> datas, SolrDocumentList list) {        for (SolrDocument solrDocument : list) {            SearchUserDTO userDTO = new SearchUserDTO();// 根据属性名称从返回结果中取得数据,并且封装到返回对象中            SearchUserDTO.setUsername(solrDocument.getFieldValue("username").toString());            SearchUserDTO.setEmail(solrDocument.getFieldValue("email").toString());            SearchUserDTO.setCustNo(solrDocument.getFieldValue("custNo").toString());SearchUserDTO.setAger((Integer)solrDocument.getFieldValue("age"));            datas.add(SearchUserDTO);        }    }}


上面的代码是实现了对索引的创建,删除以及查询,对索引的更新操作其实就是对索引的重新创建,就像redis中创建键值时一样的,创建已存在的健的话新值就是把原来的值覆盖掉。大家肯定会有疑问,solr是如何把类的字段跟索引连接到一起的呢,其实这里面是有一个配置文件的,下面重点讲讲这个配置文件:


solr服务支持单核和多核,假如只是对一张表进行创建索引数据,只使用单核就可以了,但是如果你对用户表创建索引数据,同时又对商品信息表创建索引数据,就需要对这两个表的索引数据放在不同 的地址下面,这里就利用到了solr多核,我上传到tomcat-solr服务器就是多核配置


解压tomcat-solr,在../apache-tomcat-7.0.26-master\webapps\solr\conf\multicore\下面有一个solr.xml,这里配置的是多核信息,在user、productInfo表示两个核,这里以user为例,user文件夹下有两个文件夹,conf和data,其中data中存放的是索引文件,这个就不多说了,索引文件的格式,内容等是solrj创建索引的时候写到里面去的,说一下conf问价夹,conf下面有三个文件:solrconfig.xml,scheam.xml和dataimport.properties,其中solrconfig.xml和dataimport.properties是可以配置很多功能的,但是我这个例子中只需要用到schema.xml配置,其余两个用默认就行了,重点讲下schema.xml:

<?xml version="1.0" ?><schema name="example core user" version="1.1">  <types>    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>      <!--add IKAnalyzer configuration-->      <fieldType name="textik" class="solr.TextField" >        <analyzer type="index">          <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" isMaxWordLength="false" useSmart="false"/>        </analyzer>        <analyzer type="query">          <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" isMaxWordLength="false" useSmart="false"/>        </analyzer>    </fieldType><fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>  </types>  <fields>    <field name="username"      type="textik" indexed="true"  stored="true" multiValued="false" required="true"/>    <field name="custNo" type="string" indexed="true"  stored="true" multiValued="false" required="true" />     <field name="email"     type="string" indexed="true" stored="true" multiValued="false" /><field name="age"     type="int" indexed="false" stored="true" multiValued="false" />  </fields>  <!-- field to use to determine and enforce document uniqueness. -->  <uniqueKey>custNo</uniqueKey>  <!-- field for the QueryParser to use when an explicit fieldname is absent -->  <defaultSearchField>username</defaultSearchField>  <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->  <solrQueryParser defaultOperator="OR"/></schema>

这里面配置的是一些字段信息,从上到下依次是types,fields,uniquekey,defaultSearchField,solrQueryParser,还有其他的一些配置的,我们这里没用到而已

types:这里面配置的是字段类型,就像java中的int,long,String等,用fieldType标签表示,以String为例:

<fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
name:FieldType的名称

class:指向org.apache.solr.analysis包里面对应的class名称,用来定义这个类型的行为

sortMissingLast:设置成true没有该field的数据排在有该field的数据之后,而不管请求时的排序规则, 默认是设置成false。 sortMissingFirst 跟上面倒过来呗

omitNorms:true 则字段检索时被省略相关的规范

fields:

name:字段名称
type:类型,此处写的类型必须在fieldType中声明,假如你type=long,在fieldType中就要有对应的long的声明(<fieldtype name="long" class="solr.LongField" omitNorms="true"/>),不然启动服务时会报错
indexed:是否创建索引,true表示创建,默认false,加入你username的indexed=false,那么你用username来查询索引是查询不到的
stored:是否保存数据,如果false的话,就查询返回结果中该字段数据就没有,这个属性在设计的时候是要注意的,有写字段不需要的话可以设为false,可以减小索引件的大小
multiValued:是否多值,true的话就是多值,假如username的multiValued=true,在索引文件中查出的结果就是username['zhangsan','lisi'..]
required:是否必须,假如username的required=true,那么在创建索引的时候,username就必须有值,否则创建不成功
当然,field还包含其他很多属性,比如默认值defaule,是否压缩compressed等就不多写了,因为我没用到。

uniqueKey:唯一性主键

defaultSearchField:默认使用该字段查询,但是我们往往查询的时候是根据自己需要查询的,比如我们查询条件为:username:zhangsan,如果直接写成zhangsan的话,查询条件就是:"defauleSearchField:zhangsan"

solrQueryParser:设置默认操作,OR还是AND,加入我们设置<solrQueryParser defaultOperator="OR"/>,我们查询username:zhangsan lisi ,就相当于username=zhangsan OR username=lisi,如果这个地方我们设置成and的话,就是username=zhagnsan and username=lisi

配置文件这里就讲完了,现在大家应该懂了吧,下面加一点查询扩展

/*设置查询条件*查询所有:*:**按照名称查询:username:zhangsan*按照email查询:email:1234@sina.cn*我们假设按照名称查询*/ SolrQuery query = new SolrQuery("username:zhangsan");        /**设置限制级查询条件*假如我们同时查出email是1234@sina.cn的**这里面也可以多条件,假如email是1234@sina.cn同时年龄是21*就应该写成:email:1234@sina.cn AND age:21(注意这里面的AND和OR必须大写)*/        query.setFilterQueries("email:1234@sina.cn AND age:21");/**设置排序*假如要求按照名称降序*/query.addSortField("username", ORDER.desc);query.addSortField(username, order);        query.setStart(pager.getStart()); // 起始位置,用于分页        query.setRows(pager.getPageSize()); // 每页文档数response = server.query(query);
// 概括一下上面的查询条件就是:username=zhangsan,age=21 email=1234@sina.cn,按照username降序排列(上面age的indexed=false,应该改为true,才能按照age索引)

根据这些,基本上可以满足对数据的条件查询了,如果是多表关联查询的话,我现在只知道从一个索引文件中查到数据,然后根据标识去另一个索引中查找,没发现有可以替代DB中的join表的功能,solr中还有很多功能,有高亮显示查询结果,根据各种复杂的算法解析啊等,第一次接触solr,就写到这儿吧,望各位指出不足之处




0 0
原创粉丝点击