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

依赖Lucene的电话号码查询优化

阅读更多

  某天的生产环境服务器突然报告有cpu load超负荷的报警,然后赶紧查看查询日志,发现里里面有大量类似这样的查询:

{q=(customer_mobile:/[0-9]{7}7785/+OR+code:7785)&distrib=false&_stateVer_=search4card:1494&start=0&fentityid:123456&rows=20&wt=javabin&version=2&_route_=123456&single.slice.query=true} hits=1 status=0 QTime=694 

 很明显有一个很特殊的查询条件customer_mobile:/[0-9]{7}7785/,意思是需要查询手机号码后四位为7785的记录,从日志上观察到响应时间还挺长的,平均都需要600毫秒起。

 

  经验告诉我,CPU load飙高是因为这个查询条件造成的,然后联系了业务团队,先把这个查询CASE关掉,随后服务器cpu load也随之恢复了正常。

  那么,现在需要对查询进行优化,使用正则式匹配依赖了lucene4之后引入的FST来存储term,一定程度上优化了Lucene上的模糊匹配性能,但,本质上肯定比依赖倒排链的查询效率低了几个数量级。

  改成使用倒排链也比较简单:

 

<fieldType name="mobile" class="solr.TextField"  > 
   <analyzer type="index"> 
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      <filter class="solr.NGramFilterFactory" minGramSize="2" maxGramSize="11" />	</analyzer>  
    <analyzer type="query"> 
      <tokenizer class="solr.WhitespaceTokenizerFactory"/> 
    </analyzer> 
</fieldType>

 

 

      使用ngram分词可以很好地解决模糊匹配的问题,但是这没法解决,需要匹配的关键词在电话号码中出现的位置进行过滤,比如“7785”在电话号码中间位置出现,这样的记录是需要过滤掉的,但是光用NGram没法实现此功能。

  解决办法是在构建索引时,倒排链上附加上Term所在词条位置出现的偏移位置(OffsetAttribute),构建TokenStream流时,添加OffsetAttribute属性值,正好它能设置startOffset和endoffset,

private final OffsetAttribute offsetAttribute;
	public AllWithPositionNGramTokenFilter(TokenStream input, int minGram, int maxGram) {
		super(input, minGram, maxGram);
		this.offsetAttribute = this.addAttribute(OffsetAttribute.class);
	}

	@Override
	protected void appendAttribute(char[] curTermBuffer, int curTermLength, int start, int end) {
		this.offsetAttribute.setOffset(start, end);
	}

 接下来需要将索引的offset写入开关(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)打开,需要在SchemafieldType上去设置属性值。经过试验这个开关始终没有成功,最后打算,还是在代码里面写死吧,虽然粗暴但是很有效:

 

public class TextFieldWithOffset extends TextField {
	protected IndexOptions getIndexOptions(SchemaField field, String internalVal) {
		return IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS;
	}
   }

 只要在方法getIndexOptions返回DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS,构建索引时就会拿到TokenStream中的Offset属性存到倒排索引中,schema中的fieldtype改成如下:

 

<fieldType name="mobile" class="com.dfire.tis.solrextend.fieldtype.common.TextFieldWithOffset" 
           sortMissingLast="true" omitNorms="true" 
           autoGeneratePhraseQueries="false" > 
   <analyzer type="index"> 
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
	  <filter class="com.dfire.tis.solrextend.fieldtype.s4card.AllWithPositionNGramTokenFactory" minGramSize="2" maxGramSize="5" />
	</analyzer>  
    <analyzer type="query"> 
      <tokenizer class="solr.WhitespaceTokenizerFactory"/> 
    </analyzer> 
</fieldType>

 你会发现之前ngram的实现改成了AllWithPositionNGramTokenFactory,是的因为Solr默认实现的Offset的值和term的实际位置偏移量是不一致的,所以需要略微修改。

 

 完成以上工作之后,就能写一个测试,试着写一条手机到索引中,看看offset是否正常生成。

 

