szhswl
          宋針還的個人空間
          1, 有時對于一個Document來說,有一些Field會被頻繁地操作,而另一些Field則不會。這時可以將頻繁操作的Field和其他Field分開存放,而在搜索時同時檢索這兩部分Field而提取出一個完整的Document。   這要求兩個索引包含的Document的數(shù)量必須相同。
          在創(chuàng)建索引的時候,可以同時創(chuàng)建多個IndexWriter,將一個Document根據(jù)需要拆分成多個包含部分Field的Document,并將這些Document分別添加到不同的索引。
          而在搜索時,則必須借助ParallelReader類來整合。
          Directory dir1=FSDirectory.getDirectory(new File(INDEX_DIR1),false);
          Directory dir2=FSDirectory.getDirectory(new File(INDEX_DIR2),false);
          ParallelReader preader=new ParallelReader();
          preader.add(IndexReader.open(dir1));
          preader.add(IndexReader.open(dir2));

          IndexSearcher searcher=new IndexSearcher(preader);
          之后的操作和一般的搜索相同。

          2, Query的子類. 下面的幾個搜索在各種不同要求的場合,都會用到. 需要大家仔細(xì)研讀!

          Query query1 = new TermQuery(new Term(FieldValue, "name1")); // 詞語搜索
          Query query2 = new WildcardQuery(new Term(FieldName, "name*")); // 通配符
          Query query3 = new PrefixQuery(new Term(FieldName, "name1")); // 字段搜索 Field:Keyword,自動在結(jié)尾添加 *
          Query query4 = new RangeQuery(new Term(FieldNumber, NumberTools.LongToString(11L)), new Term(FieldNumber, NumberTools.LongToString(13L)), true); // 范圍搜索
          Query query5 = new FilteredQuery(query, filter); // 帶過濾條件的搜索
          Query query6 =new MatchAllDocsQuery(... // 用來匹配所有文檔
          Query query7 = new FuzzyQuery (...模糊搜索
          Query query8 = new RegexQuery (..   正則搜索
          Query query9 = new SpanRegexQuery(...)。 同上, 正則表達(dá)式的查詢:
          Query query9 = new SpanQuery 的子類嵌套其他SpanQuery 增加了 rewrite方法
          Query query10 =new DisjunctionMaxQuery () ..類,提供了針對某個短語的最大score。這一點(diǎn)對多字段的搜索非常有用
          Query query11 = new ConstantScoreQuery 類它包裝了一個 filter produces a score
          equal to the query boost for every matching document.

          BooleanQuery query12= new BooleanQuery();
          booleanQuery.add(termQuery 1, BooleanClause.Occur.SHOULD);
          booleanQuery.add(termQuery 2, BooleanClause.Occur.SHOULD);
            //這個是為了聯(lián)合多個查詢而做的Query類. BooleanQuery增加了最小的匹配短語。見:BooleanQuery.setMinimumNumberShouldMatch().


          PhraseQuery
          你可能對中日關(guān)系比較感興趣,想查找‘中’和‘日’挨得比較近(5個字的距離內(nèi))的文章,超過這個距離的不予考慮,你可以:

          PhraseQuery query 13= new PhraseQuery();
          query.setSlop(5);
          query.add(new Term("content ", “中”));
          query.add(new Term(“content”, “日”));

          PhraseQuery對于短語的順序是不管的,這點(diǎn)在查詢時除了提高命中率外,也會對性能產(chǎn)生很大的影響, 利用SpanNearQuery可以對短語的順序進(jìn)行控制,提高性能

          BooleanQuery query12=   new SpanNearQuery 可以對短語的順序進(jìn)行控制,提高性能

          3, 索引文本文件
          如果你想把純文本文件索引起來,而不想自己將它們讀入字符串創(chuàng)建field,你可以用下面的代碼創(chuàng)建field:

          Field field = new Field("content", new FileReader(file));

          這里的file就是該文本文件。該構(gòu)造函數(shù)實際上是讀去文件內(nèi)容,并對其進(jìn)行索引,但不存儲


          4, 如何刪除索引
          lucene提供了兩種從索引中刪除document的方法,一種是

          void deleteDocument(int docNum)

          這種方法是根據(jù)document在索引中的編號來刪除,每個document加進(jìn)索引后都會有個唯一編號,所以根據(jù)編號刪除是一種精確刪除,但是這個編號是索引的內(nèi)部結(jié)構(gòu),一般我們不會知道某個文件的編號到底是幾,所以用處不大。另一種是

          void deleteDocuments(Term term)

          這種方法實際上是首先根據(jù)參數(shù)term執(zhí)行一個搜索操作,然后把搜索到的結(jié)果批量刪除了。我們可以通過這個方法提供一個嚴(yán)格的查詢條件,達(dá)到刪除指定document的目的。
          下面給出一個例子:

          Directory dir = FSDirectory.getDirectory(PATH, false);
          IndexReader reader = IndexReader.open(dir);
          Term term = new Term(field, key);
          reader.deleteDocuments(term);
          reader.close();



          5, 如何更新索引
          lucene并沒有提供專門的索引更新方法,我們需要先將相應(yīng)的document刪除,然后再將新的document加入索引。例如:

          Directory dir = FSDirectory.getDirectory(PATH, false);
          IndexReader reader = IndexReader.open(dir);
          Term term = new Term(“title”, “lucene introduction”);
          reader.deleteDocuments(term);
          reader.close();

          IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);
          Document doc = new Document();
          doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
          doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));
          writer.addDocument(doc);
          writer.optimize();
          writer.close();


          但是在1.9RC1中說明:
          新增類: org.apache.lucene.index.IndexModifier ,它合并了   IndexWriter 和 IndexReader,好處是我們可以增加和刪除文檔的時候不同擔(dān)心 synchronisation/locking 的問題了。  

          6, filer類.使用 Filter 對搜索結(jié)果進(jìn)行過濾,可以獲得更小范圍內(nèi)更精確的結(jié)果。 有人說: 注意它執(zhí)行的是預(yù)處理,而不是對查詢結(jié)果進(jìn)行過濾,所以使用filter的代價是很大的,它可能會使一次查詢耗時提高一百倍


          ISOLatin1AccentFilter ,用 ISO Latin 1 字符集中的unaccented類字符替代 accented 類字符
          DateFilter   日期過濾器
          RangeFileter ,比 DateFilter 更加通用,實用
          LengthFilter 類, 已經(jīng)從 contrib 放到了 core 代碼里。從 stream 中去掉太長和太短的單詞   StopFilter 類, 增加了對處理stop words 的忽略大小寫處理


          7,本條是一個使用過濾的說明:

          過濾

          使用 Filter 對搜索結(jié)果進(jìn)行過濾,可以獲得更小范圍內(nèi)更精確的結(jié)果。

          舉個例子,我們搜索上架時間在 2005-10-1 到 2005-10-30 之間的商品。
          對于日期時間,我們需要轉(zhuǎn)換一下才能添加到索引庫,同時還必須是索引字段。
          // index
          document.Add(FieldDate, DateField.DateToString(date), Field.Store.YES, Field.Index.UN_TOKENIZED);

          //...

          // search
          Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-1"), DateTime.Parse("2005-10-30"));
          Hits hits = searcher.Search(query, filter);

          除了日期時間,還可以使用整數(shù)。比如搜索價格在 100 ~ 200 之間的商品。
          Lucene.Net NumberTools 對于數(shù)字進(jìn)行了補(bǔ)位處理,如果需要使用浮點(diǎn)數(shù)可以自己參考源碼進(jìn)行。
          // index
          document.Add(new Field(FieldNumber, NumberTools.LongToString((long)price), Field.Store.YES, Field.Index.UN_TOKENIZED));

          //...

          // search
          Filter filter = new RangeFilter(FieldNumber, NumberTools.LongToString(100L), NumberTools.LongToString(200L), true, true);
          Hits hits = searcher.Search(query, filter);

          使用 Query 作為過濾條件。
          QueryFilter filter = new QueryFilter(QueryParser.Parse("name2", FieldValue, analyzer));

          我們還可以使用 FilteredQuery 進(jìn)行多條件過濾。

          Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-10"), DateTime.Parse("2005-10-15"));
          Filter filter2 = new RangeFilter(FieldNumber, NumberTools.LongToString(11L), NumberTools.LongToString(13L), true, true);

          Query query = QueryParser.Parse("name*", FieldName, analyzer);
          query = new FilteredQuery(query, filter);
          query = new FilteredQuery(query, filter2);

          IndexSearcher searcher = new IndexSearcher(reader);
          Hits hits = searcher.Search(query);



          8, Sort
          有時你想要一個排好序的結(jié)果集,就像SQL語句的“order by”,lucene能做到:通過Sort。
          Sort sort = new Sort(“time”); //相當(dāng)于SQL的“order by time”
          Sort sort = new Sort(“time”, true); // 相當(dāng)于SQL的“order by time desc”
          下面是一個完整的例子:

          Directory dir = FSDirectory.getDirectory(PATH, false);
          IndexSearcher is = new IndexSearcher(dir);
          QueryParser parser = new QueryParser("content", new StandardAnalyzer());
          Query query = parser.parse("title:lucene content:lucene";
          RangeFilter filter = new RangeFilter("time", "20060101", "20060230", true, true);
          Sort sort = new Sort(“time”);
          Hits hits = is.search(query, filter, sort);
          for (int i = 0; i < hits.length(); i++)
          {
          Document doc = hits.doc(i);
          System.out.println(doc.get("title");
          }
          is.close();

          9,   性能優(yōu)化
          一直到這里,我們還是在討論怎么樣使lucene跑起來,完成指定任務(wù)。利用前面說的也確實能完成大部分功能。但是測試表明lucene的性能并不是很好,在大數(shù)據(jù)量大并發(fā)的條件下甚至?xí)邪敕昼姺祷氐那闆r。另外大數(shù)據(jù)量的數(shù)據(jù)初始化建立索引也是一個十分耗時的過程。那么如何提高lucene的性能呢?下面從優(yōu)化創(chuàng)建索引性能和優(yōu)化搜索性能兩方面介紹。

          9.1 優(yōu)化創(chuàng)建索引性能
          這方面的優(yōu)化途徑比較有限,IndexWriter提供了一些接口可以控制建立索引的操作,另外我們可以先將索引寫入RAMDirectory,再批量寫入FSDirectory,不管怎樣,目的都是盡量少的文件IO,因為創(chuàng)建索引的最大瓶頸在于磁盤IO。另外選擇一個較好的分析器也能提高一些性能。

          9.1.1 通過設(shè)置IndexWriter的參數(shù)優(yōu)化索引建立
          setMaxBufferedDocs(int maxBufferedDocs)
          控制寫入一個新的segment前內(nèi)存中保存的document的數(shù)目,設(shè)置較大的數(shù)目可以加快建索引速度,默認(rèn)為10。
          setMaxMergeDocs(int maxMergeDocs)
          控制一個segment中可以保存的最大document數(shù)目,值較小有利于追加索引的速度,默認(rèn)Integer.MAX_VALUE,無需修改。
          setMergeFactor(int mergeFactor)
          控制多個segment合并的頻率,值較大時建立索引速度較快,默認(rèn)是10,可以在建立索引時設(shè)置為100。

          9.1.2 通過RAMDirectory緩寫提高性能
          我們可以先把索引寫入RAMDirectory,達(dá)到一定數(shù)量時再批量寫進(jìn)FSDirectory,減少磁盤IO次數(shù)。

          FSDirectory fsDir = FSDirectory.getDirectory("/data/index", true);
          RAMDirectory ramDir = new RAMDirectory();
          IndexWriter fsWriter = new IndexWriter(fsDir, new StandardAnalyzer(), true);
          IndexWriter ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
          while (there are documents to index)
          {
          ... create Document ...
          ramWriter.addDocument(doc);
          if (condition for flushing memory to disk has been met)
          {
          fsWriter.addIndexes(new Directory[] { ramDir });
          ramWriter.close();
          ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
          }
          }

          9.1.3 選擇較好的分析器
          這個優(yōu)化主要是對磁盤空間的優(yōu)化,可以將索引文件減小將近一半,相同測試數(shù)據(jù)下由600M減少到380M。但是對時間并沒有什么幫助,甚至?xí)枰L時間,因為較好的分析器需要匹配詞庫,會消耗更多cpu,測試數(shù)據(jù)用StandardAnalyzer耗時133分鐘;用MMAnalyzer耗時150分鐘。

          9.2 優(yōu)化搜索性能
          雖然建立索引的操作非常耗時,但是那畢竟只在最初創(chuàng)建時才需要,平時只是少量的維護(hù)操作,更何況這些可以放到一個后臺進(jìn)程處理,并不影響用戶搜索。我們創(chuàng)建索引的目的就是給用戶搜索,所以搜索的性能才是我們最關(guān)心的。下面就來探討一下如何提高搜索性能。

          9.2.1 將索引放入內(nèi)存
          這是一個最直觀的想法,因為內(nèi)存比磁盤快很多。Lucene提供了RAMDirectory可以在內(nèi)存中容納索引:

          Directory fsDir = FSDirectory.getDirectory(“/data/index/”, false);
          Directory ramDir = new RAMDirectory(fsDir);
          Searcher searcher = new IndexSearcher(ramDir);

          但是實踐證明RAMDirectory和FSDirectory速度差不多,當(dāng)數(shù)據(jù)量很小時兩者都非常快,當(dāng)數(shù)據(jù)量較大時(索引文件400M)RAMDirectory甚至比FSDirectory還要慢一點(diǎn),這確實讓人出乎意料。
          而且lucene的搜索非常耗內(nèi)存,即使將400M的索引文件載入內(nèi)存,在運(yùn)行一段時間后都會out of memory,所以個人認(rèn)為載入內(nèi)存的作用并不大。

          9.2.2 優(yōu)化時間范圍限制
          既然載入內(nèi)存并不能提高效率,一定有其它瓶頸,經(jīng)過測試發(fā)現(xiàn)最大的瓶頸居然是時間范圍限制,那么我們可以怎樣使時間范圍限制的代價最小呢?
          當(dāng)需要搜索指定時間范圍內(nèi)的結(jié)果時,可以:
          1、用RangeQuery,設(shè)置范圍,但是RangeQuery的實現(xiàn)實際上是將時間范圍內(nèi)的時間點(diǎn)展開,組成一個個BooleanClause加入到BooleanQuery中查詢, 因此時間范圍不可能設(shè)置太大,經(jīng)測試,范圍超過一個月就會拋BooleanQuery.TooManyClauses,可以通過設(shè)置BooleanQuery.setMaxClauseCount(int maxClauseCount)擴(kuò)大,但是擴(kuò)大也是有限的,并且隨著maxClauseCount擴(kuò)大,占用內(nèi)存也擴(kuò)大
          2、用RangeFilter代替RangeQuery,經(jīng)測試速度不會比RangeQuery慢,但是仍然有性能瓶頸,查詢的90%以上時間耗費(fèi)在RangeFilter,研究其源碼發(fā)現(xiàn)RangeFilter實際上是首先遍歷所有索引,生成一個BitSet,標(biāo)記每個document,在時間范圍內(nèi)的標(biāo)記為true,不在的標(biāo)記為false,然后將結(jié)果傳遞給Searcher查找,這是十分耗時的。
          3、進(jìn)一步提高性能,這個又有兩個思路:
          a、緩存Filter結(jié)果。既然RangeFilter的執(zhí)行是在搜索之前,那么它的輸入都是一定的,就是IndexReader,而IndexReader是由Directory決定的,所以可以認(rèn)為RangeFilter的結(jié)果是由范圍的上下限決定的,也就是由具體的RangeFilter對象決定,所以我們只要以RangeFilter對象為鍵,將filter結(jié)果BitSet緩存起來即可。lucene API已經(jīng)提供了一個CachingWrapperFilter類封裝了Filter及其結(jié)果,所以具體實施起來我們可以cache CachingWrapperFilter對象,需要注意的是,不要被CachingWrapperFilter的名字及其說明誤導(dǎo),CachingWrapperFilter看起來是有緩存功能,但的緩存是針對同一個filter的,也就是在你用同一個filter過濾不同IndexReader時,它可以幫你緩存不同IndexReader的結(jié)果,而我們的需求恰恰相反,我們是用不同filter過濾同一個IndexReader,所以只能把它作為一個封裝類。
          b、降低時間精度。研究Filter的工作原理可以看出,它每次工作都是遍歷整個索引的,所以時間粒度越大,對比越快,搜索時間越短,在不影響功能的情況下,時間精度越低越好,有時甚至犧牲一點(diǎn)精度也值得,當(dāng)然最好的情況是根本不作時間限制。
          下面針對上面的兩個思路演示一下優(yōu)化結(jié)果(都采用800線程隨機(jī)關(guān)鍵詞隨即時間范圍):
          第一組,時間精度為秒:
          方式 直接用RangeFilter 使用cache 不用filter
          平均每個線程耗時 10s 1s 300ms

          第二組,時間精度為天
          方式 直接用RangeFilter 使用cache 不用filter
          平均每個線程耗時 900ms 360ms 300ms

          由以上數(shù)據(jù)可以得出結(jié)論:
          1、 盡量降低時間精度,將精度由秒換成天帶來的性能提高甚至比使用cache還好,最好不使用filter。
          2、 在不能降低時間精度的情況下,使用cache能帶了10倍左右的性能提高。

          9.2.3 使用更好的分析器
          這個跟創(chuàng)建索引優(yōu)化道理差不多,索引文件小了搜索自然會加快。當(dāng)然這個提高也是有限的。較好的分析器相對于最差的分析器對性能的提升在20%以下。

          10 一些經(jīng)驗

          10.1關(guān)鍵詞區(qū)分大小寫
          or AND TO等關(guān)鍵詞是區(qū)分大小寫的,lucene只認(rèn)大寫的,小寫的當(dāng)做普通單詞。

          10.2 讀寫互斥性
          同一時刻只能有一個對索引的寫操作,在寫的同時可以進(jìn)行搜索

          10.3 文件鎖
          在寫索引的過程中強(qiáng)行退出將在tmp目錄留下一個lock文件,使以后的寫操作無法進(jìn)行,可以將其手工刪除

          10.4 時間格式
          lucene只支持一種時間格式y(tǒng)yMMddHHmmss,所以你傳一個yy-MM-dd HH:mm:ss的時間給lucene它是不會當(dāng)作時間來處理的

          10.5 設(shè)置boost
          有些時候在搜索時某個字段的權(quán)重需要大一些,例如你可能認(rèn)為標(biāo)題中出現(xiàn)關(guān)鍵詞的文章比正文中出現(xiàn)關(guān)鍵詞的文章更有價值,你可以把標(biāo)題的boost設(shè)置的更大,那么搜索結(jié)果會優(yōu)先顯示標(biāo)題中出現(xiàn)關(guān)鍵詞的文章(沒有使用排序的前題下)。使用方法:
          Field. setBoost(float boost);默認(rèn)值是1.0,也就是說要增加權(quán)重的需要設(shè)置得比1大。

          上面這篇關(guān)于性能的講解是很深刻. 請學(xué)習(xí).

          本文轉(zhuǎn)自:http://zhangxinzhou.blog.ccidnet.com/blog-htm-do-showone-uid-36421-type-blog-itemid-213713.html


          ---------------------------------------------------------------------------------------------------------------------------------
          說人之短,乃護(hù)己之短。夸己之長,乃忌人之長。皆由存心不厚,識量太狹耳。能去此弊,可以進(jìn)德,可以遠(yuǎn)怨。
          http://www.aygfsteel.com/szhswl
          ------------------------------------------------------------------------------------------------------ ----------------- ---------
          posted on 2007-12-17 19:36 宋針還 閱讀(413) 評論(0)  編輯  收藏 所屬分類: 搜索引擎
          主站蜘蛛池模板: 巴林右旗| 高台县| 宁陵县| 石屏县| 织金县| 三门县| 娄底市| 元谋县| 波密县| 都安| 满城县| 塘沽区| 乌鲁木齐县| 蓝山县| 来宾市| 西畴县| 九寨沟县| 扎囊县| 芦溪县| 阿拉善盟| 库车县| 怀集县| 闻喜县| 正安县| 江源县| 子长县| 康平县| 沙坪坝区| 抚顺县| 扶沟县| 宁强县| 饶阳县| 富锦市| 梁河县| 东光县| 永清县| 鸡西市| 山东省| 蓝山县| 雅江县| 深圳市|