ES实战- data too large, data for
Posted 忘崽牛仔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES实战- data too large, data for相关的知识,希望对你有一定的参考价值。
场景
客户现场业务系统突然查询不到数据,个人一开始分析以为是聚合查询报错,于是去看了下系统日志,看到如下日志打印:
Caused by: ElasticsearchStatusException[Elasticsearch exception
[type=circuit_breaking_exception, reason=[parent] Data too large,
data for [<http_request>] would be [1032639682/984.8mb],
which is larger than the
limit of [1032637056/972.7mb],
real usage: [1032637056/984. 7mb],
new bytes reserved: [2626/2.5kb],
usages [request=72/72b, fielddata=0/0b, in_flight_requests=2626/2.5kb, accounting=6830904/6. 5mb]
]
尝试重启ES后系统可以恢复正常,但是运行一段时间后又会再次上报这个Data too large的错误。
异常原因
最终定位结论:请求数据量太大,内存使用达到设定的极限值,触发了es熔断请求,一旦某个内存使用达到设定的内存限值,
则触发熔断,不再响应任何请求。报错信息中的“Elasticsearch exception [type=circuit_breaking_exception”表明是
触发了熔断器导致报错,且日志显示实际使用量[1032637056/984. 7mb]已经超过了限制[1032637056/972.7mb],故触发熔断机制。
报错分析
PS:es版本7.10
分析报错,看日志Caused by: ElasticsearchStatusException[Elasticsearch exception这段,显然是es内部报的错,那还等啥,解铃还须系铃人,直接去撸es源码去瞧瞧到底咋引发的呗。
首先,异常抛出异常类是ElasticsearchStatusException,那么先看它:
public class ElasticsearchStatusException extends ElasticsearchException
、、、//此处源码无关紧要,咱不看
可以看到,这个异常是继承了ElasticsearchException异常的,那么我们就去找他爹看看
public class ElasticsearchException extends RuntimeException implements ToXContentFragment, Writeable
、、、//略去无关源码
//ps:我们就事论事,在开篇,我们的异常日志打印输出了“ElasticsearchStatusException[Elasticsearch exception[ ”我们就在这个代码里用这个搜一下,发现如下源码:
static String buildMessage(String type, String reason, String stack)
StringBuilder message = new StringBuilder("Elasticsearch exception [");
message.append("type").append('=').append(type).append(", ");
message.append("reason").append('=').append(reason);
if (stack != null)
message.append(", ").append("stack_trace").append('=').append(stack);
message.append(']');
return message.toString();
、、、//略去无关源码
仔细比对下报错输出格式就会发现,确实就是这个方法打印输出的。那么看下源码打印方法的这一行
message.append("type").append('=').append(type).append(", ");
对应我们报错里就是type=circuit_breaking_exception,显然,这就是报错的错误类型,有了这个,我们就可以有的放矢,具体分析了。这个type啥意思,我们直接翻译就是电路中断异常,那么用行话来说就是触发了熔断机制。在ElasticsearchException类中可以找到这个异常的枚举:
private static enum ElasticsearchExceptionHandle
、、、//略去无关源码
CIRCUIT_BREAKING_EXCEPTION(CircuitBreakingException.class, CircuitBreakingException::new, 133, ElasticsearchException.UNKNOWN_VERSION_ADDED),
、、、//略去无关源码
既然知道了错误结果,那么我们反过来,顺藤摸瓜就行了呀,谁会抛出这个异常呢? 继续在源码里搜索抛出此异常的代码 “throws CircuitBreakingException”,结果还真有发现,报错类位置org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService,具体代码如下
public void checkParentLimit(long newBytesReserved, String label) throws CircuitBreakingException
HierarchyCircuitBreakerService.MemoryUsage memoryUsed = this.memoryUsed(newBytesReserved);
long parentLimit = this.parentSettings.getLimit();
if (memoryUsed.totalUsage > parentLimit && this.overLimitStrategy.overLimit(memoryUsed).totalUsage > parentLimit)
this.parentTripCount.incrementAndGet();
StringBuilder message = new StringBuilder("[parent] Data too large, data for [" + label + "] would be [" + memoryUsed.totalUsage + "/" + new ByteSizeValue(memoryUsed.totalUsage) + "], which is larger than the limit of [" + parentLimit + "/" + new ByteSizeValue(parentLimit) + "]");
if (this.trackRealMemoryUsage)
long realUsage = memoryUsed.baseUsage;
message.append(", real usage: [");
message.append(realUsage);
message.append("/");
message.append(new ByteSizeValue(realUsage));
message.append("], new bytes reserved: [");
message.append(newBytesReserved);
message.append("/");
message.append(new ByteSizeValue(newBytesReserved));
message.append("]");
message.append(", usages [");
message.append((String)this.breakers.entrySet().stream().map((e) ->
CircuitBreaker breaker = (CircuitBreaker)e.getValue();
long breakerUsed = (long)((double)breaker.getUsed() * breaker.getOverhead());
return (String)e.getKey() + "=" + breakerUsed + "/" + new ByteSizeValue(breakerUsed);
).collect(Collectors.joining(", ")));
message.append("]");
Durability durability = memoryUsed.transientChildUsage >= memoryUsed.permanentChildUsage ? Durability.TRANSIENT : Durability.PERMANENT;
logger.debug(() ->
return new ParameterizedMessage("", message.toString());
);
throw new CircuitBreakingException(message.toString(), memoryUsed.totalUsage, parentLimit, durability);
嚯,这不就是拼接的我们的报错日志吗?那么什么条件下才会进入这个拼接逻辑呢,继续看上面的源码,可以看到:
if (memoryUsed.totalUsage > parentLimit && this.overLimitStrategy.overLimit(memoryUsed).totalUsage > parentLimit)
```//拼接错误信息
从代码可以看出,当memoryUsed.totalUsage > parentLimit时且this.overLimitStrategy.overLimit(memoryUsed).totalUsage > parentLimit才会出现熔断;
哦豁,这一堆变量比来比去,都是啥意思啊,别急,我们继续分析,慢慢拨开云雾,首先看parentLimit,都在和它比,先看看它到底是个什么玩意。
看代码:long parentLimit = this.parentSettings.getLimit();
原来是从this.parentSettings里取得值,那就直捣黄龙,看它是怎么怎么赋值的。
在HierarchyCircuitBreakerService这个类中搜this.parentSettings初始化的地方,得到:
this.parentSettings = new BreakerSettings("parent", ((ByteSizeValue)TOTAL_CIRCUIT_BREAKER_LIMIT_SETTING.get(settings)).getBytes(), 1.0D, Type.PARENT, (Durability)null);
logger.trace(() ->
return new ParameterizedMessage("parent circuit breaker with settings ", this.parentSettings);
);
看到它的值是一个BreakerSettings对象,它new了一个BreakerSettings对象,来吧,继续,看下这个对象的构造函数:
public BreakerSettings(String name, long limitBytes, double overhead, Type type, Durability durability)
this.name = name;
this.limitBytes = limitBytes;
this.overhead = overhead;
this.type = type;
this.durability = durability;
调用这个构造函数发现就第二个值它是比较特殊的,其他的参数都是固定值,那么我们就干它,TOTAL_CIRCUIT_BREAKER_LIMIT_SETTING,看它又是在哪赋值,还是在HierarchyCircuitBreakerService类中,搜出TOTAL_CIRCUIT_BREAKER_LIMIT_SETTING赋值之处,发现在一个静态代码块里,原来类加载时它就已经赋值了。
static
USE_REAL_MEMORY_USAGE_SETTING = Setting.boolSetting("indices.breaker.total.use_real_memory", true, new Property[]Property.NodeScope);
TOTAL_CIRCUIT_BREAKER_LIMIT_SETTING = Setting.memorySizeSetting("indices.breaker.total.limit", (settings) ->
return (Boolean)USE_REAL_MEMORY_USAGE_SETTING.get(settings) ? "95%" : "70%";,
new Property[]Property.Dynamic, Property.NodeScope);
、、、//略去无关源码
至此,这个参数怎么来的可以知道了,parentLimit的值与配置indices.breaker.total.limit(默认值为95%或者70%),至于是95%还是70%则与indices.breaker.total.use_real_memory(默认值为true)的配置有关。
parentLimit的值与配置indices.breaker.total.limit(默认值为95%或者70%)有关,它的默认值与indices.breaker.total.use_real_memory(默认值为true)的配置有关,如下代码所示:
再来看下和它做比较的memoryUsed.totalUsage这个参数,
HierarchyCircuitBreakerService.MemoryUsage memoryUsed = this.memoryUsed(newBytesReserved);
memoryUsed.totalUsage它也是从一个对象里取得一个属性值,MemoryUsage对象如下,作为了一个内部类出现:
static class MemoryUsage
final long baseUsage;
final long totalUsage;
final long transientChildUsage;
final long permanentChildUsage;
MemoryUsage(long baseUsage, long totalUsage, long transientChildUsage, long permanentChildUsage)
this.baseUsage = baseUsage;
this.totalUsage = totalUsage;
this.transientChildUsage = transientChildUsage;
this.permanentChildUsage = permanentChildUsage;
继续在源码里搜索可以发现,该值经历了一个计算得到
private HierarchyCircuitBreakerService.MemoryUsage memoryUsed(long newBytesReserved)
long transientUsage = 0L;
long permanentUsage = 0L;
Iterator var7 = this.breakers.values().iterator();
while(var7.hasNext())
CircuitBreaker breaker = (CircuitBreaker)var7.next();
long breakerUsed = (long)((double)breaker.getUsed() * breaker.getOverhead());
if (breaker.getDurability() == Durability.TRANSIENT)
transientUsage += breakerUsed;
else if (breaker.getDurability() == Durability.PERMANENT)
permanentUsage += breakerUsed;
long parentEstimated;
if (this.trackRealMemoryUsage)
parentEstimated = this.currentMemoryUsage();
return new HierarchyCircuitBreakerService.MemoryUsage(parentEstimated, parentEstimated + newBytesReserved, transientUsage, permanentUsage);
else
parentEstimated = transientUsage + permanentUsage;
return new HierarchyCircuitBreakerService.MemoryUsage(parentEstimated, parentEstimated, transientUsage, permanentUsage);
代码中最后返回结果根据trackRealMemoryUsage的值进行了if判断,看下源码它在哪赋值:
this.trackRealMemoryUsage = (Boolean)USE_REAL_MEMORY_USAGE_SETTING.get(settings);
可以看到它和USE_REAL_MEMORY_USAGE_SETTING取值有关。这个在刚才我们分析parentLimit已经看到过了,它也是在类加载时就已经初始化了,代码如下
static
USE_REAL_MEMORY_USAGE_SETTING = Setting.boolSetting("indices.breaker.total.use_real_memory", true, new Property[]Property.NodeScope);
、、、//略去无关源码
也就是说trackRealMemoryUsage取值和配置indices.breaker.total.use_real_memory有关。
至此,这个参数的来源也搞清楚了,存在的疑惑大概就是上文中的配置项indices.breaker.total.limit,indices.breaker.total.use_real_memory,到底是啥意思了。
我也不知道这是啥玩意,于是就网上找资料了解了一下,
indices.breaker.total.limit 所有breaker使用的内存值,默认值为 JVM 堆内存的70%,当内存达到最高值时会触发内存回收。elasticsearch包含多个circuit breaker来避免操作的内存溢出。每个breaker都指定可以使用内存的限制。另外有一个父级breaker指定所有的breaker可以使用的总内存。
总熔断器
indices.breaker.total.use_real_memory
它的值直接影响JVM堆内存分配的大小,
1、值为 true, indices.breaker.total.limit 为堆大小的 95%。
2、值为 false,indices.breaker.total.limit 为堆大小的70%
原来这玩意是elasticsearch断路器生效的参数配量项,关于ES断路器,在此不做详细介绍,Elasticsearch包含多个断路器,每个断路器指定它可以使用多少内存的限制.其功能就是用于防止操作导致OutOfMemoryError.
解决方案
1、调大ES JVM堆内存
ES默认是1g,根据服务器配置做调整,一般建议为服务器内存的一半,并且建议Xms与Xmx大小一致。
2、设置indices.fielddata.cache.size
如果服务器没有足够的内存可考虑此选项,有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间,在elasticsearch.yml中配置:indices.fielddata.cache.size: 40%
ps:这样的方式可以帮助你合理的分配你有限的内存,但是不能改变你的内存。所以终极解决办法还是增加你的内存大小。
通过postman或者其他接口测试工具更改:
PUT _cluster/settings
"persistent" :
"indices.breaker.fielddata.limit" : "20%"
以上是关于ES实战- data too large, data for的主要内容,如果未能解决你的问题,请参考以下文章
ElasticsearchData too large, data for which is larger than the limit of
Elasticsearch Breaker CircuitBreakingException Parent Data Too Large Real Usage
ES报错Result window is too large问题处理