Solr入門和實踐以及我對Solr的8點理解

小雷FansUnion發表於2015-11-24


友情提示
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>  


  定義了資料匯入、增量更新的查詢語句。
  
 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>


  
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);


		}
	}
}




程式碼使用示例:
    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

相關文章