YAZONG 我的开源

关于生成elasticsearch索引数据时内存溢出的故障

 
0 评论0 浏览

一、问题描述

在java应用中按批次生成elasticsearch中的索引数据的同时,首先在mysql中查询大量数据转成对象存放在list列表,然后把list中的大批量数据通过bulk的方式写入索引,最终导致java内存泄露。通过top命令惊奇的发现CPU突然从60%-90%-160%-飙到200%.。。。当发现此问题时,只是简单的设置了JVM的内存参数 -Xms:1024m,-Xmx:3074m再次调大了 -Xmx:4096m ,当设置JVM参数后,还是不可以。

二、异常列表

2、1 Java heap space

Exception in thread "pool-2-thread-3" java.lang.OutOfMemoryError: Java heap space
	at com.fasterxml.jackson.core.util.BufferRecycler.balloc(BufferRecycler.java:155)
	at com.fasterxml.jackson.core.util.BufferRecycler.allocByteBuffer(BufferRecycler.java:96)
	at com.fasterxml.jackson.core.util.BufferRecycler.allocByteBuffer(BufferRecycler.java:86)
	at com.fasterxml.jackson.core.io.IOContext.allocWriteEncodingBuffer(IOContext.java:160)
	at com.fasterxml.jackson.core.json.UTF8JsonGenerator.<init>(UTF8JsonGenerator.java:122)
	at com.fasterxml.jackson.core.JsonFactory._createUTF8Generator(JsonFactory.java:1426)
	at com.fasterxml.jackson.core.JsonFactory.createGenerator(JsonFactory.java:1139)
	at org.elasticsearch.common.xcontent.json.JsonXContent.createGenerator(JsonXContent.java:79)
	at org.elasticsearch.common.xcontent.XContentBuilder.<init>(XContentBuilder.java:168)
	at org.elasticsearch.common.xcontent.XContentBuilder.<init>(XContentBuilder.java:142)
	at org.elasticsearch.common.xcontent.XContentBuilder.builder(XContentBuilder.java:69)
	at org.elasticsearch.common.xcontent.json.JsonXContent.contentBuilder(JsonXContent.java:47)
	at org.elasticsearch.common.xcontent.XContentFactory.contentBuilder(XContentFactory.java:121)
	at org.elasticsearch.action.index.IndexRequest.source(IndexRequest.java:313)
	at org.elasticsearch.action.index.IndexRequest.source(IndexRequest.java:303)
	at org.elasticsearch.action.update.UpdateRequest.doc(UpdateRequest.java:553)
	at org.elasticsearch.action.update.UpdateRequestBuilder.setDoc(UpdateRequestBuilder.java:212)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid250485.hprof ...
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid250485.hprof ...

2、2 c3p0

[-AdminTaskTimer] c.m.v.a.ThreadPoolAsynchronousRunner     : com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@bf88359 -- APPARENT DEADLOCK!!! Complete Status: 
	Managed Threads: 3
	Active Threads: 3
	Active Tasks: 
		com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@501c26bd
			on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#0
		com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@396518ab
			on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#1
		com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@682291e5
			on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#2
	Pending Tasks: 
		com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@1b7d5a99
		com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@2ae5be85
