`
xmgestapo
  • 浏览: 5917 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

lucene3.3实战

阅读更多
最近做的一项目,需要用到lucene,项目开始的时候lucene3.4还没有发布,选择了最新的3.3版本

先说一下业务背景:

需要搜索的文件是TXT文件,每天会增量增加,而且文件一直保留,文件存放的结构化数据,具体结构如下:  Id|name|address|date

需要根据name address date进行搜索  date需要考虑跨时间段

由于业务每天会生成一个文件,当天的文件日期肯定是一致的,这个由业务方保证,每天的文件最大会有200M左右


考虑到文件是增量增加,然后需要按时间跨度搜索,时间跨度最大可以是三个月之久,响应时间必须在2S内

如果放在一个文件里面,索引的建立会随着文件不断增长而变得无比庞大,同时对于索引的搜索和优化也很麻烦

根据实际情况,考虑了如下个方案:

1、根据文件日期做索引分类,这需要数据提供者配合给每天生成的文件名中必须要包含日期

2、然后按照日期格式生成诸如2011/10/2011-10-11这样的目录结构,对应每天的索引就存放在对应的日期文件夹内

这样的好处有如下几点:

1) 索引源文件里面不需要再处理日期,可以直接把日期字段删除,减少索引文件大小

2)搜索时不需要进行区间搜索,如搜索2011-10-01至2011-10-31号的数据,可以生成31个文件目录,如2011/10/2011-10-01,2011/10/2011-10-02等,

    直接去指定日期目录下通过多目录索引搜索文件

3)对于生成的文件可以随时重建索引,因为是分目录索引,所以重建效率非常高,不需要进行专门的索引优化

/**
     *根据不同的域使用不同的分词器
     *
     * @return
     */
    public PerFieldAnalyzerWrapper kmsAnalyzer() {
        Analyzer standardAnalyzer = new StandardAnalyzer(LUCENE_VERSION);

        Analyzer kwAnalyzer = new KeywordAnalyzer();
        PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(kwAnalyzer);
        analyzer.addAnalyzer("content", standardAnalyzer);
        return analyzer;
    }


//构造索引生成对象

private IndexWriter indexWriter33(String indexPath) throws CorruptIndexException,
            LockObtainFailedException, IOException {

        File file = new File(indexPath);
        LogMergePolicy policy = new LogDocMergePolicy();

        // SetUseCompoundFile这个方法可以使Lucene在创建索引库时,会合并多个 Segments 文件到一个 .cfs中 
        // 此方式有助于减少索引文件数量,对于将来搜索的效率有较大影响。 
        // 压缩存储(True则为复合索引格式)
        policy.setUseCompoundFile(true);

        //合并因子,当硬盘上的索引块达到设置的数量时,会合并成一个一个较大的索引块
        policy.setMergeFactor(5000);
        IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, this.kmsAnalyzer());
        config.setOpenMode(OpenMode.CREATE);
        config.setMergePolicy(policy);

        //最大缓存文档数 根据内存大小进行设置,设置较大的数目可以加快建索引速度
        config.setMaxBufferedDocs(200000);

        //构造索引存放目录
        FSDirectory directory = FSDirectory.open(file);

        //写索引对象
        IndexWriter indexWriter = new IndexWriter(directory, config);

        return indexWriter;
    }


/**
     * 构造document
     *
     * @param lineRecord 针对每一行记录构造document
     * @return
     */
    private Document buildDocument(String lineRecord, boolean fileStatus) {
        Document doc = new Document();
        String[] columns = lineRecord.split(String.valueOf((char) 5));
        //长度为4时,说明是站内词
        if (columns.length == 3) {
            Field cateId = new Field("cateId", columns[2], Store.NO, Index.ANALYZED);
            doc.add(cateId);
        }
        //长度为6时,表示是站外词
        else if (columns.length == 5) {
            Field sessionId = new Field("sessionId", columns[2], Store.NO, Index.ANALYZED);
            Field countryId = new Field("countryId", columns[3], Store.NO, Index.ANALYZED);
            Field urlGourpId = new Field("urlGourpId", columns[4], Store.NO, Index.ANALYZED);
            doc.add(sessionId);
            doc.add(countryId);
            doc.add(urlGourpId);
        } else {
            logger.error("The file content [" + lineRecord + "] error.");
            fileStatus = false;
            return new Document();
        }
        Field id = new Field("id", columns[0], Store.YES, Index.ANALYZED);
        Field keyword = new Field("keyword", columns[1], Store.NO, Index.ANALYZED);
        //需要进行分词处理
        Field content = new Field("content", columns[1], Store.NO, Index.ANALYZED);
        //Field date = new Field("date", columns[2], Store.YES, Index.ANALYZED);

        doc.add(id);
        doc.add(keyword);
        doc.add(content);
        //doc.add(date);
        return doc;

    }


public void createIndex(String srcPath, String desPath) {
        //文件内容正常标识
        boolean fileStatus = true;
        String path = null;
        //获取所有*.dat文件
        List<File> fileList = SEFileUtil.getSrcFiles(SEFileUtil.pathToFile(srcPath),
                FILE_SUFFIX_DAT);

        // 获取所有以*.lock的文件
        List<File> lockFileList = SEFileUtil.getSrcFiles(SEFileUtil.pathToFile(srcPath),
                FILE_SUFFIX_LOCK);

        //建立索引
        label0: for (File file : fileList) {
            IndexWriter writer = null;
            BufferedReader br = null;
            //构造写索引对象
            try {
                String prxFileName = file.getName().substring(0, file.getName().indexOf("_"));

                //需要索引的文件正在生成时不处理
                if (lockFileList != null && !lockFileList.isEmpty()) {
                    for (File lockFile : lockFileList) {
                        String preLockFileName = lockFile.getName().substring(0,
                                file.getName().indexOf("_"));
                        if (preLockFileName.equalsIgnoreCase(prxFileName)) {
                            lockFileList.remove(lockFile);
                            continue label0;
                        }
                    }
                }
                //生成索引文件存储路径
                path = SEFileUtil.buildFilePath(desPath, prxFileName, "yyyyMMdd");
                if (logger.isDebugEnabled()) {
                    logger.debug("The index file path: " + path);
                }
                writer = this.indexWriter33(SEFileUtil.createDirectory(path));
                br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
                String record = null;

                //生成索引文件
                while (StringUtils.isNotBlank(record = br.readLine())) {
                    writer.addDocument(this.buildDocument(record, fileStatus));
                }
                writer.optimize();
                writer.commit();
            } catch (Exception e) {
                e.printStackTrace();
                return;
            } finally {
                this.close(writer, br);
            }
            //文件解析异常时不删除原文件
            if (fileStatus) {
                if (StringUtils.isNotBlank(this.getIndexCopyToIP())) {
                    String[] ipArray = this.getIndexCopyToIP().split("\\|");
                    for (String ip : ipArray) {
                        int exitValue = this.copyIndex(ip.trim(), path);
                        if (0 != exitValue) {
                            logger.error("^_^ Copy index directory [" + path + "] to [" + ip
                                    + "] failed.");
                        }
                    }
                }
                //删除文件
                boolean flag = SEFileUtil.deleteFile(file);
                if (!flag) {
                    logger.error("Delete file failed: " + file.getPath());
                }
            }

        }

    }


下面是搜索,搜索由于我的业务相对简单,所以搜索也比较简单,主要有一点就是我需要搜索的返回值只要取其中的ID值就可以了,

开始不太了解lucene,取值时把整个document都装载进去了,所以取文件时很慢,后来通过MapFieldSelector使速度提高了5部以下,具体用法如下

下面是多目录搜索

/**

下面是查询主方法,包括构造搜索条件和多目录索引

*/

public List<Long> searchIndex(Map<String, String> paramMap) {
        Long startTime = null;
        if (logger.isDebugEnabled()) {
            startTime = System.currentTimeMillis();
            logger.debug("^_^ start search: " + paramMap);
        }
        List<Long> ids = null;
        //打开索引文件存放目录
        String keyword = paramMap.get("keyword");//关键字       
        String cateId = paramMap.get("cateId");//类目标识
        String matchFlag = paramMap.get("matchFlag"); //匹配标识 0:精确,1:模糊
        String cateType = paramMap.get("cateType");//02:发布类目 03:展示类目
        String siteWord = paramMap.get("siteWord");//0:=站内词,1:=站外词
        String sessionId = paramMap.get("sessionId");//来源
        String countryId = paramMap.get("countryId");//国家
        String urlGourpId = paramMap.get("urlGourpId");//url 组
        String fromDate = paramMap.get("startDate");//起始时间
        String toDate = paramMap.get("endDate");//结束时间

        //获取搜索目录
        String searchPath = this.getSearchPath(siteWord, cateType);

        //计算时间段内所有日期
        List<String> dateStringList = SEDateUtil.getDateRange(fromDate, toDate);
        //        IndexReader[] subReaders = new IndexReader[dateStringList.size()];
        List<IndexReader> subReadersList = new ArrayList<IndexReader>();
        boolean flag = true;
        try {
            //构造索引搜索对象
            for (int i = 0; i < dateStringList.size(); i++) {
                //获取所有搜索路径文件
                String fullPath = SEFileUtil.buildFilePath(searchPath, dateStringList.get(i),
                        "yyyy-MM-dd");
                File file = SEFileUtil.pathToFile(fullPath);
                if (!file.isDirectory()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("The directory is not exist: " + fullPath);
                    }
                    continue;
                }
                FSDirectory directory = FSDirectory.open(new File(fullPath));
                IndexReader subReader = IndexReader.open(directory);
                flag = false;
                subReadersList.add(subReader);
            }
            if (flag) {
                return null;
            }
            IndexReader[] subReaders = subReadersList
                    .toArray(new IndexReader[subReadersList.size()]);
            if (logger.isDebugEnabled()) {

                logger.debug("Build search directory consume time: "
                        + (System.currentTimeMillis() - startTime));
                startTime = System.currentTimeMillis();
            }
            //获取搜索结果
            ids = this.getSearchResult(subReaders, matchFlag, keyword, cateId, sessionId,
                    countryId, urlGourpId);
        } catch (CorruptIndexException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (null != subReadersList) {
                subReadersList = null;
            }
        }
        if (logger.isDebugEnabled()) {
            Long endTime = (System.currentTimeMillis() - startTime);
            logger.debug("search end. Consume Time(s): " + endTime);
        }
        if (null != ids && !ids.isEmpty()) {
            //按ID升级排序
            Collections.sort(ids, new IndicatorComparator());
        }
        return ids;
    }

/**
     *
     */
    private List<Long> getSearchResult(IndexReader[] subReaders, String matchFlag, String keyword,
                                       String cateId, String sessionId, String countryId,
                                       String urlGourpId) throws ParseException,
            CorruptIndexException, Exception {
        List<Long> result = null;
        PerFieldAnalyzerWrapper analyzer = buildIndexJob.kmsAnalyzer();
        IndexReader multiReader = new MultiReader(subReaders);

        BooleanQuery query = new BooleanQuery();
        //分词匹配keyword
        if ("1".equals(matchFlag) && StringUtil.isNotBlank(keyword)) {
            QueryParser queryParser = new QueryParser(BuildIndexJob.LUCENE_VERSION, "content",
                    analyzer);
            //两个词之间的关系是or
            queryParser.setDefaultOperator(QueryParser.OR_OPERATOR);
            query.add(queryParser.parse(QueryParser.escape(keyword.toLowerCase())), Occur.MUST);
        }
        //全量匹配keyword
        else if ("0".equals(matchFlag) && StringUtils.isNotBlank(keyword)) {
            Query kQuery = new TermQuery(new Term("keyword", keyword.toLowerCase()));
            query.add(kQuery, Occur.MUST);
        }
        //categoryId匹配
        if (StringUtils.isNotBlank(cateId)) {
            Query bQuery = new TermQuery(new Term("cateId", cateId));
            query.add(bQuery, Occur.MUST);
        }
        if (StringUtils.isNotBlank(sessionId)) {
            Query bQuery = new TermQuery(new Term("sessionId", sessionId));
            query.add(bQuery, Occur.MUST);
        }
        if (StringUtils.isNotBlank(countryId)) {
            Query bQuery = new TermQuery(new Term("countryId", countryId));
            query.add(bQuery, Occur.MUST);
        }
        if (StringUtils.isNotBlank(urlGourpId)) {
            Query bQuery = new TermQuery(new Term("urlGourpId", urlGourpId));
            query.add(bQuery, Occur.MUST);
        }
        Long startTime = System.currentTimeMillis();
        IndexSearcher search = new IndexSearcher(multiReader);
        //最多只返回20W数据,此数据是业务需求
        TopDocs topDocs = search.search(query, 200000);
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        if (logger.isDebugEnabled()) {
            logger.debug("search result: " + scoreDocs.length);
            logger.debug("search consume time: " + (System.currentTimeMillis() - startTime));
            startTime = System.currentTimeMillis();
        }
        if (scoreDocs.length <= 0) {
            return null;
        }
        result = this.getIds(scoreDocs, search);
        if (logger.isDebugEnabled()) {
            logger.debug("Reader [id] consume time: " + (System.currentTimeMillis() - startTime));
        }
        return result;
    }

/**
     * 从doc对象中获取所有的ID集合
     *
     * @param scoreDocs
     * @param multiSearcher
     * @return
     * @throws CorruptIndexException
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public List<Long> getIds(ScoreDoc[] scoreDocs, IndexSearcher search)
            throws CorruptIndexException, IOException {
        List<Long> ids = new ArrayList<Long>(scoreDocs.length);
        Map<String, FieldSelectorResult> fieldSelections = new HashMap<String, FieldSelectorResult>(
                1);
        fieldSelections.put("id", FieldSelectorResult.LOAD);
        FieldSelector fieldSelector = new MapFieldSelector(fieldSelections);

        //获取ID集合
        for (int i = 0; i < scoreDocs.length; i++) {
            Document doc = search.doc(scoreDocs[i].doc, fieldSelector);
            ids.add(Long.valueOf(doc.getFieldable("id").stringValue()));
        }
        return ids;
    }

红色部分是取文件时只装载ID,其它的域不装载,主样获取速度会快很多,文件越大加载越快


再有就是在性能测试时,windows和linux区别很大,主要是windows上默认没有使用多线程和内存映射,源代码如下

/** Just like {@link #open(File)}, but allows you to
   *  also specify a custom {@link LockFactory}. */
  public static FSDirectory open(File path, LockFactory lockFactory) throws IOException {
    if ((Constants.WINDOWS || Constants.SUN_OS || Constants.LINUX)
          && Constants.JRE_IS_64BIT && MMapDirectory.UNMAP_SUPPORTED) {
      return new MMapDirectory(path, lockFactory);
    } else if (Constants.WINDOWS) {
      return new SimpleFSDirectory(path, lockFactory);
    } else {
      return new NIOFSDirectory(path, lockFactory);
    }
  }

所以测试效果必需要在64位的linux上才能体现出

有问题请联系,ming.xiaom@qq.com//ming.xiaom@gmail.com,  gmail打开邮箱最近一直比较慢,不知道是什么原因
0
2
分享到:
评论
1 楼 飞儿9530 2011-11-11  
     

相关推荐

    Lucene+原理与代码分析完整版

    五、代码分析与实战 结合Lucene的API,我们可以编写出从创建索引、执行搜索到显示结果的完整代码。例如,如何自定义Analyzer、构建Document、使用IndexWriter写入索引,以及如何使用QueryParser和IndexSearcher进行...

    lucene-5.4.0

    3.3 查询过程:`QueryParser`解析用户输入的查询,生成`Query`对象,然后`IndexSearcher`执行查询,涉及`Scorer`, `Collector`等组件,这些组件协同工作完成文档的评分和排序。 3.4 分析器实现:深入源码可以了解...

    Lucene In Action second edition

    ##### 3.3 第三部分:实战篇 这部分提供了多个真实世界中的案例分析,帮助读者更好地理解和应用所学知识: - **第 7 章:案例研究——构建一个全文搜索引擎**:通过一个完整的项目来展示从零开始搭建基于 Lucene ...

    lucene2.2.0

    四、实战应用 在实际项目中,开发者可以利用Lucene 2.2.0来实现新闻网站的全文搜索、电子商务平台的商品搜索、论坛的帖子搜索等功能。同时,Lucene也可作为其他复杂搜索系统的底层引擎,例如Elasticsearch就是在...

    解密搜索引擎技术实战:Lucene&Java精华版

    ### 解密搜索引擎技术实战:Lucene&Java精华版 #### 搜索引擎基础知识及工作原理 本书开篇便从搜索引擎的基本概念入手,详细介绍了搜索引擎的工作原理和技术框架。在**第1章**“搜索引擎总体结构”中,作者从搜索...

    Elasticsearch 技术解析与实战.zip

    前言 第1章 Elasticsearch入门 1 1.1 Elasticsearch是什么 1 1.1.1 Elasticsearch的历史 2 1.1.2 相关产品 3 1.2 全文搜索 3 1.2.1 Lucene介绍 4 1.2.2 Lucene倒排索引 4 1.3 基础知识 6 1.3.1 Elasticsearch术语及...

    JAVA WEB典型模块与项目实战大全

    3.3 实现spring与struts 2.x集成  3.4 实现spring、struts2.x和hibernate框架集成  3.5 小结  第2篇 典型模块开发  第4章 在线文本编辑器(fckeditor)  4.1 分析fckeditor在线文本编辑器  4.2 ...

    百度云盘 pdf《大数据架构和算法实现之路:电商系统的技术实战》百度云盘-带标签目录

    4.5.1 Lucene 简介……………… 108 4.5.2 Solr 简介 ......………………… 113 4.5.3 Elasticsearch 简介…………… · 120 4.6 案例实践……………… 123 4.6.1 实验环境设置.. ... ....………… 123 4.6.2 基于...

    微信公众平台应用开发:方法、技巧与案例.(机械工业.柳峰)

     3.3 BAE的使用 46  3.3.1 注册账号 46  3.3.2 创建应用 47  3.3.3 托管设置 47  3.3.4 部署应用 48  3.3.5 获取访问地址 49  3.4 启用开发模式的步骤 49  3.5 小结 51 第4章 消息的接收与响应 52...

    Elasticsearch 是一个开源的分布式搜索和分析引擎,广泛用于实时搜索、日志和指标分析、全文搜索等应用 以下是关于 El

    Elasticsearch 基于 Apache Lucene 构建,这意味着它继承了 Lucene 在搜索方面的强大功能。同时,作为分布式系统,Elasticsearch 能够在多台服务器之间分发数据和任务,从而实现更高的性能和更大的数据处理能力。...

    opencms内容管理

    **5.18 至 5.20 实战案例** - 通过创建导航条和导航列表等具体例子,加深对标签的理解。 #### 六、FLEXCACHE缓存机制 **6.1 介绍** - FLEXCACHE是OpenCMS中的缓存机制,用于提高网站响应速度。 **6.2 FLEXCACHE...

    elk日志分析系统、elk速成宝典、elk新手晋级大神

    Elasticsearch是一个基于Lucene的分布式搜索引擎。它提供了一个高度可扩展的全文搜索和分析引擎,能够高效地处理大量数据,并提供实时搜索功能。 ##### 2.1 创建执行用户 为了确保Elasticsearch的安全运行,通常...

Global site tag (gtag.js) - Google Analytics