• Lucene第八篇:多目录多线程搜索 原创

  • user image
    • jack
    • 2018-04-15 23:11:28 +0800

        我们知道在多线程或多进程的环境中,对统一资源的访问需要特别小心,特别是在写资源时,如果不加锁将会导致很多严重的后果。这里说下Lucene索引文件锁机制,Lucene对同一个索引文件(索引目录)只能有一把锁,线程只能拿到锁才能创建IndexWriter对索引进行写操作。

        在实际项目中使用Lucene,你会发现把所有不同类型的数据放到同一个索引目录,这是无法忍受的也是不现实的。既然是这样,我们把索引放到不同的目录不就可以解决这个问题吗,也还可以利用多线程提高写操作的效率。而检索的时候,也可以用多线程并发来提高搜索速度。

        怎么对多索引文件(目录)进行搜素呢,Lucene提供了MultiReader来实现的多目录多线程的索引检索方案。实际上就是分别对多个索引文件进行搜索,再合并搜索结果,有点像数据库的union all。

        1)这里我修改了前面的Searcher.java类,增加了一个multiSearch()方法来演示多目录多线程的索引检索;

        2)附件lucene.rar为我创建的测试索引,index90~index945个文件中的索引是完全一样的,也就是说对于同样的关键字,5个目录搜索结果是相同的;

        3)Test.java为测试类。

 

Test.java

 

import java.util.List;

import java.util.Map;

import java.util.concurrent.ExecutorService;

public class Test {

    public static void main(String []args) {

        Searcher searchManager = Searcher.instance();

        int pageNum = 1;

        String indexDirecory[] = new String[] {"D:/lucence/blog/index90", "D:/lucence/blog/index91", 

                "D:/lucence/blog/index92", "D:/lucence/blog/index93", "D:/lucence/blog/index94"};

        String keyword = "lucene 第三篇";

        String multipleFieldsText = "co_title,co_content";

        String targetFieldsText = "ti_uuid,co_title";

        ExecutorService executor = null;

        Map<String, Object> searchData = searchManager.multiSearch(pageNum, indexDirecory, keyword, 

                multipleFieldsText, targetFieldsText, executor);

        List<Map<String, Object>> list = (List<Map<String, Object>>)searchData.get("search");

        

        for(Map<String, Object> blog : list) {

            System.out.println(blog.toString());

        }

        searchData.remove("search");

        System.out.println(searchData.toString());

        searchManager.shutdown();

    }

}


Searcher.java

 

import java.io.File;

import java.io.IOException;

import java.nio.file.Path;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexReader;

import org.apache.lucene.index.MultiReader;

import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.TopDocs;

import org.apache.lucene.store.FSDirectory;

public class Searcher {

    private static Searcher _searcher = null;

    /** index manager,对同一个索引目录共享indexReader,key为索引目录。 */

    public static Map<String, IndexReader> _indexReaderManager = new HashMap<String, IndexReader>();

    /** 线程池 */

    ExecutorService _executorService = Executors.newFixedThreadPool(10);

    /** 返回最高匹配搜索结果数目,根据自己需要调整 */

    public static int _TOP_N_HITS = 10000;

    /** 最每页返回的搜索结果数目,根据自己需要调整 */

    public static int _PAGE_SIZE = 20;

    /**

     * 私有化默认构造方法,使用单例模式。

     */

    private Searcher() {

    }

    public  static synchronized Searcher instance() {

        if (_searcher == null) {

            _searcher = new Searcher();

        }

        return _searcher;

    }

    

    /**

     * 

     * 按关键字词搜索,返回指定的field结果集,可同时对多目录进行搜索。

     * 

     * @param pageNum

     *            页号

     * @param indexDirecory[]

     *            索引文件目录,注意大小写。

     * @param keyword

     *            关键字词,多个关键词以空格分隔,例如:"Lucene 演示"。(不要引号)

     * @param multipleFieldsText

     *            搜索指定的field,以英文逗号分隔,例如:"title,content"。(不要引号)

     * @param targetFieldsText

     *            指定要返回的field,以英文逗号分隔,例如:"uuid,title"。(不要引号)

     * @param executor 线程池ExecutorService,如果为NULL,会自建一个线程池。线程池的数量和要搜索的目录数一样。

     * @return 返回配置搜索结果,格式:{"previous": 上一页页号, "current": 当前页号,"nextp":

     *         上一页页号,"last": 最后页页号,"search": [{"field1": value, "field2":

     *         value}]}。

     

     * @return

     */

