lucene FieldCache 内部细节和使用方式分享

来源:互联网 发布:iphone6s没有4g网络 编辑:程序博客网 时间:2024/05/20 16:42

以下主要以使用的lucene 3.5为例:

 

一,FieldCache是什么?

FieldCache其实就是一个字段域值的一个缓存,外层是一些Map,它的底层数据结构就是一个数组,数组的下标就是docId, 值就是docId对应的Field value.可以快速完成docIdField value的映射。

 

二,FieldCache怎么使用以及使用场景是什么?

    1)使用方式:

比如Field的数据类型为Int,那么就可以通过如下方式来使用:

int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field);

或者

int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field, IntParser parser)

或者

int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field, IntParser parser, boolean setDocsWithField)

第一个参数就是IndexReader,第二个参数就是field的名称,第三个参数parser,它可以把值转换成对于的数据类型,第四个参数setDocsWithField是否记录存在这个FielddocID

2)使用场景:

   其实使用场景有很多,在我们的代码里可以随时使用,这里举例几种比较典型的使用场景

   1,用SortField排序时,Lunene会使用这个字段对应的FieldCache

   2solr在做facet时也会使用

   3,在自定义的Collector里,使用详情可以参考mandy内部的YHDCollector

 

三,FieldCache的内部实现细节

1,从结构层次上看,FieldCacheImpl是一个三层的Map实现。

   a , 第一层Map是一个HashMap,根据数据类型来划分,有9种类型的cache,根据字段类型不同选择不同的cache.  Key为数据类型,valueCache类的子类实例, Cache类里包含了第二层的Map

   private synchronized void init() {

      caches = new HashMap<Class<?>,Cache>(9);

      caches.put(Byte.TYPE, new ByteCache(this));

      caches.put(Short.TYPE, new ShortCache(this));

      caches.put(Integer.TYPE, new IntCache(this));

      caches.put(Float.TYPE, new FloatCache(this));

      caches.put(Long.TYPE, new LongCache(this));

      caches.put(Double.TYPE, new DoubleCache(this));

      caches.put(String.class, new StringCache(this));

      caches.put(StringIndex.class, new StringIndexCache(this));

      caches.put(DocsWithFieldCache.class, new DocsWithFieldCache(this));

    }

b, 第二层的Map是一个WeakHashMap,按照IndexReader来划分,不同的IndexReader实例对应不同的cache,keyIndexReader实例, value为第三层的map

 

c,第三层的Map是一个HashMap,按照字段来划分,以Entrykey, EntryFieldNameparser组成,value就是一个数组或者FixedBitSet

    

2, 解析实现细节:

1)先来看一下Cache类的定义,主要包含这几个部分:

  A, final Map<Object,Map<Entry,Object>>readerCache = new WeakHashMap<Object,Map<Entry,Object>>();

内部定义一个WeakHashMap,这个作为第二层的map,以IndexReaderkey,以第三层的hashMap作为value.

             B,protectedabstract Object createValue(IndexReader reader, Entry key,boolean setDocsWithField)

        throws IOException;

                一个抽象方法,9种不同数据类型的子类cache都必须实现这个方法,用于获取真正的Field value,来填充字段对应的数组或者FixedBitSet.

    C, put(IndexReader reader, Entry key, Object value)

       用于构建整个Cache体系,创建第三层的innerCache,然后把参数keyvalue放入innerCache中,最后把参数readerinnerCache放入到第二层的weakHashMap

             Dget(IndexReader reader, Entry key, boolean setDocsWithField)

                 根据readerEntry(entryfieldNameparser组成)来获取field对应的数组或者FixedBitSet如果不存在,通过调用子类的实现的createValue方法获取,并放入各层map中。setDocsWithField是否记录存在这个FielddocID集合

           Epurge(IndexReader r) 如果IndexReader已经完成或者关闭了,就会调用该方法,用于清理weakHashMapIndexReader对应的value

        

2) 看一下Cache抽象方法createValue(IndexReader reader, Entry key,boolean setDocsWithField)在子类中的实现,以子类IntCache为例:

            finalint maxDoc = reader.maxDoc(); //获取reader对应的maxDoc,这个用于定义数组的长度或者FixedBitSet的大小

              TermDocs termDocs = reader.termDocs();//获取reader对应termDocs,不断循环迭代获取所有的docId

              TermEnum termEnum = reader.terms (new Term (field));//获取reader对应的TermEnum,不断循环迭代获取所有的field的值

              通过不断的循环迭代,构建出最后填充好的数组或者FixedBitSet,完成docIdField value的映射.

         

3) 看一下parser, 如果是一个数字类型的Field,使用时要注意,举例来说,  