Pool thread stack traces:
	Thread[C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#0,5,main]

三、系统环境还原

cat /etc/redhat-release

CentOS Linux release 7.4.1708 (Core)

uname -r

3.10.0-693.21.1.el7.x86_64

java -version

openjdk version “1.8.0_171”

OpenJDK Runtime Environment (build 1.8.0_171-b10)

OpenJDK 64-Bit Server VM (build 25.171-b10, mixed mode)

三台mesos-1.6.0、三台marathon-1.5、三台elasticsearch6.1.4

启动java应用的marathon配置:cpu0.2,内存3g

初始化c3p0的mysql5.7连接属性:

##连接池大小管理

##初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default:3(规范值:20,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.initialPoolSize=5

##连接池中保留的最小连接数。Default:3(规范值:20,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.minPoolSize=5

##连接池中保留的最大连接数。Default:15(规范值:100~500之间,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.maxPoolSize=50

##当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default:3(规范值:5,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.acquireIncrement=5

##重连接相关规范

##从数据库获取新连接失败后重复尝试的次数。Default:30

spring.datasource.c3p0.mediadb.acquireRetryAttempts=30

##两次连接中间隔时间,单位毫秒。Default:1000

spring.datasource.c3p0.mediadb.acquireRetryDelay=1000

##当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。Default:0(不建议此配置使用)

##checkoutTimeout: 0

##连接池的大小和连接的生存时间管理规范

##连接的最大空闲时间,单位秒,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。Default:0(规范值:120,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.maxIdleTime=120

##配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待 它close再断开。配置为0的时候则不会对连接的生存时间进行限制。(规范值:0,除非使用其他类型数据库)

spring.datasource.c3p0.mediadb.maxConnectionAge=0

##配置PreparedStatement缓存规范

##连接池为数据源缓存的PreparedStatement的总数。default: 0

spring.datasource.c3p0.mediadb.maxStatements=100

##定义了连接池内单个连接所拥有的最大缓存statements数。Default:0(规范值:5,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.maxStatementsPerConnection=5

##配置连接测试

##SQL检测语句。Default:null

spring.datasource.c3p0.mediadb.preferredTestQuery=SELECT SYSDATE() FROM DUAL

##用来配置测试空闲连接的间隔时间,单位秒。测试方式还是上面的两种之一,因为它保证连接池会每隔一定时间对空闲连接进行一次测试,从而保证有效的空闲连接能每隔一定时间访问一次数据库,为0则不测试。Default:0

spring.datasource.c3p0.mediadb.idleConnectionTestPeriod=120

##c3p0是异步操作的,缓慢的JDBC操作通过进程完成。扩展这些操作可以有效的提升性能通过多线程实现多个操作同时被执行。Default:3

spring.datasource.c3p0.mediadb.numHelperThreads=3

四、代码示例

4、1未优化前java部分

List<Map<String,Object>> list = new ArrayList<>();
for (int i = beginNum; i < endNum; i++) {
	User sysUser = userList.get(i);
	String userId = sysUser.getId();

	List<Map<String,Object>> initList = xxMapper.selectUserXXList(userId);

	if(!initList.isEmpty()){
		list.addAll(initList);
	}
}

4、2未优化前sql部分

SELECT
	CONCAT(#{uId},'$',less.l_id) as u_l_id,
	(
		SELECT
			COUNT(1)
		FROM
			Xx_favorites fa
		WHERE
			fa.user_id = #{userId}
		AND fa.l_id = less.l_id) AS is_f,
	IFNULL(
		(SELECT
			lm.ip
		FROM
			xx_mapping lum
		WHERE
			lm.user_id = #{userId}
		AND lm.l_id = less.l_id),0) AS is_p
FROM
	xx_ls ls

4、3改造方法

4、3、1加入多线程

在调用“4、1未优化前java部分”的方法时,由于是使用分批次处理,这里使用CountDownLatch的countDown()和await()方法,注意每次处理时,需重新初始化CountDownLatch对象,且设置的批次个数和CountDownLatch构造方法中传的值的个数务必保持一致。还要注意的时,此应用是否跟其他批处理应用在同一台机器中,机器资源是否允许等硬件环境。

4、3、2优化生成索引方式

“4、2未优化前sql部分”中的内容是生成一个索引,在测试环境开发时,由于开发库数量级比较少,且未询问DBA生产环境中的数据条数,所以这是导致内存溢出的一个主要原因。

在“4、2未优化前sql部分”中,xx_ls表以及“4、1未优化前java部分”中的Users表的条数是关键因素,二者和下述内容是笛卡尔积的关系,如果Xx_favorites表和xx_mapping表中的数据条数可忽略,且按照下述SQL生成索引的话,那么索引中的数据将是亿级别的,这样虽然在查询时不用多嵌套其他索引查询来获取字段,但是这种数据量的积累也造成了不必要的开销。

在ES解决方案就是把“4、2未优化前sql部分”中的Xx_favorites表和xx_mapping表分别单独建立索引,与表的内容保持一致,在其他情况允许的情况下,查询时多查询两个索引即可。

所以在建立索引时,不仅要熟悉业务逻辑、表关系以外,在硬件环境和人为因素也要考虑进去。


标题:关于生成elasticsearch索引数据时内存溢出的故障
作者:yazong
地址:https://blog.llyweb.com/articles/2019/03/05/1578152233542.html