Solr入門和實踐以及我對Solr的8點理解
友情提示
Solr的內容還是比較多的,一篇文章只能講解一部分。
全面介紹,沒興趣,沒時間,也沒能力,回報還不大。
本文只寫點我認為比較重要的知識點,獨特的個人想法。
僅供參考哦,更多細節需要自己去琢磨。
概述
Solr是一個高性能,采用Java5開發,基于Lucene的全文搜索服務器。同時對其進行了擴展,提供了比Lucene更為豐富的查詢語言,
同時實現了可配置、可擴展并對查詢性能進行了優化,并且提供了一個完善的功能管理界面,是一款非常優秀的全文搜索引擎。
工作方式
文檔通過Http利用XML 加到一個搜索集合中。
查詢該集合也是通過http收到一個XML/JSON響應來實現。
它的主要特性包括:高效、靈活的緩存功能,垂直搜索功能,高亮顯示搜索結果,通過索引復制來提高可用性,
提供一套強大Data Schema來定義字段,類型和設置文本分析,提供基于Web的管理界面等。
需求場景
查詢和搜索,我們直接查詢數據庫MySQL。查詢數據庫主要有一些局限性:
比如多表查詢效率低,大文本字段不好建立索引和搜索,復雜的條件查詢和搜索功能不夠強大,或者說不夠簡單。
使用Solr的話,就很簡單地解決了以上問題。
以上需求,或者說關系型數據庫mysql的問題,只是目前的一點理解。
雖說能夠使用MySQL和Sorl解決實際中的問題,但畢竟都是中低難度的問題(自認為如此哦)。
非要說深入理解,剖析Solr的好處,MySQL是否“干的過”Solr,真心不懂。
單獨搞MySQL,夠你研究5年以上,DBA畢竟是個傳說。
Solr,想搞懂,也得好多年。
個人同時學習Java服務端、Android、iOS、Web前端,目標是能夠解決工作中最常見的問題,并不想要
深入學習有限的幾種技術,比如MySQL,達到那種“再難的問題,也可以搞定”的程度。
我對Solr的8點理解
1.定義數據源接口,獲得數據。
比如定義MySQL查詢語句,把一個表或多個表的數據,導入到Solr中。
這個地方我覺得特別“不公平”,數據都是從別的地方搞過來的。外界的數據如果會變化,意味著,必須處理“數據同步”。
實時性要求不高的情況下,可以每天“全量更新”。要求高的情況下,單條數據的變化,需要“實時更新-單條”。
因此,Solr和Mysql并不是“直接競爭”關系,而是“互補”的關系。
2.把Mysql等數據源的數據,導入到Solr中去。
Solr定義數據,可以理解成一張很大的表,包含了很多字段,比如可以包含mysql中3個表的所有字段。
這樣,查詢就不存在“多表”的問題。
既然是一張表,建立索引,查詢就很快了。
3.自帶緩存功能。
Mysql,Solr,Redis等數據源或者有能力獲得數據和管理數據的組件,只要需要,就可以提供“緩存”功能。
Solr簡化了查詢,緩存就更容易了。
4.索引和全文搜索。
Solr底層采用Lucene建立索引,全文索引,這樣可以實現更多的“搜索功能”,可以說增強了Mysql的查詢。
5.站內搜索的另外一種形式。
百度等搜索引擎,可以為網站提供“站內搜索”功能,他們爬去數據可以是公開的URL的形式。
如果需要和百度等合作,可以申請使用百度的搜索API,將站內數據,更友好,更快速地告訴百度。
而Solr和百度提供的搜索相關接口就基本一樣,只不過是處在我們的管理之下。
6.簡潔使用的管理界面。
后臺有Web界面,導入數據,更新,可以通過可視化的操作來管理,比較方便。
7.功能服務化。
Solr提供的查詢等功能,有Java等多種語言的實現。
建立數據結構,導入數據,維護緩存和實時性,最重要的就是“查詢”和“搜索”了。
8.最大的“隱患”。
只用Mysql管理數據和查詢的時候,我們必須并且只需要保障mysql的“高可用性”。
不能出任何問題,如果只用1個Mysql,意味著我們需要實時監控Mysql是否可用,如果出了問題,我們需要立即修復它。
如果是多臺Mysql,我們需要用主從,或者更復雜的主從。
現在用了Solr,意味著,我們很多查詢和搜索,優先使用Solr,不再使用Mysql。
這個時候,為了“高可靠性”,我們也必須保障Solr是靠譜的。
單臺Solr服務器,可靠性怎么樣,我不太清楚。
無論單臺Solr是否靠譜,多臺Solr更加靠譜,這都意味著
“我們程序中必須可靠的基礎服務更多了”。
常見的必須“高可用性”的服務有
a.Mysql
b.Redis
3.Nginx
4.Solr
高可用性的服務越多,意味著我們的程序越復雜。
大部分的公司,都是中小型企業。
大部分的應用,都是為了快速推出,看看是否有效果。
真正需要保障“高可靠性”的項目,是很少的,如果遇到了,是很幸運的。
官方網站:http://lucene.apache.org/solr/
本地環境:Windows-5.3.1版本
運行和建立工程
啟動:solr.cmd start(類似這樣)
建立工程:
name=raikou
config=solrconfig.xml
schema=schema.xml
dataDir=J\:\SoftData\Solr\raikou\data
指定config、schema等多種參數。
(圖文并茂的入門指引,可以參考其它博主的文章,本人覺得這種“圖文并茂”的太尼瑪費事了。
方便了讀者,但是“技術含量”不夠高,博主表示不過癮o(︶︿︶)o )
簡要介紹下幾個配置,附帶源文件內容
core.properties
name=raikou(項目名稱) config=solrconfig.xml(Solr配置) schema=schema.xml(模式定義) dataDir=J\:\\SoftData\\Solr\\raikou\\data (存儲索引等數據)
Web界面輸入的內容,保存在這了,入口配置,可以這么說。
schema.xml
<field name="id" type="long" indexed="true" stored="true" required="true" multiValued="false" /> <field name="title" type="string" indexed="true" stored="true" required="true" /> <field name="content" type="string" indexed="true" stored="true" /> <field name="summary" type="string" indexed="true" stored="true" />
定義了幾個字段
<uniqueKey>id</uniqueKey>
<defaultSearchField>title</defaultSearchField>
唯一字段,默認查詢字段
schemal.xml還配置了若干其它配置文件,比如“stopwords_en.txt”、“protwords.txt”、“stopwords.txt”等。
如果Solr啟動報錯,可能是缺少了這些字段。
data-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataConfig>
<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/raikou?useUnicode=true&characterEncoding=UTF-8"
user="root"
password="mypassword"/><document name="raikou_article">
<entity name="raikou_article" query="select from raikou_article" deltaImportQuery="select from raikou_article where id='${dih.delta.id}'"
deltaQuery="select * from raikou_article where update_time > '${dataimporter.last_index_time}'">
<field column="id" name="id" />
<field column="title" name="title" /> <field column="content" name="content" /> <field column="summary" name="summary" /> </entity>
</document>
</dataConfig></pre>
定義了數據導入、增量更新的查詢語句。
web.xml 這段配置,可能有用
E:\Mongodb-Redis-Nginx\solr-5.3.1\server\solr-webapp\webapp\WEB-INF\web.xml
<!-- People who want to hardcode their "Solr Home" directly into the WAR File can set the JNDI property here... --><env-entry> <env-entry-name>solr/home</env-entry-name> <env-entry-value>J:\SoftData\Solr\</env-entry-value> <env-entry-type>java.lang.String</env-entry-type> </env-entry></pre><br />
Java程序訪問
maven配置
<dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId> <version>5.3.1</version> </dependency>
包名:org.apache.solr.client.solrj
工具類
SolrHelper.java 查詢(查詢語句構造和執行查詢,分頁查詢),更新,重建索引
import java.beans.PropertyDescriptor; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List;import org.apache.commons.lang3.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import com.github.pagehelper.Page;
/*查詢(查詢語句構造),更新,重建索引/ public class SolrHelper<T> {
protected final Logger logger = LoggerFactory.getLogger(SolrHelper.class); private HttpSolrClient server; private StringBuffer queryString; public SolrHelper(String reqUrl) { server = new HttpSolrClient(reqUrl); queryString = new StringBuffer(); } public void andEquals(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":").append(val); } public void orEquals(String fieldName, String val) { queryString.append(" || ").append(fieldName).append(":").append(val); } public void andNotEquals(String fieldName, String val) { queryString.append(" && ").append("-").append(fieldName).append(":") .append(val); } public void orNotEquals(String fieldName, String val) { queryString.append(" || ").append("-").append(fieldName).append(":") .append(val); } public void andGreaterThan(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append(val) .append(" TO ").append("*]"); } public void orGreaterThan(String fieldName, String val) { queryString.append(" || ").append(fieldName).append(":[").append(val) .append(" TO ").append("*]"); } public void andGreaterThanOrEqualTo(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append(val) .append(" TO ").append("*]"); } public void orGreaterThanOrEqualTo(String fieldName, String val) { queryString.append(" || ").append(fieldName).append(":[").append(val) .append(" TO ").append("*]"); } public void andDateGreaterThan(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[") .append(formatUTCString(val)).append(" TO ").append("*]"); } public void orDateGreaterThan(String fieldName, Date val) { queryString.append(" || ").append(fieldName).append(":[") .append(formatUTCString(val)).append(" TO ").append("*]"); } public void andDateGreaterThanOrEqualTo(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[") .append(formatUTCString(val)).append(" TO ").append("*]"); } public void orDateGreaterThanOrEqualTo(String fieldName, Date val) { queryString.append(" || ").append(fieldName).append(":[") .append(formatUTCString(val)).append(" TO ").append("*]"); } public void andLessThan(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(val).append("]"); } public void orLessThan(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(val).append("]"); } public void andLessThanOrEqualTo(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(val).append("]"); } public void orLessThanOrEqualTo(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(val).append("]"); } public void andDateLessThan(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(formatUTCString(val)).append("]"); } public void orDateLessThan(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(formatUTCString(val)).append("]"); } public void andDateLessThanOrEqualTo(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(formatUTCString(val)).append("]"); } public void orDateLessThanOrEqualTo(String fieldName, Date val) { queryString.append(" && ").append(fieldName).append(":[").append("*") .append(" TO ").append(formatUTCString(val)).append("]"); } public void andLike(String fieldName, String val) { queryString.append(" && ").append(fieldName).append(":*").append(val) .append("*"); } public void orLike(String fieldName, String val) { queryString.append(" || ").append(fieldName).append(":*").append(val) .append("*"); } public void andNotLike(String fieldName, String val) { queryString.append(" && ").append("-").append(fieldName).append(":*") .append(val).append("*"); } public void orNotLike(String fieldName, String val) { queryString.append(" || ").append("-").append(fieldName).append(":*") .append(val).append("*"); } public void andIn(String fieldName, String[] vals) { queryString.append(" && "); in(fieldName, vals); } private void in(String fieldName, String[] vals) { List<String> list=Arrays.asList(vals); in(queryString,fieldName,list); } public void orIn(String fieldName, List<String> vals) { queryString.append(" || "); in(queryString,fieldName,vals); } private static void in(StringBuffer queryString,String fieldName, List<String> vals) { queryString.append("("); inStr(queryString, fieldName, vals); queryString.append(")"); } private static void inStr(StringBuffer queryString, String fieldName, List<String> vals) { int index = 0; for (String val : vals) { if (0 != index) { queryString.append(" || "); } queryString.append(fieldName).append(":").append(val); index++; } } // http://stackoverflow.com/questions/634765/using-or-and-not-in-solr-query //instead of "NOT [condition]" use "(*:* NOT [condition])" public void andNotIn(String fieldName, String[] vals) { List<String> list=Arrays.asList(vals); queryString.append("&&("); queryString.append("*:* NOT "); inStr(queryString, fieldName, list); queryString.append(")"); } public void andDateBetween(String fieldName, Date startDate, Date endDate) { queryString.append(" && ").append(fieldName).append(":[") .append(formatUTCString(startDate)).append(" TO ") .append(formatUTCString(endDate)).append("]"); } public void orDateBetween(String fieldName, Date startDate, Date endDate) { queryString.append(" || ").append(fieldName).append(":[") .append(formatUTCString(startDate)).append(" TO ") .append(formatUTCString(endDate)).append("]"); } public void andDateNotBetween(String fieldName, Date startDate, Date endDate) { queryString.append(" && ").append("-").append(fieldName).append(":[") .append(formatUTCString(startDate)).append(" TO ") .append(formatUTCString(endDate)).append("]"); } public void orDateNotBetween(String fieldName, Date startDate, Date endDate) { queryString.append(" && ").append("-").append(fieldName).append(":[") .append(formatUTCString(startDate)).append(" TO ") .append(formatUTCString(endDate)).append("]"); } public void andBetween(String fieldName, String start, String end) { queryString.append(" && ").append(fieldName).append(":[").append(start) .append(" TO ").append(end).append("]"); } public void orBetween(String fieldName, String start, String end) { queryString.append(" || ").append(fieldName).append(":[").append(start) .append(" TO ").append(end).append("]"); } public void andNotBetween(String fieldName, String start, String end) { queryString.append(" && ").append("-").append(fieldName).append(":[") .append(start).append(" TO ").append(end).append("]"); } public void orNotBetween(String fieldName, String start, String end) { queryString.append(" || ").append("-").append(fieldName).append(":[") .append(start).append(" TO ").append(end).append("]"); } public void andStartSub() { queryString.append(" && ("); } public void orStartSub() { queryString.append(" || ("); } public void endSub() { queryString.append(")"); } private String formatUTCString(Date d) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); String s = sdf.format(d); return s; } public int execQueryTotalCount() { SolrQuery params = handleQuery(); params.set("start", 0); params.set("rows", Integer.MAX_VALUE); QueryResponse response = null; try { response = server.query(params); return response.getResults().size(); } catch (SolrServerException e) { logger.error("", e); } catch (IOException e) { logger.error("", e); } return 0; } public List<T> query(String sort, Class<T> beanClass) { SolrQuery params = handleQuery(); QueryResponse response = null; List<T> list = null; try { logger.info("SolyQuery:" + params.toString()); response = server.query(params); list = (List<T>) response.getBeans(beanClass); } catch (SolrServerException e) { logger.error("SolrServerException", e); } catch (IOException e) { logger.error("IOException", e); } return list; } public Page<T> execQuery(Integer pageNo, Integer rows, String sort, Class<T> beanClass) { List<T> results = null; Page<T> page = null; SolrQuery params = handleQuery(); if (pageNo != null && rows != null && pageNo > 0 && rows > 0) { params.set("start", (pageNo - 1) * rows); params.set("rows", rows); } if (null != sort && !"".equals(sort)) { params.set("sort", sort); } QueryResponse response = null; try { logger.info("SolyQuery WithPage:" + params.toString()); response = server.query(params); results = (List<T>) response.getBeans(beanClass); page = new Page<T>(pageNo, rows, execQueryTotalCount()); page.addAll(results); } catch (SolrServerException e) { logger.error("SolrServerException", e); } catch (IOException e) { logger.error("IOException", e); } return page; } private SolrQuery handleQuery() { SolrQuery params = new SolrQuery(); String qryFinalStr = queryString.toString(); if (qryFinalStr.startsWith(" && ")) { qryFinalStr = qryFinalStr.replaceFirst(" && ", ""); } else if (qryFinalStr.startsWith(" || ")) { qryFinalStr = qryFinalStr.replaceFirst(" || ", ""); } // 子查詢開頭的關聯符號 if (-1 != qryFinalStr.indexOf("( && ")) { qryFinalStr = qryFinalStr.replaceAll("\\( \\&\\& ", "("); } if (-1 != qryFinalStr.indexOf("( || ")) { qryFinalStr = qryFinalStr.replaceAll("\\( \\|\\| ", "("); } if (StringUtils.isBlank(qryFinalStr)) { qryFinalStr = "*:*"; } params.set("q", qryFinalStr); return params; } public void execDelete(String keyName, String keyVal) { try { server.deleteByQuery(keyName + ":" + keyVal); server.commit(); } catch (SolrServerException | IOException e) { logger.error("", e); } } public void execUpdate(T model) { Field[] fields = model.getClass().getDeclaredFields(); SolrInputDocument solrDoc = new SolrInputDocument(); try { for (Field f : fields) { PropertyDescriptor pd; pd = new PropertyDescriptor(f.getName(), model.getClass()); // 屬性名 String fieldName = f.getName(); Method rM = pd.getReadMethod();// 獲得讀方法 solrDoc.addField(fieldName, rM.invoke(model)); } server.add(solrDoc); server.commit(); } catch (Exception e) { logger.error("", e); } } public void execUpdate(SolrInputDocument solrDoc) { try { server.add(solrDoc); server.commit(); } catch (SolrServerException e) { logger.error("", e); } catch (IOException e) { logger.error("", e); } } /** * 重建索引和增量索引的接口 * * @param delta */ public void buildIndex(boolean delta) { SolrQuery query = new SolrQuery(); // 指定RequestHandler,默認使用/select query.setRequestHandler("/dataimport"); String command = delta ? "delta-import" : "full-import"; String clean = delta ? "false" : "true"; String optimize = delta ? "false" : "true"; query.setParam("command", command).setParam("clean", clean) .setParam("commit", "true").setParam("optimize", optimize); try { server.query(query); } catch (SolrServerException e) { logger.error("建立索引時遇到錯誤,delta:" + delta, e); } catch (IOException e) { logger.error("建立索引時遇到錯誤,delta:" + delta, e); } }
}</pre>
代碼使用示例:
1.常見的分頁查詢,更新單條數據public static void main(String[] args) { SolrHelper<Project> sh = new SolrHelper<Project>( "http://host/solr/project"); sh.andEquals("id", "32404"); List<Project> page = sh.execQuery(1, 10, "id desc", Project.class); Project ps = page.get(0); ps.setTotal(3.1415); sh.execUpdate(ps); }
2.不修改,直接同步
public void synProject(long id) { ProjectSolrDto solrDto = projectMapper.selectSolrProjectSimple(id); SolrHelper<ProjectSolrDto> solrHelper = new SolrHelper<ProjectSolrDto>( solrProjectUrl); solrHelper.execUpdate(solrDto); }
3.同步某幾個字段
public void synIntention(Long id) { Intention intention = intentionMapper.selectByPrimaryKey(id); SolrHelper<Intention> solrHelper = new SolrHelper<Intention>( solrIntentionUrl); SolrInputDocument solrDoc = new SolrInputDocument(); solrDoc.addField("id", intention.getId()); solrDoc.addField("intro", intention.getIntro()); solrDoc.addField("industry", intention.getIndustry()); solrHelper.execUpdate(solrDoc); }
4.刪除
public void delFund(Long id) { SolrHelper<Intention> solrHelper = new SolrHelper<Intention>( solrFundUrl); solrHelper.execDelete("id", id.toString()); }
幾點補充
1.需要有“定時器”,定時“全量更新”和“重建索引”,防止數據不一致,或者查詢效率低。
2.SolrHelper中的代碼,或者說Solr的相關代碼,無非就是“增刪改查CRUD”,比較特殊的
“重建索引”和為了執行查詢,拼接查詢條件的“And,Or”等工具方法。
3.分頁有個實體類,用到了Github上的1個工具,個人覺得一般般,Page類的定義比較糟糕。
如有需要,自己引入,或者自行改造。
寫在最后
IT互聯網技術很多,更新很快,問題也很多,深入需要實踐,深入需要時間。
技術方面的博學和專注,自己去平衡吧~
技術和技術之外的平衡,自己看著辦哦~
更多資料
Solr 搭建搜索服務器
http://my.oschina.net/u/1757458/blog/389109?fromerr=HUEucn9b
Solr調研總結
http://www.cnblogs.com/guozk/p/3498831.html
Solr中國
http://www.solr.cc/blog/
solr對跨服務器表聯合查詢的配置
http://blog.csdn.net/awj3584/article/details/10326439