    public Map<String, Object> multiSearch(int pageNum, String indexDirecory[], String keyword, String multipleFieldsText,

            String targetFieldsText, ExecutorService executorService) {

        

        List<IndexReader> direcories = new ArrayList<IndexReader>();

        IndexReader indexReader = null;

        for(String direcory : indexDirecory){

            

            if(_indexReaderManager.containsKey(direcory)){

                indexReader = _indexReaderManager.get(direcory);

            }else {

                try {

                    indexReader = getIndexReader(direcory);

                } catch (IOException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

            }

            

            if(indexReader != null) {

                direcories.add(indexReader);

            }

            

        }

        

        int size = direcories.size();

        IndexReader[] indexReaders = new IndexReader[size];

        

        MultiReader multiReader = null;

        try {

            multiReader = new MultiReader(direcories.toArray(indexReaders));

        } catch (IOException e) {

            e.printStackTrace();

        }

        

        ExecutorService executor = null;

        if(executorService == null) {

            executor = _executorService;

        }else {

            executor = executorService;

        }

        

        IndexSearcher indexSearcher = new IndexSearcher(multiReader, executor);

        

        Map<String, Object> searchData = search(pageNum, indexSearcher, keyword, multipleFieldsText, targetFieldsText);

        

        return searchData;

        

    }

    

    /**

     * 按关键字词搜索,返回指定的field结果集。

     * 

     * @param pageNum

     *            页号

     * @param indexDirecory

     *            索引文件目录,注意大小写。

     * @param keyword

     *            关键字词,多个关键词以空格分隔,例如:"Lucene 演示"。(不要引号)

     * @param multipleFieldsText

     *            搜索指定的field,以英文逗号分隔,例如:"title,content"。(不要引号)

     * @param targetFieldsText

     *            指定要返回的field,以英文逗号分隔,例如:"uuid,title"。(不要引号)

     * @return 返回配置搜索结果,格式:{"previous": 上一页页号, "current": 当前页号,"nextp":

     *         上一页页号,"last": 最后页页号,"search": [{"field1": value, "field2":

     *         value}]}。

     */

    public Map<String, Object> search(int pageNum, String indexDirecory, String keyword, String multipleFieldsText,

            String targetFieldsText) {

        IndexSearcher indexSearcher = null;

        try {

            indexSearcher = getIndexSearcher(indexDirecory);

        } catch (IOException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        

        return search(pageNum, indexSearcher, keyword, multipleFieldsText, targetFieldsText);

    }

    

    private Map<String, Object> search(int pageNum, IndexSearcher indexSearcher, String keyword, String multipleFieldsText,

            String targetFieldsText) {

        Map<String, Object> hitData = new HashMap<String, Object>();

        if (indexSearcher == null || isNULL(keyword) || isNULL(multipleFieldsText) || isNULL(targetFieldsText)) {

            hitData.put("previous", 0);

            hitData.put("current", 0);

            hitData.put("nextp", 0);

            hitData.put("last", 0);

            hitData.put("search", new ArrayList<Map<String, Object>>());

        } else {

            String parseText = parseKeyword(keyword);

            String[] multipleFields = multipleFieldsText.split(",");

            String[] targetFields = targetFieldsText.split(",");

            hitData = searchByPaging(pageNum, indexSearcher, parseText, multipleFields, targetFields);

        }

        return hitData;

    }

    

    private Map<String, Object> searchByPaging(int pageNum, IndexSearcher indexSearcher, String parseText,

            String[] multipleFields, String[] targetFields) {

        Map<String, Object> hitData = new HashMap<String, Object>();

        try {

            ScoreDoc[] scoreDoc = searchByMultipleFields(parseText, multipleFields, indexSearcher);

            if (scoreDoc != null && scoreDoc.length > 0) {

                paging(pageNum, hitData, scoreDoc, indexSearcher, targetFields);

            } else {

                hitData.put("previous", 0);

                hitData.put("current", 0);

                hitData.put("nextp", 0);

                hitData.put("last", 0);

                hitData.put("search", new ArrayList<Map<String, Object>>());

            }

        } catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

            hitData.put("previous", 0);

            hitData.put("current", 0);

            hitData.put("nextp", 0);

            hitData.put("last", 0);

            hitData.put("search", new ArrayList<Map<String, Object>>());

        }

        return hitData;

    }

    

    private ScoreDoc[] searchByMultipleFields(String parseText, String[] multipleFields,

            IndexSearcher indexSearcher) throws IOException, ParseException {

        Analyzer analyzer = new StandardAnalyzer();

        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(multipleFields, analyzer);

        Query query = queryParser.parse(parseText);

        TopDocs topDocs = indexSearcher.search(query, _TOP_N_HITS);

        return topDocs.scoreDocs;

    }

    /**

     * 移除indexReader

     * 

     * @param indexDirecory

     * @return

     */

    public IndexReader removeIndexReader(String indexDirecory) {

        IndexReader indexReader = _indexReaderManager.remove(indexDirecory);

        return indexReader;

    }

    

    private String parseKeyword(String keyword) {

        keyword = keyword.trim();

        String[] keywords = keyword.split(" ");

        StringBuilder parseText = new StringBuilder();

        for (String text : keywords) {

            parseText.append("\"").append(text).append("\" ");

        }

        keyword = parseText.toString().trim();

        return keyword;

    }

    /**

     * 近时搜索

     * @param indexDirecory

     * @return

     * @throws IOException

     */

    private IndexSearcher getIndexSearcher(String indexDirecory) throws IOException {

        IndexReader indexReader = getIndexReader(indexDirecory);

        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        return indexSearcher;

    }

    

    private IndexReader getIndexReader(String indexDirecory) throws IOException {

        IndexReader indexReader = null;

        if (_indexReaderManager.containsKey(indexDirecory)) {

            indexReader = _indexReaderManager.get(indexDirecory);

            // openIfChanged这个方法类似3.x中的reader.reopen方法,这样就实现了近实时搜索,避免多次新建IndexReader耗费系统资源

            IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader);

            if (newReader != null) {

                // 要记得将原来的 IndexReader 对象关掉

                indexReader.close();

                indexReader = newReader;

            }

        } else {

            File ifDir = new File(indexDirecory);

            Path path = ifDir.toPath();

            FSDirectory fSDirectory = FSDirectory.open(path);

            indexReader = DirectoryReader.open(fSDirectory);

        }

        

        _indexReaderManager.put(indexDirecory, indexReader);

        

        return indexReader;

    }

