java 8 的 stream 操作导致 线程卡死
root@data-f7b697db9-hq9lf:/app# jstack 8 | grep -A20 0x46
"http-nio-8200-exec-1" #60 daemon prio=5 os_prio=0 cpu=573228.20ms elapsed=783.76s tid=0x00007f8751e8d800 nid=0x46 runnable [0x00007f871eaf1000]
java.lang.Thread.State: RUNNABLE
at java.util.stream.ReferencePipeline$2$1.accept([email protected]/ReferencePipeline.java:176)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining([email protected]/ArrayList.java:1655)
at java.util.stream.AbstractPipeline.copyInto([email protected]/AbstractPipeline.java:484)
at java.util.stream.AbstractPipeline.wrapAndCopyInto([email protected]/AbstractPipeline.java:474)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential([email protected]/ReduceOps.java:913)
at java.util.stream.AbstractPipeline.evaluate([email protected]/AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect([email protected]/ReferencePipeline.java:578)
at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:32)
at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:40)
at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:40)
at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImpl.getTableNames(DataBaseRepositoryImpl.java:66)
at cn.bobmao.pro.data.repository.externalDataSourceHelper.ExternalDataSourceExecutor.getTableNames(ExternalDataSourceExecutor.java:57)
at cn.bobmao.pro.data.service.ExternalDataSourceService.updateTable(ExternalDataSourceService.java:215)
at cn.bobmao.pro.data.controller.ExternalDataSourceController.getTableInfo(ExternalDataSourceController.java:50)
at cn.bobmao.pro.data.controller.ExternalDataSourceController$$FastClassBySpringCGLIB$$f577fbc0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
pid 为 70 的线程( 16 进制就是 0x46 )为异常线程
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12 root 20 0 7793308 1.9g 24564 S 61.3 6.0 2:03.58 G1 Conc#0
70 root 20 0 7793308 1.9g 24564 S 16.3 6.0 10:17.51 http-nio-8200-e
29 root 20 0 7793308 1.9g 24564 S 5.0 6.0 0:06.73 GC Thread#1
10 root 20 0 7793308 1.9g 24564 S 4.7 6.0 0:06.69 GC Thread#0
15 root 20 0 7793308 1.9g 24564 S 0.7 6.0 0:01.93 VM Thread
@Override
public List<String> getForeignKeyTable(List<String> tableNames, DataSourceEntity info, List<ForeignKeyInfo> foreignKeyInfos) {
List<ColumnInfo> result = new ArrayList<>();
List<String> allTableNames = new ArrayList<>(tableNames);
for (String tableName : tableNames) {
result.addAll(findAllTable(info.getDataBaseName(), tableName));
}
result = result.stream().filter(column -> Arrays.stream(DB_KEYWORD).noneMatch(it -> column.getColumnName().equalsIgnoreCase(it))).collect(Collectors.toList());
Map<String, List<ColumnInfo>> tables = result.stream().collect(Collectors.groupingBy(ColumnInfo::getTableName));
List<String> childTables = new ArrayList<>();
for (String name : tables.keySet()) {
List<ColumnInfo> columnInfos = tables.get(name);
for (ColumnInfo columnInfo : columnInfos) {
List<ForeignKeyInfo> collect = foreignKeyInfos.stream().filter(it -> it.getSourceTableName().equals(name) && it.getSourceColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList()); // 32 行
if (!CollectionUtils.isEmpty(collect)) {
//获取子表的表名
childTables.addAll(collect.stream().map(ForeignKeyInfo::getTargetTableName).collect(Collectors.toList()));
}
}
}
if (!CollectionUtils.isEmpty(childTables)) {
allTableNames.addAll(getForeignKeyTable(childTables, info, foreignKeyInfos)); // 40 行
}
return allTableNames;
}
一脸疑问,不清楚怎么排查。。。等一会儿容器就被 k8s 杀死重启了。
1
zhangleshiye 2022-08-01 17:15:32 +08:00
for 加 stream 看着有点吓人
|
2
DonaldY 2022-08-01 17:25:12 +08:00
估计是 OOM ,跟 Java8 stream 没啥关系。
这个在递归调诶。 allTableNames.addAll(getForeignKeyTable(childTables, info, foreignKeyInfos)); 可以去看下 gc.log 或者 日志中是否有 OutOfMemoryError |
3
Bootis 2022-08-01 17:34:01 +08:00
childTables 逻辑有问题,你可以加一个日志打印,应该第二次以后的每次递归调用的入参 tableNames 都是一样的
|
4
MarkP 2022-08-01 17:35:11 +08:00
你这个递归都没出口。。。
|
5
MarkP 2022-08-01 17:36:34 +08:00
看错了,有出口,但我怀疑就是这个递归的问题
|
6
hhjswf 2022-08-01 18:16:16 +08:00
恐怖。。这么多遍历,肉眼看上去起码有三层,再递归一下,这算法复杂度得是什么规模啊
|
7
coderstory OP @MarkP childTables 是空的 就返回了
|
8
coderstory OP 面向业务编程的结果 按代码一行行看很容易 就是查询一张表的外键以及外键表的外键表。。。整个外键引用链表全查出来 先循环表 在循环列 然后 表的列查询是否有外键
|
9
guxingke 2022-08-01 18:52:10 +08:00
递归了就有问题吧
a -> b -> c -> ... -> b -> ... |
10
DT37 2022-08-01 21:14:48 +08:00
我看 Stream 就头疼,用的太多就很懵逼
|
11
Hug125 2022-08-01 21:22:09 +08:00 via iPhone 1
stream 用不明白的话建议先把 stream 换成 for debug 明白了再换回 stream 回归测试。
stream 和 for 混着来建议统一换成 stream 流在处理大批量的数据还是有性能优势的 |
12
Leviathann 2022-08-01 21:46:48 +08:00 3
评价一下:
为什么 columnInfo 的 list 要叫 result ? 为什么 result 要用 new list + foreach addAll 的方法初始化,然后又用 stream 过滤? 为什么过滤以后的 result 又直接赋值给 result ? 为什么复杂的 filter 不抽成函数? 为什么不用 map.entrySet().stream 遍历而是写得这么麻烦? 为什么要 foreignKeyInfos 过滤以后要 collect 再判空再 add 到 childTables 而不是直接 forEach 里 add? 为什么 foreignKeyInfos 过滤后的名字叫 collect ? 为什么不是遍历 foreignkeyinfos 而是遍历用来过滤的中间变量 tables ? 说实话代码这样我一般都懒得看具体逻辑 |
13
iosyyy 2022-08-01 21:57:28 +08:00
这个应该是拷贝 list 的时候太大导致卡住了 要不用个 map? 这过滤写的..不忍直视
|
14
oneisall8955 2022-08-01 22:15:19 +08:00 via Android
这和 stream 没关系,改成 for 递归也一样
|
15
oneisall8955 2022-08-01 22:16:24 +08:00 via Android
@oneisall8955 口误,for 迭代变量
|
16
oneisall8955 2022-08-01 22:16:34 +08:00 via Android
@oneisall8955 遍历。。。
|
17
ChicC 2022-08-01 23:23:48 +08:00
没注释,已经理不清了
|
18
dqzcwxb 2022-08-02 00:57:46 +08:00
换成 for 一样卡死,跟 stream 没有关系
|
19
Vegetable 2022-08-02 01:14:39 +08:00
这写法麻了,这种复杂度还敢用 stream ?真的绝了
|
20
TWorldIsNButThis 2022-08-02 02:28:41 +08:00 via iPhone
@Vegetable 他想干的事情根本不复杂,是瞎 jb 写的代码导致看起来复杂
|
21
TWorldIsNButThis 2022-08-02 02:32:43 +08:00 via iPhone
@Vegetable 而且这里的所谓 stream 全 tm 是单步操作然后就 collect ,看得出来这人根本就不怎么会,完全是把 stream 当成 collectionutil.filter 在用
|
22
Aloento 2022-08-02 02:33:59 +08:00
好恐怖呀哈哈哈
|
23
chengchen 2022-08-02 03:38:51 +08:00 via iPhone
这不就是二叉树层序遍历的变形题吗,leetcode 的 easy 难度
|
24
MoYi123 2022-08-02 10:13:56 +08:00
看起来像是数据里有环.
|
25
Belmode 2022-08-02 10:54:52 +08:00
数据库里存在表外键循环依赖了,导致内存居高不下,一直 GC
|
27
zmal 2022-08-02 11:21:33 +08:00
线程卡死本身和 stream 没啥关系。
但这个代码写的实在是太辣了。stream 不是让这么用的。 |
28
lmshl 2022-08-02 11:32:05 +08:00 4
|
29
lmshl 2022-08-02 11:36:18 +08:00
|
32
bigfei 2022-08-02 18:15:08 +08:00
MYSQL 有元数据表的呀。。直接用 CTP 查询元数据表就可以了
|
33
bigfei 2022-08-02 18:17:08 +08:00
|
34
bigfei 2022-08-02 18:21:27 +08:00
|
35
lmshl 2022-08-02 18:49:20 +08:00 2
|
36
lmshl 2022-08-02 18:58:22 +08:00 2
|
37
nbndco 2022-08-02 19:00:08 +08:00 via iPad
每当这个时候我就特别能理解为什么说千万不要用新特性,没事不要修改不要更新不要升级了。这水平要是写 for 可能这代码还跑的快一点,至少不用 collect 这么多次。可读性本来就没有,所以也无所谓了。
|
40
golangLover 2022-08-13 01:02:48 +08:00
@lmshl new dark? 找不到啊
|
41
ozipin 2022-08-15 15:05:31 +08:00
是不是多表之间的外键形成了环状结构然后有没有加以检测
|