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

合理设置Solr Schema防止出现OOM

    博客分类:
  • solr
 
阅读更多

背景:

      晚上收到报警,说线上的一个solr的collection挂掉了,赶紧打开远程服务起查看服务器的状态,果然业务方查询全部超时,增量更新也宕机了,从异常信息上来看是集群中没有可用的节点可以使用,看到这样的问题,第一想到的是要重启一下服务器。悲剧的是重启完服务器,服务只正常了15秒钟,转而又全部宕机。

     

      判断是VM的堆内存溢出了,看了一下虚拟机启动参数 -Xmx4400m -Xms4400m(服务器是8G内存)

,临时的解决方案是增加内存,设置为-Xmx6400m -Xms6400m,设置完成,迅速重启服务器之后,观察了一会,core节点果然是不会挂了,但,通过命令工具 jstat -gcutil 命令查看VM fullgc的状态,观察到Full GC的频率是比较高,只是服务器勉强不会挂罢了。

     

     同事告诉我他们团队刚刚上了一个新的查询需求,这个提示我是不是因为的查询里面设置了什么查询条件的原因,随即仔细查看了一下日志,果然在查询日志中观察到了有两个查询中带sort的参数,需要排序的字段在在schema field节点只是设置了indexed=true而没有设置docValues=true ,瞬间明白了为什么会OOM。

 

原因分析:

      究其原因是客户端查询请求中需要sort的字段在schema field定义中,开启了indexed=true ,但是没有开启docValue=true,这样solr在对命中结果集进行排序时候,会将docid对应的value预先加载内存中,如果一个core中文档数有2000w条的话,试想一个long字段类型的field,在内存中就需要2000w*8个字节的内存,大概152兆内存,而且这个内存块需要随着文档内容更新,频繁刷新,内存频繁OOM也可想而知了。

     在solr5.0开始,框架中引入了docvalue机制,按照我现在的理解,这种存贮格式有以下三个特点:

  1. 这始终列存储,所以通过docid取单列中的内容比基于行存储的document中的内容要要快上好多倍
  2. 存储中的内容在物理上是按序排列的,利用这个特性,在文档排序时,只需要通过docid取对应的所在存储上的偏移量offset,通过这个offset偏移量就能判断两个值的大小,这样就能省去额外的IO开销,具体可以查看org.apache.solr.response.SortingResponseWriter这个类。基于此,solr框架可以衍生出很多非常酷的功能,比如基于"/export"的流式导出功能,和基于"/export"的stream expression功能。
  3. 存储内容不是依赖于内存的,这个和老版本的fieldcache机制有本质区别。

以下是查询中使用了sort字段,solr的执行栈路径视图:


 从调用路径来看,最终会调用FieldCacheImpl的getNumerics的方法,如下:

@Override
  public NumericDocValues getNumerics(LeafReader reader, String field, Parser parser, boolean setDocsWithField) throws IOException {
    if (parser == null) {
      throw new NullPointerException();
    }
    // schema field 上是否开启了docValue=true
    final NumericDocValues valuesIn = reader.getNumericDocValues(field);
    if (valuesIn != null) {
      // Not cached here by FieldCacheImpl (cached instead
      // per-thread by SegmentReader):
      return valuesIn;
    } else {
      final FieldInfo info = reader.getFieldInfos().fieldInfo(field);
      if (info == null) {
        return DocValues.emptyNumeric();
      } else if (info.getDocValuesType() != DocValuesType.NONE) {
        throw new IllegalStateException("Type mismatch: " + field + " was indexed as " + info.getDocValuesType());
      } else if (info.getIndexOptions() == IndexOptions.NONE) {
        return DocValues.emptyNumeric();
      }
      return (NumericDocValues) caches.get(Long.TYPE).get(reader, new CacheKey(field, parser), setDocsWithField);
    }
  }
 

 