如果采用老的方式,doc.add(new Field("stock_"+i,, Integer.toString(product.getStock()), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));

加载FieldCache时就用DEFAULT_INT_PARSERinBrandsValues = FieldCache.DEFAULT.getInts(reader, "brand_id",FieldCache.DEFAULT_INT_PARSER, missingValue != null);

如果是用doc.add(new NumericField("stock_"+i, Field.Store.NO, true).setIntValue(0));  加载时FieldCache如果使用DEFAULT_INT_PARSER就会报错,

必须使用FieldCache.NUMERIC_UTILS_INT_PARSER,因为NumericField 在索引的时候是经过特殊编码的。或者直接使用FieldCache.DEFAULT.getInts(reader, "brand_id"),不加parser.

原因如下,在createValue方法中有如下代码,如果没有传入parser,那么默认采用DEFAULT_INT_PARSER,发生NumberFormatException异常后会采用NUMERIC_UTILS_INT_PARSER.

               if (parser ==null) {

                   try {

                      returnwrapper.getInts(reader, field, DEFAULT_INT_PARSER, setDocsWithField);

                   } catch (NumberFormatException ne) {

                      returnwrapper.getInts(reader, field,NUMERIC_UTILS_INT_PARSER, setDocsWithField);

                   }

}

 

建议我们在加载FieldCache时,可以直接使用FieldCache.DEFAULT.getInts(reader, "brand_id")方式,不要传任何的parser,肯定不会报错。

 

4) 第二层Map是一个的WeakHashMap,内部实现采用的是WeakReference,这并不会阻止垃圾回收器对其内部数据的回收,垃圾回收器可能在任何时候对其数据进行回收,如果回收掉了,

下次再使用时就必须重新加载,Lucene采用WeakHashMap也是考虑到内存占用的问题。其实FieldCache加载时需要遍历所有的doc,有一定的性能消耗,需要做一个权衡。

 

        5) 再来看一下FixedBitSet

          FixedBitSet是一个固定长度为reader.maxDocBitSet,非常的节省空间,相当于同样大小的int数组的1/32,上面第9cacheDocsWithFieldCache,内部就是使用了fixedBitSet,在有两种情况可以使用:

          1,如果我们并不需要直接使用和获取FieldCacheField value,只是需要判断doc是否存在这个字段时,可以采用FixedBitSet.

             public Bits getDocsWithField(IndexReader reader, String field)

             例如判断有无库存时,是可以采用这种方式的。

          2,在使用SortField排序时,如果设置了missingValue,内部实现也是一个FixedBitSet.

SortField stockSort = new SortField("stock_" +req.getProvinceId(),SortField.INT,true).setMissingValue(1)

意思是如果doc没有这个字段,采用默认值1参与排序。

 

四,使用过程中注意点:

1FieldCache的字段必须是被索引的,是否保存没有关系。

2FieldCache的字段必须是单值域,也就意味着创建Field必须选择Field.Index.NOT_ANALYZED或者Field.Index.NOT_ANALYZED_NO_NORMS

    如果选择的是Field.Index.ANALYZED或者Field.Index.ANALYZED_NO_NORMS,这个Field value有可能被分词成多个Term

     FieldCache加载时只会加载每个Field value分词之后的最后那个Term,这个时候加载的数据是不正确的。有的Field虽然不可能被分词,但是尽量还是不要选择

     Field.Index.ANALYZED或者Field.Index.ANALYZED_NO_NORMS

3,如果字段是存在多个同名域,加载FieldCache也会不正确。

     doc.add(new Field("id", String.valueOf(i), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));

    doc.add(new Field("id", String.valueOf(i+1), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));

FieldCache加载的是同名域的后面那个Fieldvalue.

4lucene4.2以下版本加载FieldCache时,尽量避免传入不存在的FieldName,因为即使传入一个不存在的Field,也会创建一个数组,占用同样多的内存。

5,调用FieldCache,不要传入最外层的IndexReader,最好传入都是SegmentReader,如果既有外层的IndexReader被传入,又有内层的SegmentReader被传入,相当于占用了两份内存。

   1)在CollectorFieldComparator中类存在下面的方法,这个方法里的参数是SegmentReader,可以在这个方法里加载需要的FieldCache.

setNextReader(IndexReader reader, int docBase)

   2) 如果拿到的是外层的IndexReader的话,可以先获取内部的subReaders,然后判断是否为SegmentReader,直到获取到SegmentReader,再加载FieldCache.

 

 

Lucene4.2及以上版本,FieldCache实现大体是一样的,增加了从docValues中获取FieldCache,对不存在的FieldName做了特殊处理等等!

0 0
原创粉丝点击