先說說什麼是solr,我從百度上覆制了一點定義:
html
Solr是一個獨立的企業級搜索應用服務器,它對外提供相似於Web-service的API接口。用戶能夠經過http請求,向搜索引擎服務器提交必定格式的XML文件,生成索引;也能夠經過Http Get操做提出查找請求,並獲得XML格式的返回,Solr是一個高性能,採用Java5開發,基於Lucene的全文搜索服務器。同時對其進行了擴展,提供了比Lucene更爲豐富的查詢語言,同時實現了可配置、可擴展並對查詢性能進行了優化,而且提供了一個完善的功能管理界面,是一款很是優秀的全文搜索引擎。java
多了我就不復制了,你們能夠網上搜搜看,我這裏把我如何用到solr,已經用到了solr中的哪些地方和如何使用展現出來,solr的功能很是強大,可是我用到的到不是不少。web
下面我按照需求-思路-實現來說解solr的使用,這是我生平第一次使用solr,不到之處還望多多指正:redis
需求緣由:系統數據量太大,根據系統業務邏輯須要,sql已經沒有多少優化空間,併發量太大和過大的數據量致使查詢響應速度緩慢,頁面加載緩慢,用戶體驗很是很差。算法
需求:系統原來的邏輯是:redis->DB,修改成redis->solr->DBsql
這樣設計的緣由:數據庫
一、首先同等併發量和數據量的狀況下,solr檢索出結果集的速度遠大於關係型數據庫(具體緣由涉及到檢索排序算法<solr是基於倒排序算法的> 等多種緣由)apache
二、從solr服務器中讀取數據,就避免了訪問訪問DB,除非solr服務器出現問題,這個機率不是很大tomcat
固然主要緣由仍是第一個,查詢速度快,用戶體驗會上升服務器
開發思路:既然系統要接入solr,本身首先得弄清楚原理,爲何solr能夠看成數據庫來使用。
solr是企業級搜索服務器,提供對數據的存儲,數據索引建立等一系列功能。好比對一條數據建立索引的時候,同時將這個數據保存到solr服務器,就能夠根據索引數據查詢到整條數據,知道solr能夠看成容器同樣存放數據,除此以外,solr還提供了跟DB相似的功能-對數據的增刪改查,知道這些,就知道爲何能夠「替代」DB了吧。
代碼開發階段:
1、開發環境的搭建:
solr是獨立的服務器,因此咱們須要搭建環境,我使用的應用服務器是tomcat,將solr接到tomcat服務器中便可,至於怎麼配置的,我這裏不想詳細說明了,網上配置這個東西的就像配置JDK環境變量同樣多,你們本身動手,豐衣足食,我會上傳一個我配置好solr功能的tomcat服務器,你們能夠直接下載,地址:http://download.csdn.net/detail/duyunduzai/7286411
2、代碼開發:
我代碼中是將solr封裝成一個接口,只須要調用對索引增刪改查的接口便可。使用的是solrj系類功能,下面會針對代碼詳細講述。
一、建立接口
/** * * 前面文章中說過了,是對系統的優化,假如以前數據庫中保存的數據是用戶的信息 * 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接口的實現類,在實現類中就是具體的實現如何對數據建立索引,刪除索引等操做
@Service public 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://10.22.10.110: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,就寫到這兒吧,望各位指出不足之處