通过代码了解到,先调用LeafReader的getNumericDocValues的方法,结果是否为空取决于schema中的field定义是否设置docValue=true设置,如果开启了docvalue这里就能直接取得docValue对象,不然的话就通过预先在cache中准备的五种基于索引term的,field加载策略:

 

  private Map<Class<?>,Cache> caches;
  FieldCacheImpl() {
    init();
  }
  private synchronized void init() {
    caches = new HashMap<>(6);
    caches.put(Long.TYPE, new LongCache(this));
    caches.put(BinaryDocValues.class, new BinaryDocValuesCache(this));
    caches.put(SortedDocValues.class, new SortedDocValuesCache(this));
    caches.put(DocTermOrds.class, new DocTermOrdsCache(this));
    caches.put(DocsWithFieldCache.class, new DocsWithFieldCache(this));
  }
    我就纳闷了,solr5.0中既然已经有docvalue机制,为什么还要在框架中保留这些通过term预加载到内存的fieldcache机制,因为一旦用户需要使用排序,功能而又在schema中忘记定义docvalue为true,一旦文档数量多,很有可能导致OOM的,也许solr的开发者为了版本向下兼容的原因吧。

 

    问题解决:

     那如何解决用户索引结构不当使用导致的OOM问题呢,是通过在wiki中标注,应该如何小心的配置schema,来防止出现类似的问题。这就像在市区的马路上,通过在马路当中画上双黄线,来明令告知驾驶者不要越过双黄线逆向行驶,但事实是在高峰期,只要没有监控的地方总有胆子大的驾驶者要越过双黄线,解决办法就是,要像高速公路上在马路中间建造隔离带,强行防止开到逆向车道上去,但是这个成本确实是有点高,但是非常有效。我们在做平台式的产品中也需要借鉴类似经验,需要在平台产品中构筑起一个个轨道,保证用户在既定的轨道上操作,如果用户试图跳出既定轨道,我们就要通过友好的反馈消息告知他已经偏离了轨道需要及时纠正。这样一种办法,可定比在wiki中写开发规约之类的东西有效,友好得多。

 

    所以我在solr容器启动的时,执行了一个将solr框架预先准备的cache清空的操作,后续如果有操作试图不通过docvalue机制来执行sort之类的操作就一律报错,这样在开发过程中就避免的因为不合理设置schema导致的错误,代码如下:

   RemoveFieldCacheListener:

 

public class RemoveFieldCacheListener implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		RemoveFieldCacheStrategy.removeFieldCache();
	}
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
	}
}
 RemoveFieldCacheStrategy:

 

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;

import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.uninverting.FieldCacheImpl.Cache;
import org.apache.lucene.uninverting.FieldCacheImpl.CacheKey;
import org.apache.lucene.uninverting.FieldCacheImpl.DocsWithFieldCache;
import org.apache.lucene.util.Accountable;

public class RemoveFieldCacheStrategy {

	@SuppressWarnings("all")
	public static void removeFieldCache() {
		try {
			FieldCacheImpl fieldCacheManager = (FieldCacheImpl) FieldCache.DEFAULT;
			Field cacheField = FieldCacheImpl.class.getDeclaredField("caches");
			cacheField.setAccessible(true);
			// 防止启动的时候在schema中没有设置 docvalue属性的时候,字段设置了indexed=true
			// 将doc的term的值预先加载到内存中,防止業務方不適當設置query對象導致服務端oom
			Map<Class<?>, Cache> caches = (Map<Class<?>, Cache>) cacheField.get(fieldCacheManager);

			FieldCacheImpl.Cache disable = new FieldCacheImpl.Cache(null) {
				@Override
				public Object get(LeafReader reader, CacheKey key, boolean setDocsWithField)
						throws IOException {
					throw new IllegalStateException(
							"you are intending to use sorting,facet,group or other statistic feature,please set field:["
									+ key.field + "] docValue property 'true'");
				}
				@Override
				protected Accountable createValue(LeafReader reader, CacheKey key,
						boolean setDocsWithField) throws IOException {
					return null;
				}
			};
			caches.clear();
			caches.put(Long.TYPE, disable);
			caches.put(BinaryDocValues.class, disable);
			caches.put(SortedDocValues.class, disable);
			caches.put(DocTermOrds.class, disable);
			caches.put(DocsWithFieldCache.class, disable);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

 完!

 

   

      

 

 

  • 大小: 58.1 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics