- 浏览: 633666 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
liuche20083736:
非常好
从问题看本质: 研究TCP close_wait的内幕 -
xiaopohai85707:
优化算法与原来需求不符
过滤字符的性能调优?挤一挤还是有的 -
kmy_白衣:
生成的area图有时候 标签的数值和图标上看上去的数值不一致。 ...
OpenFlashChart2之恶心文档 -
tom&jerry:
大神,请教一个问题,按名称排序为何无效,用的2.4.3 XPA ...
深入浅出jackrabbit之十三 查询之AST和QT -
jd2bs:
改成精确匹配可以了< filter-mapping &g ...
细谈Ehcache页面缓存的使用
/**
*author: ahuaxuan
*date: 2009-06-20
*/
查询进阶
在第一篇文章的例子中,我们看到jackrabbit中存放数据的流程还是比较清晰的,而且我们也基本确定了其中数据的存储方式,一颗m叉树。
正如db一样,insert都是看上去相对简单的,select相对总是没有这么简单的,如同关系数据库一样,jackrabbit中查询也有类似的流程,一般来说分成三个部分:1解析查询语句,2查询,3返回结果。这个应该是一个比较通用的模型。Jackrabbit的search部分亦不过如此。下面我们来看一个search的小例子。
这个例子很简单,使用xpath语法来查询world节点中包含指定字符串的同层节点。然后按照指定的属性排序并返回。从上面的例子中我们大概就只能得到这么多信息了,但是我想爱好技术的你一定不会满足,也一定不会止步于这个小小的例子。如果同第一篇文章所讲,我们不能只看到事物的表面,而是要通过这个表面来挖掘其背后的本质。
所以接下来让我们来看看事物背后的本质。众所周知,只要是数据存储方案,那么必定离不开一个东西,那就是索引,索引可以帮助我们快速找到我们想要的数据。那么jackrabbit是怎么根据xpath语法就找到对应的数据的呢,无疑,它也是在运用搜索技术,而且通过它的package列表我们可以看到,它其实就是利用lucene来完成了它的查询模块。也就是说在jackrabbit中,利用了xpath和lucene实现了它的查询模块。
搞清楚了这两点,我们就可以继续往下深入了。导入源代码,让我们从createQuery方法开始,这个方法很重要,可以说是非常重要,而且这个方法中也包含了一门基础学科-编译原理(jackrabbit中是使用javacc来生成xpath的词法语法分析类的org.apache.jackrabbit.core.query.xpath.Xpath,并且它的语法文件xpath.jj长达9000行,ahuaxuan暂时还没有这个闲心看下去)。createQuery方法的两个参数,一个是查询语句,还有一个是语句的语法,目前jackrabbit支持xpath语法和sql语法,他们在整个流程上属于一个可以相互替换的节点上,就是生成AST。再生产语法树之后,会生成查询节点树,这个颗树中包含了lucene需要的很多信息,比如需要搜索的key word,搜索时的排序字段等等。所以在第一大步骤createquery中我们可以细分为两个小的步骤:
1 编译query statement,生成AST
2 通过AST生成querynode
通过上面的两个主要步骤,我们顺利的得到了query对象,query对象就包含的查询所需要的基本条件了,很显然它和hibernate的query对象有异曲同工之妙。都是通过一个查询语句来生产query对象,然后query对象中包含了查询的所有信息,一旦执行execute方法,就真正的触发了的查询的流程。
说到这里就不得不看一下一个关键的方法(org.apache.jackrabbit.core.query.lucene.QueryImpl):
通过上面的代码注释,那么我们可以得到一个有效的信息,那就是在ahuaxuan提供的示例代码中执行query.execute()的时候,这个时候,我们需要的结果其实就已经加载到内存中了。有的人会感到疑惑,为什么上面的QueryResultImpl中带有这么多构造参数,其实那些对象是用来组装lucene出来的结果的。因为放到索引中的数据并不是node的所有信息,比如一个node,因为放到索引中只是nodeid,其对应的node对象还是需要通过itemMgr这样的对象来得到。
这样我们又确定了第二大步骤包含了二个步骤:
1 根据querynode生成lucene的query对象
2 执行lucene的query对象并得到结果集
现在数据,也有了,那么接下来做什么呢?当然是遍历数据,拿出结果来,显然这个和jdbc的工作流程有得一拼啊(看来这里的时候,大家对查询的使用是不是比较清楚了,它和很多持久化框架在流程上是相当一致的,熟悉了一个之后,再来看其他的,学习的速度明显得到加强,当前前提是你已经充分理解了某个框架的内部原理)。那么接下来的事情只有这些:
看上去很简单。实际上也比较简单,但是其中确有一个ahuaxuan认为比较愚蠢的设计。拿这个例子来说,接下来的流程就是得到得到数据,并作一些客户端需要的操作,比如高亮。
通过代码的阅读:在这个大步骤中,又细分为以下步骤:
1 根据getvalue中参数判断查询的内容,比如jcr:score,jcr:path等,这些属性很容易就能得到,可以说是现成的,稍微看一下代码就知道他们是怎么获得的,而在上面的例子中,我们是要找jcr:data这个属性,并高亮之。而且这个属性也是最复杂的,其他属性比较简单,下面我们把注意力集中到这个属性。Jcr:data是jackrabbit的内在属性,目前一个node只能有一个这样的属性,这个属性中存放着二进制文档提取之后的fulltext的信息(当然你可以手动指定这个属性存放什么text)。现在我们就是要在结果集中高亮这个我们的keyword->’Maven’,之前ahuaxuan将maven的一篇文档放到jackrabbit中了,所以现在要高亮这个结果集。
要高亮之,必须得到Maven在这个text中的offset,所以就出现了下面这段代码:
就是这段代码,可能会给我们带来一些些小麻烦。得到offset之后,那么下一步肯定就是高亮了,高亮的逻辑简要说来就是遍历这个offset,并根据每个offset指定的start和end来插入高亮的标识。
看上去,一切顺理成章,我们的例子是使用1m的maven文档,测试的结果是:
也就是说计算每个term和对应的offset集合用了370ms,是整个搜索过程耗时的75%以上,如果把这一步省掉,可以节约75%的时间,那么如何省掉呢,两个方案,一,修改analyser,只找keyword和对应的offset,二,搜索的时候将term和对应offset保存起来。这样此处的370ms应该可以大大变小。经过ahuaxuan的修改之后,将offset保存在index之中,然后高亮的时候直接拿出来,避免了实时分词。测试的结果如下
也就是说这个方法从370ms变成了4ms,速度大大加快(虽然索引文件会变大一些)。
除此之外,在高亮的时候我们可以不用高亮全文,也就是取前10个offset,然后高亮之,再次运行相同的测试:
高亮从146ms变成了1ms
这样虽然只会高亮前10个关键字,但是速度从146ms变成了1ms.对于用户来说,高亮一个摘要并非不能接受。带来的好处是:
1 用户可以更加快速的得到结果集
2 大家知道,文字处理是非常消耗cpu的,通过这种方式也一定程度上节约了服务器上的cpu
对服务器,对客户都好,何乐而不为
通过以上两处的修改,原来高亮1m的文本需要530ms,而现在只需要5ms,提高了100倍。ahuaxuan以为,为了速度,这样的修改是值得的。
说到这里,查询这块可以告一段落了:
下面总结一下查询过程中的主要步骤:
1 编译query statement,生成AST
2 通过AST生成querynode
3 根据querynode生成lucene的query对象
4 执行lucene的query对象并得到结果集
5 遍历中:通过itemmanager和查询结果中的nodeid得到需要的node
6 生成offset
7 高亮之
由于ahuaxuan水平有限,见解未必全面,如有错误,请大家拍砖。
这段代码我也没有了,因为我发现这样做还是有问题,这样做会使lucene的index文件增加3倍,其中offset和解析出来的文本是不需要放进去的,索引文件越大,那么搜索或者以后改造成分布式的困难就越高。我比较推荐的做法是高亮还是放在客户端做吧
*author: ahuaxuan
*date: 2009-06-20
*/
查询进阶
在第一篇文章的例子中,我们看到jackrabbit中存放数据的流程还是比较清晰的,而且我们也基本确定了其中数据的存储方式,一颗m叉树。
正如db一样,insert都是看上去相对简单的,select相对总是没有这么简单的,如同关系数据库一样,jackrabbit中查询也有类似的流程,一般来说分成三个部分:1解析查询语句,2查询,3返回结果。这个应该是一个比较通用的模型。Jackrabbit的search部分亦不过如此。下面我们来看一个search的小例子。
public static void search(String co) throws Exception { Repository repository = new TransientRepository(); Session session = repository.login(new SimpleCredentials("username", "password".toCharArray())); try { // path query Workspace workSpace = session.getWorkspace(); QueryManager queryMgr = workSpace.getQueryManager(); //查询语句 String queryPath2 = "//world [jcr:contains(., 'Maven')] order by jcr:score() descending, @name ascending"; //编译查询语句 Query query = queryMgr.createQuery(queryPath2, Query.XPATH); //执行查询任务 QueryResult queryResult = query.execute(); //得到结果 RowIterator rows = queryResult.getRows(); String result = ""; while (rows.hasNext()) { Row resultRow = rows.nextRow(); /* Value[] values = resultRow.getValues(); for (Value value : values) { System.out.println(" Value ------ " + value.getString()); } */ // // -------------------------------------------------------------------------- TimerMasker tm = new TimerMasker(); result = resultRow.getValue("rep:excerpt(jcr:data)").getString(); tm.print("----------------------------------"); // } } finally { session.logout(); } }
这个例子很简单,使用xpath语法来查询world节点中包含指定字符串的同层节点。然后按照指定的属性排序并返回。从上面的例子中我们大概就只能得到这么多信息了,但是我想爱好技术的你一定不会满足,也一定不会止步于这个小小的例子。如果同第一篇文章所讲,我们不能只看到事物的表面,而是要通过这个表面来挖掘其背后的本质。
所以接下来让我们来看看事物背后的本质。众所周知,只要是数据存储方案,那么必定离不开一个东西,那就是索引,索引可以帮助我们快速找到我们想要的数据。那么jackrabbit是怎么根据xpath语法就找到对应的数据的呢,无疑,它也是在运用搜索技术,而且通过它的package列表我们可以看到,它其实就是利用lucene来完成了它的查询模块。也就是说在jackrabbit中,利用了xpath和lucene实现了它的查询模块。
搞清楚了这两点,我们就可以继续往下深入了。导入源代码,让我们从createQuery方法开始,这个方法很重要,可以说是非常重要,而且这个方法中也包含了一门基础学科-编译原理(jackrabbit中是使用javacc来生成xpath的词法语法分析类的org.apache.jackrabbit.core.query.xpath.Xpath,并且它的语法文件xpath.jj长达9000行,ahuaxuan暂时还没有这个闲心看下去)。createQuery方法的两个参数,一个是查询语句,还有一个是语句的语法,目前jackrabbit支持xpath语法和sql语法,他们在整个流程上属于一个可以相互替换的节点上,就是生成AST。再生产语法树之后,会生成查询节点树,这个颗树中包含了lucene需要的很多信息,比如需要搜索的key word,搜索时的排序字段等等。所以在第一大步骤createquery中我们可以细分为两个小的步骤:
1 编译query statement,生成AST
2 通过AST生成querynode
通过上面的两个主要步骤,我们顺利的得到了query对象,query对象就包含的查询所需要的基本条件了,很显然它和hibernate的query对象有异曲同工之妙。都是通过一个查询语句来生产query对象,然后query对象中包含了查询的所有信息,一旦执行execute方法,就真正的触发了的查询的流程。
说到这里就不得不看一下一个关键的方法(org.apache.jackrabbit.core.query.lucene.QueryImpl):
public QueryResult execute(long offset, long limit) throws RepositoryException { if (log.isDebugEnabled()) { log.debug("Executing query: \n" + root.dump()); } TimerMasker tk = new TimerMasker(); // 这里创建lucene的query对象,同时,这是一个BooleanQuery Query query = LuceneQueryBuilder.createQuery(root, session, index.getContext().getItemStateManager(), index.getNamespaceMappings(), index.getTextAnalyzer(), propReg, index.getSynonymProvider(), index.getIndexFormatVersion()); //得到排序信息 OrderQueryNode orderNode = root.getOrderNode(); OrderQueryNode.OrderSpec[] orderSpecs; if (orderNode != null) { orderSpecs = orderNode.getOrderSpecs(); } else { orderSpecs = new OrderQueryNode.OrderSpec[0]; } Name[] orderProperties = new Name[orderSpecs.length]; boolean[] ascSpecs = new boolean[orderSpecs.length]; for (int i = 0; i < orderSpecs.length; i++) { orderProperties[i] = orderSpecs[i].getProperty(); ascSpecs[i] = orderSpecs[i].isAscending(); } //创建查询结果,事实上,这个一部才是真正执行lucene query的//地方。这一点可以从QueryResultImpl的构造方法中看出来,构造方法中//的getresult其实就是真正执行lucene search的地方,比较顺理成章 QueryResultImpl qri = new QueryResultImpl(index, itemMgr, session, session.getAccessManager(), this, query, new SpellSuggestion(index.getSpellChecker(), root), getSelectProperties(), orderProperties, ascSpecs, getRespectDocumentOrder(), offset, limit); tk.print("execute"); return qri; }
通过上面的代码注释,那么我们可以得到一个有效的信息,那就是在ahuaxuan提供的示例代码中执行query.execute()的时候,这个时候,我们需要的结果其实就已经加载到内存中了。有的人会感到疑惑,为什么上面的QueryResultImpl中带有这么多构造参数,其实那些对象是用来组装lucene出来的结果的。因为放到索引中的数据并不是node的所有信息,比如一个node,因为放到索引中只是nodeid,其对应的node对象还是需要通过itemMgr这样的对象来得到。
这样我们又确定了第二大步骤包含了二个步骤:
1 根据querynode生成lucene的query对象
2 执行lucene的query对象并得到结果集
现在数据,也有了,那么接下来做什么呢?当然是遍历数据,拿出结果来,显然这个和jdbc的工作流程有得一拼啊(看来这里的时候,大家对查询的使用是不是比较清楚了,它和很多持久化框架在流程上是相当一致的,熟悉了一个之后,再来看其他的,学习的速度明显得到加强,当前前提是你已经充分理解了某个框架的内部原理)。那么接下来的事情只有这些:
//得到结果 RowIterator rows = queryResult.getRows(); while (rows.hasNext()) { Row resultRow = rows.nextRow(); resultRow.getValue("rep:excerpt(jcr:data)").getString(); }
看上去很简单。实际上也比较简单,但是其中确有一个ahuaxuan认为比较愚蠢的设计。拿这个例子来说,接下来的流程就是得到得到数据,并作一些客户端需要的操作,比如高亮。
通过代码的阅读:在这个大步骤中,又细分为以下步骤:
1 根据getvalue中参数判断查询的内容,比如jcr:score,jcr:path等,这些属性很容易就能得到,可以说是现成的,稍微看一下代码就知道他们是怎么获得的,而在上面的例子中,我们是要找jcr:data这个属性,并高亮之。而且这个属性也是最复杂的,其他属性比较简单,下面我们把注意力集中到这个属性。Jcr:data是jackrabbit的内在属性,目前一个node只能有一个这样的属性,这个属性中存放着二进制文档提取之后的fulltext的信息(当然你可以手动指定这个属性存放什么text)。现在我们就是要在结果集中高亮这个我们的keyword->’Maven’,之前ahuaxuan将maven的一篇文档放到jackrabbit中了,所以现在要高亮这个结果集。
要高亮之,必须得到Maven在这个text中的offset,所以就出现了下面这段代码:
Reader r = new StringReader(text); TokenStream ts = index.getTextAnalyzer().tokenStream("", r); Token t; try { while ((t = ts.next()) != null) { TermVectorOffsetInfo[] info = (TermVectorOffsetInfo[]) termMap.get(t.termText()); if (info == null) { info = new TermVectorOffsetInfo[1]; } else { TermVectorOffsetInfo[] tmp = info; info = new TermVectorOffsetInfo[tmp.length + 1]; System.arraycopy(tmp, 0, info, 0, tmp.length); } info[info.length - 1] = new TermVectorOffsetInfo( t.startOffset(), t.endOffset()); termMap.put(t.termText(), info); } } catch (IOException e) { // should never happen, we are reading from a string }
就是这段代码,可能会给我们带来一些些小麻烦。得到offset之后,那么下一步肯定就是高亮了,高亮的逻辑简要说来就是遍历这个offset,并根据每个offset指定的start和end来插入高亮的标识。
看上去,一切顺理成章,我们的例子是使用1m的maven文档,测试的结果是:
createTermPositionVector >>>-------- total nano seconds : 373531154 highlight:2761 >>>-------- total nano seconds : 146860414
也就是说计算每个term和对应的offset集合用了370ms,是整个搜索过程耗时的75%以上,如果把这一步省掉,可以节约75%的时间,那么如何省掉呢,两个方案,一,修改analyser,只找keyword和对应的offset,二,搜索的时候将term和对应offset保存起来。这样此处的370ms应该可以大大变小。经过ahuaxuan的修改之后,将offset保存在index之中,然后高亮的时候直接拿出来,避免了实时分词。测试的结果如下
new createTermPositionVector >>>-------- total nano seconds : 4003012
也就是说这个方法从370ms变成了4ms,速度大大加快(虽然索引文件会变大一些)。
除此之外,在高亮的时候我们可以不用高亮全文,也就是取前10个offset,然后高亮之,再次运行相同的测试:
highlight:10 >>>-------- total nano seconds : 1264721
高亮从146ms变成了1ms
这样虽然只会高亮前10个关键字,但是速度从146ms变成了1ms.对于用户来说,高亮一个摘要并非不能接受。带来的好处是:
1 用户可以更加快速的得到结果集
2 大家知道,文字处理是非常消耗cpu的,通过这种方式也一定程度上节约了服务器上的cpu
对服务器,对客户都好,何乐而不为
通过以上两处的修改,原来高亮1m的文本需要530ms,而现在只需要5ms,提高了100倍。ahuaxuan以为,为了速度,这样的修改是值得的。
说到这里,查询这块可以告一段落了:
下面总结一下查询过程中的主要步骤:
1 编译query statement,生成AST
2 通过AST生成querynode
3 根据querynode生成lucene的query对象
4 执行lucene的query对象并得到结果集
5 遍历中:通过itemmanager和查询结果中的nodeid得到需要的node
6 生成offset
7 高亮之
由于ahuaxuan水平有限,见解未必全面,如有错误,请大家拍砖。
评论
5 楼
lnjzliulei
2013-06-03
兄弟,有源代码吗?48703577@qq.com请给发一份,谢谢!
4 楼
ahuaxuan
2009-12-14
无量胖佛 写道
有你写的源码可以给发一个吗?
wddsmzx@gmail.com
谢谢了 老哥
wddsmzx@gmail.com
谢谢了 老哥
这段代码我也没有了,因为我发现这样做还是有问题,这样做会使lucene的index文件增加3倍,其中offset和解析出来的文本是不需要放进去的,索引文件越大,那么搜索或者以后改造成分布式的困难就越高。我比较推荐的做法是高亮还是放在客户端做吧
3 楼
无量胖佛
2009-12-10
有你写的源码可以给发一个吗?
wddsmzx@gmail.com
谢谢了 老哥
wddsmzx@gmail.com
谢谢了 老哥
2 楼
ahuaxuan
2009-06-20
其实我是先看index的,index早于search,index的流程过两天写出来,我把search这部分改了一下,把创建offset放到index的流程中了,highlighter也变成只取10个offset来作,在大文本的情况下,查询速度从原来的500ms缩小为5ms
1 楼
lxiaodao
2009-06-20
不错文章,对于query有了一定得了解,如果能有分析一下index的原理的文章就更好了。
发表评论
-
深入浅出jcr之16 该死的RMI,我们需要HTTP+简单RPC协议
2009-12-12 13:22 6654从这篇文 ... -
深入浅出jackrabbit之十五 文档提取优化2.docx
2009-10-22 18:38 3828/** *author:ahuaxuan *2009- ... -
深入浅出jackrabbit之十四 分布式文档提取
2009-09-24 12:20 4679/** *author:ahuaxuan *200 ... -
深入浅出jackrabbit之十三 查询之AST和QT
2009-09-10 10:12 3343简介:在前面的文章中 ... -
深入浅出jcr之十二 key-value存储系统
2009-08-26 09:31 3696作者:ahuaxuan 在写文章方面,惰性心理 ... -
深入浅出jcr之十一 jackrabbit改进要点
2009-08-18 18:22 3563作者,ahuaxuan 在看过前 ... -
深入浅出jcr之十 redolog 和 recovery.docx
2009-08-18 18:14 2049作者:ahuaxuan 在前面的 ... -
深入浅出 jackrabbit 九 索引合并(下)
2009-07-22 14:16 2007在上文中,ahuaxuan讲到了索引创建的主体流程,但是索引合 ... -
深入浅出 jackrabbit 八 索引合并(上)
2009-07-21 17:32 2479我们从文本提取的逻辑中走出来,回到主体流程。 在前面的文 ... -
深入浅出 jackrabbit 七 文本提取(下)
2009-07-21 17:29 2483接上文,说到文本提取,在上一篇文章中,我们是管中窥豹,并没有把 ... -
深入浅出 jackrabbit 六 文本提取(上)
2009-07-21 17:27 3265用lucene作过索引的同 ... -
深入浅出 jackrabbit 之五 索引提交(下)
2009-07-14 17:53 2207接上文,在上面一篇文章中,我们谈到了update中的Delet ... -
深入浅出 jackrabbit 之四 索引提交(上)
2009-07-14 09:10 2849在上上篇文章中,我们了解了创建索引的一般流程,在上篇文章中,我 ... -
深入浅出 jackrabbit 3 创建 document
2009-07-01 13:03 4224/** *作者:ahuaxuan 张荣华 *日期:2009-0 ... -
深入浅出 jackrabbit 2 索引概览
2009-06-30 08:51 5320任何一个数据库都离不 ... -
深入浅出 jackrabbit 1
2009-05-19 18:31 12363/** * author:ahuaxuan( ...
相关推荐
NULL 博文链接:https://ahuaxuan.iteye.com/blog/391361
深入浅出讲解jackrabbit 共分十个专题。PDF 文档
jackrabbit最全入门教程,jackrabbit最全入门教程,jackrabbit最全入门教程,jackrabbit最全入门教程
jackrabbit 1.5.6 jar
jackrabbit-standalone-1.6.5.jar是webDav的支持jar包。
Apache Jackrabbit API html ,非常详细,
jackrabbit-api-1.5.0.jar
常见问题查询。作为JCR的一个实例,为用户提供一种网络存储、共享、应用的方式
一个Eclipse项目, 内含三个Apache Jackrabbit的入门实例, 以及所有需要的包, 在Eclipse中可直接运行。
jackrabbit开发用jar包,jackrabbit是基于Lucene的一种站内搜索技术,它用xml文件为他的元数据,自动穿件索引,使用xpath或者xquery的查询方法。
jackrabbit教程,主要是网上整合的资源。比较全面。用法等。
jackrabbit-webdav-2.1.0.jar 具体用法可以网上查找
jackrabbit-core-1.5.5.jar
jackrabbit 研究初步,不想多说,肯定值。研究了好久哦。
jackrabbit, 在amqplib上,简单的amqp/rabbitmq作业队列基于 node Jackrabbitnode.js 在不讨厌生命的情况下。producer.js:var jackrabbit = require('jackrabbit');var rabbit = jackrabbit(process
里面包含两个工程,一个是官方的三个小demo,另一个是在ibm页面看到的实例,比小demo稍微高级一些,希望可以帮助大家迅速入门和理解jackrabbit。
jackrabbit-webdav-1.5.5.jar
Rabbit BL1800 Jackrabbit 说明书pdf,Rabbit BL1800 Jackrabbit 说明书
NULL 博文链接:https://kinglord2010.iteye.com/blog/665530
apache内容管理apache内容管理