    /**

     * 计算分页信息及返回指定页结果集

     * @param pageNum

     * @param hitData

     * @param scoreDoc

     * @param indexSearcher

     * @param targetFields

     * @throws IOException

     */

    private void paging(int pageNum, Map<String, Object> hitData, ScoreDoc[] scoreDoc, IndexSearcher indexSearcher,

            String[] targetFields) throws IOException {

        int maxHitNum = scoreDoc.length;

        Double maxNum = Double.parseDouble(Integer.toString(maxHitNum));

        int last = (int) Math.ceil(maxNum / _PAGE_SIZE);

        pageNum = (pageNum > 0 && pageNum <= last) ? pageNum : 1;

        int previous = pageNum - 1;

        previous = previous >= 1 ? previous : 1;

        int nextp = pageNum + 1;

        nextp = nextp <= last ? nextp : 1;

        hitData.put("previous", previous);

        hitData.put("current", pageNum);

        hitData.put("nextp", nextp);

        hitData.put("last", last);

        int maxIndex = pageNum * _PAGE_SIZE;

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        for (int i = _PAGE_SIZE; i > 0; i--) {

            int index = maxIndex - i;

            if (index < maxHitNum) {

                ScoreDoc scoreDocData = scoreDoc[maxIndex - i];

                Document document = indexSearcher.doc(scoreDocData.doc);

                Map<String, Object> item = new HashMap<String, Object>();

                for (String targetField : targetFields) {

                    item.put(targetField, document.get(targetField));

                }

                list.add(item);

            } else {

                break;

            }

        }

        hitData.put("search", list);

    }

   public void shutdown() {
        if(!_executorService.isShutdown()) {
            _executorService.shutdown();
        }
    }

    private boolean isNULL(String text) {

        return text != null && text.trim().length() > 0 ? false : true;

    }

}

  • 查看 0 条评论
登 录 | 注册账号,开始评论。