public static final EmbeddedSolrServer server;
	public static final File solrHome;

	static {
		solrHome = new File("D:/solr/solrhome");
		server = new EmbeddedSolrServer(solrHome.toPath(), "s4card");
	}

	public void testAddSpan() throws Exception {
		long ver = 20171201001007l;
		SolrInputDocument doc = new SolrInputDocument();

		doc.setField("customer_mobile", "15868113480");
		
		server.add(doc);
		server.commit();

		SolrCore core = server.getCoreContainer().getCore("s4card");

		RefCounted<SolrIndexSearcher> s = core.getNewestSearcher(true);

		this.mobileOffsetView(s);	}
	public void kindCardId(RefCounted<SolrIndexSearcher> s) throws Exception {
		
		SolrIndexSearcher searcher = s.incref().get();
		PostingsEnum postings = MultiFields.getTermPositionsEnum(searcher.getIndexReader(),
				"customer_mobile", new BytesRef("480"));
		int docid = -1;
		while ((docid = postings.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
			int start = 0;
			System.out.println(postings.freq());

			if((start = postings.nextPosition()) != Spans.NO_MORE_POSITIONS) {

				System.out.println("post start:" + start + ",startOffset:" + postings.startOffset()
						+ ",endOffset:" + postings.endOffset());
			}
		}
		s.decref();
		
	}

 

 

Ok,现在我们就可以写一个自定义QueryParser了,以下代码为了突出重点作了删掉,详细的可以下载:

 

public class MoblieQParserPlugin extends QParserPlugin {
	public QParser createParser(String qstr, SolrParams localParams, SolrParams params,
			SolrQueryRequest req) {
		String fieldName = localParams.get("f");
		int startPos = localParams.getInt("start_pos", 2);
		
		SpanTermQuery tq = new SpanTermQuery(new Term(fieldName, qstr));
		final MobileSpanPositionCheckQuery fquery = new MobileSpanPositionCheckQuery(tq, startPos,
				StringUtils.length(qstr));
		return new QParser(qstr, localParams, params, req) {
			@Override
			public Query parse() throws SyntaxError {
				return fquery;
			}
		};
	}

	private class MobileSpanPositionCheckQuery extends SpanPositionCheckQuery {

		private final int startPos;
		private final int end;

		public MobileSpanPositionCheckQuery(SpanQuery match, int startPos, int length) {
			super(match);
			this.startPos = startPos;
			this.end = startPos + length;
		}

		。。。。。

		@Override
		protected AcceptStatus acceptPosition(Spans spans) throws IOException {
			TermSpans termsSpan = (TermSpans) spans;
			PostingsEnum posting = termsSpan.getPostings();

			if (posting.startOffset() >= end) {
				return AcceptStatus.NO_MORE_IN_CURRENT_DOC;
			} else if (posting.startOffset() == startPos) {
				return AcceptStatus.YES;
			} else {
				return AcceptStatus.NO;
			}
		}
		。。。。。。	
	}

}

 QP中告诉Query需要设置起始的Offset通过acceptPosition函数可以实现doc的二次过滤(第一次是按照Term命中,第二次在第一次的结果集上通过offset进一步过滤)。

将这个QPPlugin配置到solrconfig中:

 

<queryParser name="termPos"    class="com.s4card.MoblieQParserPlugin" /> 

 

最后,就是客户端调用啦,通过这样的query语句就能找到手机末四位为3480的记录了,如果刚开始学Lucene的同学可能会说,“这个查询写法为啥这么复杂,数据库SQLlike查询写法多简单”,那么我要提醒你,哪怕有上亿记录筛选也能达到毫秒级响应速度哦(这个是大数据时代尤为重要的),而且还能根据关键字出现位置进行过滤,这又是数据库查询无法实现的。

 

{!termPos f=customer_mobile start_pos=7}3480

 

 

Java查询代码为:

 

 SolrQuery q = new SolrQuery();
q.setQuery("{!termPos f=customer_mobile start_pos=7}3480");
QueryResponse result = server.query(q);
System.out.println("numFound:" + result.getResults().getNumFound());

 

 

至此,代码发布上线,电话后缀查询的RT恢复到毫秒级别,CPUload也恢复正常了,依赖倒排索引优化电话号码查询的案例就介绍到这里。还等什么赶紧去尝试吧。在实际的开发中,我们完全可以通过这个优化案例举一反三,org.apache.lucene.util.Attribute的扩展除了OffsetAttribue还有PostingAttributeCharTermAttribute等,依赖他们能实现更多杀手级的使用方式。

 

 

分享到:
评论

相关推荐

    lucene分组查询优化facet

    本篇文章将详细探讨Lucene的分组查询优化,以及如何使用Facet功能来提升用户体验。 一、Lucene分组查询原理 Lucene的分组查询(Faceting)是通过对索引中的文档进行多级分类来实现的。它首先会计算每个分面值的文档...

    Lucene3.0之查询类型详解

    【Lucene3.0查询类型详解】 在Lucene3.0中,查询处理是一个关键环节,涉及多种查询方式和理论模型。以下是对这些概念的详细解释: 1. **查询方式**: - **顺序查询**:是最简单的查询方式,直接遍历索引,效率较...

    lucene表达式处理查询

    在实际项目中,结合对Lucene内核的理解和优化技巧,可以进一步提升查询性能,满足不同场景下的搜索需求。 以上就是关于“Lucene表达式处理查询”的详细解析,希望对你理解Lucene的高级查询功能有所帮助。通过实践和...

    lucene、lucene.NET详细使用与优化详解

    《lucene、lucene.NET 详细使用与优化详解》 lucene 是一个广泛使用的全文搜索引擎库,其.NET版本称为lucene.NET,它提供了强大的文本检索和分析能力,适用于各种场景下的全文搜索需求。lucene 并非一个可以直接...

    lucene 多字段查询+文字高亮显示

    本话题聚焦于“Lucene多字段查询”和“文字高亮显示”,这两个特性在信息检索和数据挖掘中具有广泛应用。 首先,让我们深入理解“Lucene多字段查询”。在信息检索系统中,用户可能希望根据多个字段来过滤和排序结果...

    Lucene索引和查询

    **Lucene索引和查询** Lucene是Apache软件基金会的开放源码全文搜索引擎库,它提供了文本检索的核心工具,使得开发者能够快速构建自己的搜索应用。本项目中的代码旨在展示如何利用Lucene对多个文件夹下的数据进行...

    lucene的查询语法事例

    **Lucene查询语法详解** Apache Lucene是一款高性能、全文本搜索库,被广泛应用于各种搜索引擎的构建。在使用Lucene进行信息检索时,理解和掌握其查询语法至关重要。本篇文章将深入探讨Lucene的查询语法,帮助你更...

    lucene实现索引查询

    以下是关于使用Lucene实现索引查询的详细知识: ### 一、创建索引 创建索引是Lucene的核心过程,它涉及到以下步骤: 1. **定义索引目录**:首先,你需要指定一个目录来存储索引文件。这通常是一个文件夹,可以...

    lucene的封装和性能优化

    **Lucene封装与性能优化详解** Lucene是一个高性能、全文本搜索库,它为开发者提供了在应用程序中实现全文检索的功能。然而,为了更好地适应实际项目需求,通常需要对其进行封装,以便于管理和提升性能。本文将深入...

    lucene做索引查询流程

    lucene 做索引查询流程,来自《lucene in action》

    Lucene分词与查询详解

    **Lucene分词与查询详解** Lucene是一个高性能、全文本搜索库,广泛应用于各种搜索引擎的开发中。它提供了一套强大的API,用于索引文本数据,并执行复杂的查询操作。在深入理解Lucene的分词与查询机制之前,我们...

    Lucene时间区间搜索

    本篇将深入探讨如何在C#中实现Lucene的时间区间查询匹配,以及涉及的相关技术点。 首先,我们需要了解Lucene的基本操作流程,包括索引构建、查询解析和结果检索。在C#中,我们可以使用Apache.Lucene.Net库来操作...

    lucene实现企业产品检索

    Lucene提供了多种查询类型,如TermQuery、PhraseQuery、WildcardQuery等,可以根据需求选择合适的查询方式。接着,使用`IndexSearcher`执行查询,并通过`TopDocs`获取排名最高的搜索结果。 **4. 高级搜索特性** ...

    lucene排序、设置权重、优化、分布式搜索.pdf

    Lucene 排序、设置权重、优化、分布式搜索 Lucene 是一个高性能的搜索引擎库,它提供了强大的文本搜索和索引能力。下面我们将详细介绍 Lucene 的排序、设置权重、优化和分布式搜索等知识点。 一、Lucene 排序 ...

    Lucene5学习之分页查询

    **正文** 在IT行业中,搜索引擎和全文检索技术是...通过对源码的深入学习,我们可以更好地理解其工作原理,优化查询性能。同时,利用相关的工具,如Solr和Elasticsearch,可以进一步简化开发过程,提供更强大的功能。

Global site tag (gtag.js) - Google Analytics