`
suichangkele
  • 浏览: 200287 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

solr中的cache的实现原理

    博客分类:
  • solr
阅读更多

先说一下,我使用的版本是5.5.3

 

搭建过solr的人肯定对solrconf.xml不陌生,在<query></query>中有多个cache,比如filterCache、queryResultCache,documentCache。这个博客就是介绍这三个cache的意思、配置以及他们的使用。

我们直接看代码,对于这三个cache的使用是在solrIndexSearcher中,他有下面的属性

  private final boolean cachingEnabled;//这个indexSearcher是否使用缓存

  private final SolrCache<Query,DocSet> filterCache;对应于filterCache

  private final SolrCache<QueryResultKey,DocList> queryResultCache;//对应于queryResultCache

  private final SolrCache<Integer,Document> documentCache;//对应于documentCache

  private final SolrCache<String,UnInvertedField> fieldValueCache;//这个稍后再说

 

在SolrIndexSearcher的构造方法中可以发现对上面的几个cache的赋值:

 if (cachingEnabled) {//默认是启用缓存的,即进入if
      ArrayList<SolrCache> clist = new ArrayList<>();
      fieldValueCache = solrConfig.fieldValueCacheConfig==null ? null : solrConfig.fieldValueCacheConfig.newInstance();//如果配置文件中存在fieldValueCache则创建fieldValueCache
      if (fieldValueCache!=null) clist.add(fieldValueCache);
      filterCache= solrConfig.filterCacheConfig==null ? null : solrConfig.filterCacheConfig.newInstance();//如果存在配置文件创建filterCache
      if (filterCache!=null) clist.add(filterCache);
      queryResultCache = solrConfig.queryResultCacheConfig==null ? null : solrConfig.queryResultCacheConfig.newInstance();//如果存在配置文件创建queryResultCache
      if (queryResultCache!=null) clist.add(queryResultCache);
      documentCache = solrConfig.documentCacheConfig==null ? null : solrConfig.documentCacheConfig.newInstance();//如果存在配置文件创建documentCache
      if (documentCache!=null) clist.add(documentCache);

      if (solrConfig.userCacheConfigs == null) {//可以发现还可以创建一个叫做userCacheConfig的cache,我自己没有试验
        cacheMap = noGenericCaches;
      } else {
        cacheMap = new HashMap<>(solrConfig.userCacheConfigs.length);
        for (CacheConfig userCacheConfig : solrConfig.userCacheConfigs) {
          SolrCache cache = null;
          if (userCacheConfig != null) cache = userCacheConfig.newInstance();
          if (cache != null) {
            cacheMap.put(cache.name(), cache);
            clist.add(cache);
          }
        }
      }

      cacheList = clist.toArray(new SolrCache[clist.size()]);
    }

 通过上面的代码可以发现,如果在solrconf.xml中配置了对应的cache,就会在solrIndexSearcher中创建对应的cache。

 

solrconf.xml中cache的实现原理:

我们以<filterCache class="solr.FastLRUCache"  size="512"   initialSize="512"  autowarmCount="0"/>这个为例:创建这个cache的实现类是FastLRUCache,他的实现原理就是封装了concurrentHashMap,最大可以存放512个缓存的key,初始大小为512个,autoWarmCount这个稍后再说。在solr中默认有两个cache,一个是刚才说的FastLRUCache,还有一个是LRUCache,他的实现原理是LinkedHashMap+同步,很明显这个的性能要比前一个要差一些,所以可以将LRUCache都换为FastLRuCache。不过这两个cahce都是基于lru算法的,貌似也不适合我们的需求,最好是lfu的,所以可以通过改变这些配置,使用一个基于lfu算法的cache,当然这个不是这篇博客的内容。我们先看一下这个FastLRUCache的实现:

 1、初始化cache的方法:

public Object init(Map args, Object persistence, CacheRegenerator regenerator) { // map即我们在配置文件中写的那些属性的封装,比如size=512
    super.init(args, regenerator);//在这个方法中就做两个事,一个是调用它的名字,即这个cache的名字,通过调用name()方法可以获得这个名字,第二个获得指定的warmCount,可以是百分数,也可以是具体的数字(warmCount先不用操心)以后会介绍
    String str = (String) args.get("size");//缓存的大小,
    int limit = str == null ? 1024 : Integer.parseInt(str);//默认是1024个
    int minLimit;
    str = (String) args.get("minSize");//缓存的最小值
    if (str == null) {
      minLimit = (int) (limit * 0.9);
    } else {
      minLimit = Integer.parseInt(str);
    }
    if (minLimit==0) minLimit=1;
    if (limit <= minLimit) limit=minLimit+1;

    int acceptableLimit;
    str = (String) args.get("acceptableSize");//缓存在清理之后剩余的可以接受的数量,默认是最大值的95%
    if (str == null) {
      acceptableLimit = (int) (limit * 0.95);
    } else {
      acceptableLimit = Integer.parseInt(str);
    }
    // acceptable limit should be somewhere between minLimit and limit
    acceptableLimit = Math.max(minLimit, acceptableLimit);

    str = (String) args.get("initialSize");
    final int initialSize = str == null ? limit : Integer.parseInt(str);//初始值,即创建的ConcurrentHashMAP的初始值。
    str = (String) args.get("cleanupThread");//这个是用来说明当缓存的数量太大要进行驱逐的时候要不要新生成一个thread还是使用添加缓存的thread。
    boolean newThread = str == null ? false : Boolean.parseBoolean(str);//默认是false,即使用添加缓存的thread。这个最好设置为true

    str = (String) args.get("showItems");
    showItems = str == null ? 0 : Integer.parseInt(str);//showItems是以后做统计的时候用到的,下面的关于统计的方法中有介绍
    description = generateDescription(limit, initialSize, minLimit, acceptableLimit, newThread);//这个是用来产生一个描述性质的字符串,没啥用
    cache = new ConcurrentLRUCache<>(limit, minLimit, acceptableLimit, initialSize, newThread, false, null);//根据使用的参数来创建真正的缓存对象。创建的是一个ConcurrentLRUCache对象。
    cache.setAlive(false);
  }

 在FastLruCache中还有一些put,get,clear这些显而易见的方法(对生成的ConcurrentLRUCache对象操作),另外还有一个warm方法比较重要,我专门在一篇博客中写他的作用

 

接下来我们进入到ConcurrentLRUCache类中,看看他的实现。

 public ConcurrentLRUCache(int upperWaterMark, final int lowerWaterMark, int acceptableWatermark,
                            int initialSize, boolean runCleanupThread, boolean runNewThreadForCleanup,
                            EvictionListener<K,V> evictionListener) {
    if (upperWaterMark < 1) throw new IllegalArgumentException("upperWaterMark must be > 0");
    if (lowerWaterMark >= upperWaterMark)
      throw new IllegalArgumentException("lowerWaterMark must be  < upperWaterMark");
    map = new ConcurrentHashMap<>(initialSize);//最终的缓存使用的就是一个ConcurrentHashMap,在配置文件中指定他的初始化大小。
    newThreadForCleanup = runNewThreadForCleanup;//如果添加一个cahceh后缓存满了,要重新运行一个线程做缓存驱逐
    this.upperWaterMark = upperWaterMark;//缓存的最大值
    this.lowerWaterMark = lowerWaterMark;//缓存的最小值,这个乍听上去没有什么用,其实他是配置做驱逐用的
    this.acceptableWaterMark = acceptableWatermark;//
    this.evictionListener = evictionListener;//这个默认就是null。
    if (runCleanupThread) {//一直运行一个线程做缓存驱逐,最好是采用这个配置,这样不影响前台的搜索,否则可能会导致某个搜索变得很慢。
      cleanupThread = new CleanupThread(this);
      cleanupThread.start();
    }
  }

 再看一下他的添加方法:

 @Override
  public V put(K key, V val) {//put方法
    if (val == null) return null;
    CacheEntry<K,V> e = new CacheEntry<>(key, val, stats.accessCounter.incrementAndGet());//将参数封装
    CacheEntry<K,V> oldCacheEntry = map.put(key, e);//去的原来的值
    int currentSize;
    if (oldCacheEntry == null) {//如果原来没有值则缓存数量增加1,
      currentSize = stats.size.incrementAndGet();//stats是用来描述这个缓存的使用情况的,比如命中数,未命中数,使用的次数,size属性是一个AtomicInteger
    } else {
      currentSize = stats.size.get();
    }
    if (islive) {//这个概念可以先不用管。(这里的isLive是在这个caceh所属的SolrIndexSearcher注册之后真正提供搜索服务的searcher后才会成为true,即等他真正成为要使用的searcher之后才会记录统计,因为可能我们在listener中可能调用一个新创建的searcher,这个时候这个searcher并没有成为
     //提供服务的sarcher,此时要记录为nonLive,这里提到的listener在后面的博客中会有介绍。)
      stats.putCounter.incrementAndGet();//这里的stats是用于做统计的,比如命中数、未命中数等
    } else {
      stats.nonLivePutCounter.incrementAndGet();
    }
    //如果缓存的大小超过上限,则要进行驱逐,根据配置要使用三种驱逐的手段,
    if (currentSize > upperWaterMark && !isCleaning) {
      if (newThreadForCleanup) {//临时建立线程进行markAndSweep(驱逐),很差
        new Thread() {
          @Override
          public void run() {
            markAndSweep();
          }
        }.start();
      } else if (cleanupThread != null){//一直维护一个线程,最好
        cleanupThread.wakeThread();
      } else {
        markAndSweep();//使用当前的线程进行操作,不是很好,尤其是当缓存很多的时候
      }
    }
    return oldCacheEntry == null ? null : oldCacheEntry.value;
  }

 看到这里就明白了solr自带的缓存的实现原理了。(markAndSweep方法我没有完全看懂,不过不影响我们的理解)

 

在缓存中还有一个重要的方法是获得这个缓存的使用情况: public NamedList getStatistics() 方法,返回一个类似于map的结构,我们看看FastLRUCache的代码:

  public NamedList getStatistics() {
    NamedList<Serializable> lst = new SimpleOrderedMap<>();
    if (cache == null)  return lst;
    ConcurrentLRUCache.Stats stats = cache.getStats();//这里的stats就是用来记录缓存使用的情况的,比如大小,添加次数,访问次数、查询未命中次数,驱逐次数。
    long lookups = stats.getCumulativeLookups();//这个是查询总的次数,包括命中的次数+未命中的次数
    long hits = stats.getCumulativeHits();//查询的有效命中次数
    long inserts = stats.getCumulativePuts();//添加缓存的次数
    long evictions = stats.getCumulativeEvictions();//累计的驱逐次数
    long size = stats.getCurrentSize();//大小
    long clookups = 0;
    long chits = 0;
    long cinserts = 0;
    long cevictions = 0;

    // NOTE: It is safe to iterate on a CopyOnWriteArrayList
    for (ConcurrentLRUCache.Stats statistiscs : statsList) {//这个是对于多个SolrIndexSearcher之间的统计,不过现在我做测试发现并没有开启,也就是统计的还是一个SolrIndexSearcher生存期间的缓存使用情况,
      clookups += statistiscs.getCumulativeLookups();
      chits += statistiscs.getCumulativeHits();
      cinserts += statistiscs.getCumulativePuts();
      cevictions += statistiscs.getCumulativeEvictions();
    }

    lst.add("lookups", lookups);//返回的结果包括这些:
    lst.add("hits", hits);
    lst.add("hitratio", calcHitRatio(lookups, hits));
    lst.add("inserts", inserts);
    lst.add("evictions", evictions);
    lst.add("size", size);

    lst.add("warmupTime", warmupTime);
    lst.add("cumulative_lookups", clookups);
    lst.add("cumulative_hits", chits);
    lst.add("cumulative_hitratio", calcHitRatio(clookups, chits));
    lst.add("cumulative_inserts", cinserts);
    lst.add("cumulative_evictions", cevictions);

    if (showItems != 0) {//showItem的意思是将多少个缓存的key展示出来,展示最近搜索的,
      Map items = cache.getLatestAccessedItems( showItems == -1 ? Integer.MAX_VALUE : showItems );
      for (Map.Entry e : (Set <Map.Entry>)items.entrySet()) {
        Object k = e.getKey();
        Object v = e.getValue();

        String ks = "item_" + k;
        String vs = v.toString();
        lst.add(ks,vs);
      }
      
    }

    return lst;
  }

 关于这个统计的获取在后面关于solr的SolrEventListener(监听SolrIndexSearcher的变化的监听器)中会有介绍。

 

 

分享到:
评论

相关推荐

    solr中cache综述

    Solr提供了两种主要的`SolrCache`接口实现类,即`solr.search.LRUCache`和`solr.search.FastLRUCache`。 1. **solr.search.LRUCache**:基于LRU(Least Recently Used,最近最少使用)算法实现,适用于需要根据访问...

    solr教材-PDF版

    - **1.3.1 索引**:Solr使用倒排索引技术,将文档中的词汇映射到包含这些词汇的文档ID列表上,从而实现高效的搜索。 - **1.3.2 搜索**:用户提交查询请求后,Solr会解析查询语句,查找相应的索引,并返回匹配的文档...

    SOLR的应用教程

    1.3 Solr服务原理 1.3.1 索引 索引是Solr的核心功能,它将数据转化为倒排索引,以快速响应查询请求。索引过程包括分析、存储和建立倒排索引。 1.3.2 搜索 搜索是通过查询解析器和评分函数实现的,查询结果按...

    apache solr 源文件 3.6.1

    这些缓存在`Cache`接口和相关实现类中定义。 10. **插件系统**:Solr允许用户自定义查询解析器、过滤器、请求处理器等,源代码中有很多插件实现的例子。 通过研究Apache Solr 3.6.1的源代码,不仅可以深入了解其...

    solr搜索引擎支持分页

    在Solr中,分页主要通过`start`和`rows`参数来实现。`start`参数定义了从哪个文档开始返回结果,`rows`参数指定了返回结果的数量。例如,如果你想获取第一页的10条结果,可以设置`start=0`和`rows=10`。第二页则将`...

    solr-4.9.1

    2. **文档**:这个压缩包中的文档部分提供了详细的使用指南、API参考、安装教程等,帮助用户理解Solr的工作原理和如何操作。这些文档通常包含`docs`目录下的HTML文件,对于初学者来说是极好的学习资源。 3. **Jar包...

    Solr.学习文档

    - **实现原理**: - Solr 通过在底层文件系统层面共享相同的数据文件来实现索引共享。 - 这种方法尤其适用于那些需要维护多个相似或相同内容的索引的情况。 - **应用场景**: - 多个应用程序或环境需要访问相同...

    开源企业搜索引擎SOLR的应用教程

    **1.3 Solr服务原理** - **1.3.1 索引**:索引是Solr处理数据的基础,所有数据在存储之前都会被转换成索引格式。索引过程包括解析文档、提取字段、建立索引等步骤。 - **1.3.2 搜索**:用户通过提交查询请求到Solr...

    开源企业搜索引擎SOLR的 应用教程

    **1.3 Solr服务原理** - **1.3.1 索引** 索引是Solr工作的基础。当新的文档添加到索引中时,Solr会对其进行分析、提取关键词并存储这些信息。索引过程可以通过API调用或批量导入等方式完成。 - **1.3.2 搜索** ...

    Solrj 中文教程

    - **3.1.1 Solr的应用模式**:介绍Solr在实际项目中的常见部署模式。 - **3.1.2 SOLR的使用过程说明**:从索引建立到搜索结果呈现的整个流程。 ##### 3.2 一个简单的例子 - **3.2.1 SolrSchema设计**:为示例项目...

    Lucene原理

    它提供了一套高效、可扩展的搜索框架,使得开发者能够快速地在大量文本数据中实现高效的全文搜索功能。Lucene的核心概念包括索引、分词、倒排索引以及查询解析等。 1. **索引过程** - **分词(Tokenization)**:...

    如何将Lucene索引写入Hadoop?

    6. **性能调优**:在实际应用中,还需要考虑如BlockCache、Shard配置、索引压缩等因素,以优化索引的读取速度和降低存储成本。 总之,将Lucene索引写入Hadoop是一项复杂但必要的任务,它允许我们在大数据环境中实现...

    Lucene实战源码(Lucene in Action Source Code)part1

    在实际应用中,Lucene通常与其他技术结合使用,例如Solr或Elasticsearch,它们提供了更高级的功能,如分布式搜索、集群管理和RESTful API。这部分源码可能未涵盖这些,但对理解Lucene的基本工作原理和内部机制非常有...

    lucene_构建一个简单的WEB搜索程序

    然而,这段代码片段没有给出完整内容,所以具体实现可能包含检查当前页面是否为主页面,如果是,那么刷新页面或重定向以避免显示旧的缓存内容。 为了防止浏览器缓存,你可能需要在响应头中添加以下内容: ```jsp...

    dailyfresh.zip

    项目还涉及到了搜索功能的实现,这通常需要集成如Elasticsearch或Solr这样的搜索引擎,它们能提供高效的全文检索和复杂的分析功能,为用户提供更加智能的搜索体验。在Web应用中,Session管理和Cache管理也是必不可少...

    Lucene5学习之Facet(续)

    6. **源码分析**:对于熟悉Java和Lucene源码的开发者来说,深入理解Facet模块的实现原理可以帮助我们更好地定制和优化Facet功能。例如,研究`FacetsAccumulator`和`FacetResult`等关键类的源码,可以了解Facet统计的...

    Lucene:基于Java的全文检索引擎简介.rar

    Lucene的主要目标是为开发者提供一个简单易用的API,让他们能够快速地在应用程序中实现全文检索功能。 **一、Lucene的核心组件** 1. **索引(Indexing)**:Lucene首先将非结构化的文本数据转换为倒排索引...

    hbase是什么共24页.pdf.zip

    4. 高性能:通过MemStore缓存和BlockCache机制,HBase能够实现快速读写操作,尤其适用于实时查询场景。 5. 扩展性:HBase的设计允许横向扩展,只需添加更多的服务器到集群,即可提高存储容量和处理能力。 6. 支持...

    manual 技术手册

    《manual 技术手册》是针对某个特定技术或工具的手册,尽管具体的内容没有在...由于 Drupal 6 已经不再维护,现在的开发可能更多地转向了更新的版本,如Drupal 8和9,但理解老版本的基本原理仍然对理解新版本有所帮助。

Global site tag (gtag.js) - Google Analytics