- 浏览: 639647 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
liuche20083736:
非常好
从问题看本质: 研究TCP close_wait的内幕 -
xiaopohai85707:
优化算法与原来需求不符
过滤字符的性能调优?挤一挤还是有的 -
kmy_白衣:
生成的area图有时候 标签的数值和图标上看上去的数值不一致。 ...
OpenFlashChart2之恶心文档 -
tom&jerry:
大神,请教一个问题,按名称排序为何无效,用的2.4.3 XPA ...
深入浅出jackrabbit之十三 查询之AST和QT -
jd2bs:
改成精确匹配可以了< filter-mapping &g ...
细谈Ehcache页面缓存的使用
在上上篇文章中,我们了解了创建索引的一般流程,在上篇文章中,我们也已经明确的知道,一个node的哪些信息是需要加入到索引中去的,也就是jackrabbit是如何来创建document的。那么接下来我们就是要了解jackrabbit是如何把这些document加入到索引文件中去的。
接下来我们需要回顾一下索引概览中的最后几句话:
那么代码的主要逻辑在哪里呢?
在DeleteNode和AddNode类中,还有就是在flush方法中。
这句话充分了说明本文需要描述的内容,那便是
1 如何把document从index删除
2 如何把document放到index中
3 什么是flush
在讨论这3个内容之前我们有必要说明一下executeAndLog方法的作用,从方法名,我们得到的信息是:1执行Node,而log一些数据,那么我们再来看看源代码
executeAndLog(new DeleteNode(transactionId, (UUID) remove.next()));
先进入了executeAndLog方法:
private Action executeAndLog(Action a) throws IOException { a.execute(this); redoLog.append(a); // please note that flushing the redo log is only required on // commit, but we also want to keep track of new indexes for sure. // otherwise it might happen that unused index folders are orphaned // after a crash. if (a.getType() == Action.TYPE_COMMIT || a.getType() == Action.TYPE_ADD_INDEX) { redoLog.flush(); // also flush indexing queue indexingQueue.commit(); } return a; }
在上面这个方法中,我们可以看到以下几个流程:
1 执行DeleteNode的execute方法(是Action的实现类)
2 记录redolog(redolog做啥呀,先留着)
3 刷新redolog到磁盘同时commit indexingqueue(如果是事物提交或者添加index的时候,同时,这个indexingqueue是一个非常怪的设计)
显然这个方法是用来执行Action的,DeleteNode只是Action之一,所以要理解整个体系,我们得先了解了解Action接口到底有多少个实现类:
从图中可以看到Action有很多的实现类,不过ahuaxuan已经在图中作了一些概要的说明以帮助我们理解这些实现类的作用,而且DeleteNode和AddNode类已经出现在我们要分析的范围之内了。
在了解了Action之后,我们需要正视我们在文章开始提出的3个问题了
1 如何把document从index删除
首先,看看DeleteNode里面的逻辑:从上图中我们可以确切的知道DeleteNode就是用来把一个Node从index删除的action。它的主要逻辑都集中在execute方法中,那么下面我们来看看DeleteNode的execute方法(其中包含了ahuaxuan的一些注释和原有的注释,可以帮助我们快速理解它的功能):
public void execute(MultiIndex index) throws IOException { //一个deleteNode代表了一个需要被删除的Node,它持有这个//node的uuid String uuidString = uuid.toString(); // check if indexing queue is still working on // this node from a previous update //根据uuid把document从indexingqueue中删除 Document doc = index.indexingQueue.removeDocument(uuidString); if (doc != null) { Util.disposeDocument(doc); } Term idTerm = new Term(FieldNames.UUID, uuidString); // if the document cannot be deleted from the volatile index // delete it from one of the persistent indexes. //同时也要从document内存队列或者内存索引中删除,如果存在于volatieindex的document内存队列或者内存索引中,则表示不存在 于persistentindex中,反之亦然。 int num = index.volatileIndex.removeDocument(idTerm); if (num == 0) { for (int i = index.indexes.size() - 1; i >= 0; i--) { //不存在于内存索引中,所以从注册过的persistentindex中删除 // only look in registered indexes PersistentIndex idx = (PersistentIndex) index.indexes.get(i); if (index.indexNames.contains(idx.getName())) { num = idx.removeDocument(idTerm); if (num > 0) { return; } } } } }
这段方法的主要逻辑是从indexingqueue中删除node对应的document,并从volatileindex对象(或者indexfile)中也删除对应的document,最后从persistentindex类中删除对应的document,所以这里有几件事情值得我们注意:
1. indexingqueue中有可能保存着一个node对应的document,那么这个indexingqueue是做什么用的?
Indexingqueue中的document其实是那些需要的extract的node,因为有些node包含的二级制文件,比如pdf,提炼文本的时候需要点时间,于是,jackrabbit就把提炼的过程给弄成异步的了,提炼完成之后,会把这个document放到indexingqueue中。所以当一个document需要删除的时候,肯定要检查这个异步的队列。
2. Volatileindex是个什么玩意,内存索引,即把索引存放在memory中
但是这里有点蹊跷的地方,需要我们注意一下,我们看看它逻辑:
int removeDocument(Term idTerm) throws IOException { Document doc = (Document) pending.remove(idTerm.text()); int num; if (doc != null) { Util.disposeDocument(doc); // pending document has been removed num = 1; } else { // remove document from index num = super.getIndexReader().deleteDocuments(idTerm); } numDocs -= num; return num; }
这个方法在DeleteNode的execute方法中被调用过了,从这个方法来看,这里面有两件事情值得我们关注,一个是volatileindex类中有一个pending队列,放着document,经查,当这个队列超过一定长度的时候,document中的数据就会被写成二进制索引数据放到内存中。
另外一个是如果pending中不存在这个document,那么就会调用indexreader来删除ramdirectory中符合条件的document。
也就是不管怎么做,volatile都是在操作内存,它有一个document队列,当队列长度超过10的时候,这些document就会被转换成index的二进制数据放到ramdirectory。
看到这里,童鞋们应该会问一个问题,就是内存中的index数据怎么写到磁盘中呢?后面我们会看到它有一个copy方法负责把内存中的数据copy到磁盘上。
3. Persistentindex是什么玩意,持久化索引,把索引存放在持久化介质中,目前是local file system。而且看上去PersistentIndex有很多个。不同的persistentindex会使用不同的directory。这种设计是不是告诉我们,这些不同的fsdirectory在某个固定的点是需要合并的呢?带着两个疑问,我们可以继续往下看。
总结成一句话就是,一个document可能存在于多个地方,当删除一个node时,所有的这些地方都需要清扫一遍。
我们来看看第二个大问题:
2 如何把document放到index中
如果DeleteNode一样,AddNode的逻辑也在它的execute方法中,既然如此,我们不妨过去看看:
public void execute(MultiIndex index) throws IOException { if (doc == null) { try { //如果doc为null再次创建document,创建document的流程之前已经讲得很清楚了。 doc = index.createDocument(new NodeId(uuid)); } catch (RepositoryException e) { // node does not exist anymore log.debug(e.getMessage()); } } if (doc != null) { index.volatileIndex.addDocuments(new Document[]{doc}); } }
这个方法简单得不能再简单,简单得让人无法置信,呵呵,不要被假象所迷惑。我们来看看
volatileIndex.addDocuments这个方法,因为逻辑都在这个方法中:
void addDocuments(Document[] docs) throws IOException { for (int i = 0; i < docs.length; i++) { Document old = (Document) pending.put(docs[i].get(FieldNames.UUID), docs[i]); //这里的pending就是volatileindex中那个document内存队列 if (old != null) { Util.disposeDocument(old); } //如果队列长度超过10(bufferSize),那么执行commitPending,也就是说逻辑又跑到commitPending中去了 if (pending.size() >= bufferSize) { commitPending(); } numDocs++; } invalidateSharedReader(); }
既然逻辑跑到commitPending中去了,我们就看commitPending,从名字上来看,commitPending就是把pending中的document处理掉:
private void commitPending() throws IOException { super.addDocuments((Document[]) pending.values().toArray( new Document[pending.size()])); //从这里可以看出,一旦pending被处理,那么就把pending置空。 pending.clear(); }
逻辑还不在这个方法中,那么我们继续追踪旅程,来看看这个方法AbstractIndex.addDocuments,在这个方法中,我们终于看到我们想看到的:
void addDocuments(Document[] docs) throws IOException { final IndexWriter writer = getIndexWriter(); //一般情况下,我们的docs数组的长度为10,下面就创建10个线程 DynamicPooledExecutor.Command commands[] = new DynamicPooledExecutor.Command[docs.length]; for (int i = 0; i < docs.length; i++) { // check if text extractor completed its work final Document doc = getFinishedDocument(docs[i]); // create a command for inverting the document commands[i] = new DynamicPooledExecutor.Command() { public Object call() throws Exception { long time = System.currentTimeMillis(); //每个线程都使用往writer里加入document对象,这个时候,lucene开始解析document,并生产index数据 writer.addDocument(doc); return new Long(System.currentTimeMillis() - time); } }; } //并发执行 DynamicPooledExecutor.Result results[] = EXECUTOR.executeAndWait(commands); //置空readOnlyReader和sharedReader,为啥置空啊,index数据改了呗 invalidateSharedReader(); IOException ex = null; //检查每个线程的执行情况,有一个出错就抛出异常,其他的异常保存到log中 for (int i = 0; i < results.length; i++) { if (results[i].getException() != null) { Throwable cause = results[i].getException().getCause(); if (ex == null) { // only throw the first exception if (cause instanceof IOException) { ex = (IOException) cause; } else { IOException e = new IOException(); e.initCause(cause); ex = e; } } else { // all others are logged log.warn("Exception while inverting document", cause); } } else { log.debug("Inverted document in {} ms", results[i].get()); } } if (ex != null) { throw ex; } }
在看过方法中的注释,大家应该都明白了,在AddNode方法中执行的操作其实就是在内存中生成index数据的操作。而且这个操作是并发执行的。
那么我们内存中的index数据就不需要写到磁盘上了吗,可能吗,要回答这个问题我们回到MultiIndex#update方法:
executeAndLog(new AddNode(transactionId, doc)); // commit volatile index if needed flush |= checkVolatileCommit();
我们看到这里有一个checkVolatileCommit,就它了,从名字上看,它Y滴就是想把内存中的数据刷到磁盘上,进去看看呗:
private boolean checkVolatileCommit() throws IOException { if (volatileIndex.getNumDocuments() >= handler.getMinMergeDocs()) { commitVolatileIndex(); return true; } return false; }
果然,volatileIndex.getNumDocuments()会返回它处理的document的数量,而handler.getMinMergeDocs()的默认值是100,也就是说当内存中的index数据对应的document超过100的话,就需要做commitVolatileIndex操作,而且返回true,否则就是返回false,这个true或false非常重要,因为它决定着下面的flush操作是否需要操作,而flush就是我们后面要提到的第3个大问题,如果你已经忘记有flush这回事,请看看文章开头提到的3个大问题。那么,到这里理论上来讲内存中的数据现在就需要被刷到磁盘上,ahuaxuan之前也是这么想滴,但是jackrabbit往往出乎人的意料,那么我们一起进去看看(请注意ahuaxuan的注释):
private void commitVolatileIndex() throws IOException { // check if volatile index contains documents at all if (volatileIndex.getNumDocuments() > 0) { long time = System.currentTimeMillis(); // create index /*这里创建的是一个Action的实现类CreateIndex,它的作用是创建一个persistentindex对象,很显然,创建的逻辑应该在它的execute方法中,如果你深入的看下去,会发现,这里的传进去的第二参数null,其实是表示创建一个新的persistentindex,而创建一个persistentindex意味着需要一个fsdiretory,创建一个fsdirectory又意味着需要一个目录,于是我们可以看到其实创建index directory的命名方法*/ CreateIndex create = new CreateIndex(getTransactionId(), null); executeAndLog(create); // commit volatile index /*从VolatileCommit这个名字上看来,这个action的主要操作就是把内存里的index数据写到刚创建的 persistentindex对象中,因为需要一个persistentindex的name作为构造参数,恰巧它是新建的这个 persistentindex对象,鉴于方法的重要性,ahuaxuan还是再多罗嗦一下,它的主要功能是把volatile中index的二进制数据拷贝到persistentindex,其实就是数据从ramdirectory向fsdirectory转移的过程 */ executeAndLog(new VolatileCommit(getTransactionId(), create.getIndexName())); // add new index /*这个AddIndex类也是非常重要的类,它存在的目的是什么呢?*/ AddIndex add = new AddIndex(getTransactionId(), create.getIndexName()); executeAndLog(add); // create new volatile index /*volatileindex 的使命到这里就结束了,下面就重新创建一个volatileindex吧,放弃过去,重新开始 */ resetVolatileIndex(); time = System.currentTimeMillis() - time; log.debug("Committed in-memory index in " + time + "ms."); } }
从代码和ahuaxuan的注释上看,要把内存中的index数据刷到磁盘上还真不是一件简单的事情。不过从这个过程中,我们得到一个重要的信息,最开始的时候,persistentindex中只包含了100个document的index数据。不过聪明的你一定已经看出点端倪,冥冥之中,好像在告诉我们,所有的这些persistentindex其实都缺少一个操作,它们需要合并index文件,这个操作和AddIndex息息相关,我们貌似对AddIndex的左右已经看出一点端倪了。
要解开这个谜团,非得到AddIndex中看看不可:
public void execute(MultiIndex index) throws IOException { PersistentIndex idx = index.getOrCreateIndex(indexName); /*index.indexNames是个什么东西?其实就是一个列表,任何一个新的persistentindex都需要被注册到这个列表中,它的作用很多,比如注册之后,只需要通过persistentindex类的名字就是可以取到对应的persistentindex,在上面的方法中并没有把新建的persistentindex注册到indexNames中去,所以这个方法先判断有没有注册,没有注册那么就注册进去,接着把这个 indexName传给了merger类,从名字上来看,这个merger的功能应该就是合并persistentindex的数据了*/ if (!index.indexNames.contains(indexName)) { index.indexNames.addName(indexName); // now that the index is in the active list let the merger know about it index.merger.indexAdded(indexName, idx.getNumDocuments()); } }
那么既然知道了有这么一个merger类,那不看看他做了点什么也过意不去啊。
看看它的类注释:Merges indexes in a separate deamon thread.
原来是一个deamon线程啊,那它一定是不停的在后台执行merge操作,而且理论上来讲,也应该有一个临界值,超过多少document被写到persistentindex的时候就执行merger操作。这里面的逻辑应该有很多值得我们学习的地方,抱着这样的想法,ahuaxuan决定把它的研究放到后面。
接下来,让我们说说flush这个操作,这是索引提交的第三部重要流程
To be continue
评论
发表评论
-
深入浅出jcr之16 该死的RMI,我们需要HTTP+简单RPC协议
2009-12-12 13:22 6708从这篇文 ... -
深入浅出jackrabbit之十五 文档提取优化2.docx
2009-10-22 18:38 3873/** *author:ahuaxuan *2009- ... -
深入浅出jackrabbit之十四 分布式文档提取
2009-09-24 12:20 4730/** *author:ahuaxuan *200 ... -
深入浅出jackrabbit之十三 查询之AST和QT
2009-09-10 10:12 3430简介:在前面的文章中 ... -
深入浅出jcr之十二 key-value存储系统
2009-08-26 09:31 3753作者:ahuaxuan 在写文章方面,惰性心理 ... -
深入浅出jcr之十一 jackrabbit改进要点
2009-08-18 18:22 3611作者,ahuaxuan 在看过前 ... -
深入浅出jcr之十 redolog 和 recovery.docx
2009-08-18 18:14 2141作者:ahuaxuan 在前面的 ... -
深入浅出 jackrabbit 九 索引合并(下)
2009-07-22 14:16 2084在上文中,ahuaxuan讲到了索引创建的主体流程,但是索引合 ... -
深入浅出 jackrabbit 八 索引合并(上)
2009-07-21 17:32 2538我们从文本提取的逻辑中走出来,回到主体流程。 在前面的文 ... -
深入浅出 jackrabbit 七 文本提取(下)
2009-07-21 17:29 2519接上文,说到文本提取,在上一篇文章中,我们是管中窥豹,并没有把 ... -
深入浅出 jackrabbit 六 文本提取(上)
2009-07-21 17:27 3310用lucene作过索引的同 ... -
深入浅出 jackrabbit 之五 索引提交(下)
2009-07-14 17:53 2251接上文,在上面一篇文章中,我们谈到了update中的Delet ... -
深入浅出 jackrabbit 3 创建 document
2009-07-01 13:03 4301/** *作者:ahuaxuan 张荣华 *日期:2009-0 ... -
深入浅出 jackrabbit 2 索引概览
2009-06-30 08:51 5371任何一个数据库都离不 ... -
深入浅出 jackrabbit 十 查询概览
2009-06-20 10:29 6292/** *author: ahuaxuan *date: ... -
深入浅出 jackrabbit 1
2009-05-19 18:31 12469/** * author:ahuaxuan( ...
相关推荐
《深入浅出 Jackrabbit 1》 Jackrabbit 是一个开源的、实现了 Java Content Repository (JCR) API 的内容管理系统,它允许程序通过统一的方式访问、存储和管理各种数据,包括文本、图像、视频等多媒体信息。这篇...
这个“jackrabbit最全入门教程”将会带你深入理解这个强大的内容管理解决方案。 首先,我们需要了解什么是JCR。JCR提供了一种统一的方式来访问和管理数字内容,无论这些内容是文档、图像、视频还是其他形式的数据。...
JackRabbit学习参考资料总汇涉及了深入浅出的JackRabbit内容仓库API的学习,内容涉及多个专题,整个学习资料是PDF文档格式。从标签来看,这份资料主要涉及JackRabbit以及JCR(Java Content Repository)的内容仓库...
### Jackrabbit 在项目实施中的常见问题与解决方案 #### 一、Jackrabbit简介 Jackrabbit 是一个完全用 Java 编写的 JCR(Java Content Repository)实现,它可以作为一个独立的服务运行,也可以嵌入到更大的应用...
Apache Jackrabbit 是一个开源的Java Content Repository (JCR)实现,它是Content Management Systems (CMS)的核心技术之一。JCR是Java Specification Request (JSR) 170和JSR 283定义的标准,旨在提供一个统一的...
jackrabbit 1.5.6 jar
Apache Jackrabbit API 是一个强大的内容管理系统(CMS)的核心组件,它是Apache Software Foundation 开发的Java Content Repository (JCR) 的实现。JCR 是一个标准,它定义了一个用于存储、管理和检索结构化内容的...
Apache Jackrabbit 是一个...对于开发人员来说,理解这些功能以及如何将 Jackrabbit 集成到现有应用中是深入学习的关键部分。通过实践示例代码和探索 Jackrabbit API 文档,你可以逐步掌握这个强大的内容管理系统框架。
jackrabbit-standalone-1.6.5.jar是webDav的支持jar包。
Apache Jackrabbit是一个开源的、实现了Java Content ...总之,这个"Jackrabbit入门实例"是学习和探索JCR和Jackrabbit的好起点,它涵盖了基本的操作和概念,帮助你快速上手并深入了解这个强大的内容管理系统。
jackrabbit, 在amqplib上,简单的amqp/rabbitmq作业队列基于 node Jackrabbitnode.js 在不讨厌生命的情况下。producer.js:var jackrabbit = require('jackrabbit');var rabbit = jackrabbit(process
标题中的“查看jackrabbit仓库的小工具”指的是一个用于观察和管理Apache Jackrabbit仓库的实用程序。Jackrabbit是Java Content Repository (JCR) API的一个开源实现,它提供了一个内容管理系统(CMS)的基础框架,...
通过深入理解和使用"jackrabbit-webdav-2.3.2.src.zip"中的源代码,开发者不仅可以学习WebDAV协议的工作原理,还能了解如何在Android环境中实现高效稳定的WebDAV客户端功能。此外,对于想要对Jackrabbit进行定制化...
综上所述,Apache Jackrabbit 2.6 是一个强大而灵活的内容管理解决方案,它为开发者提供了丰富的功能和API,以构建高效、可扩展的CMS应用。其对JCR规范的支持以及与多种系统的兼容性,使其成为构建企业级内容管理...
jackrabbit-webdav-2.1.0.jar 具体用法可以网上查找
这两个项目将帮助我们深入理解和快速入门Jackrabbit的使用。 1. Jackrabbit核心概念: - JCR:JSR 170定义了内容存储的标准接口,使得应用程序可以透明地访问和操作不同类型的存储系统。 - Node:在JCR中,内容被...
jackrabbit开发用jar包,jackrabbit是基于Lucene的一种站内搜索技术,它用xml文件为他的元数据,自动穿件索引,使用xpath或者xquery的查询方法。
标题中的"jackrabbit-webdav-2.7.1.zip"指的是Apache Jackrabbit的一个特定版本——2.7.1的WebDAV模块的压缩包。Apache Jackrabbit是Java内容存储库(Content Repository)的一个实现,它遵循JCR(Java Content ...
杰克兔(Jackrabbit)是Apache软件基金会的一个开源项目,主要致力于实现Java Content Repository (JCR) 规范。这个规范定义了一种用于存储、管理和检索结构化内容的标准API。在给定的压缩包"jackrabbit-jcr-commons...