![]() | 1 xuanbg 2019-10-16 13:13:09 +08:00 拆分服务。。。。。。 |
2 IamNotShady 2019-10-16 13:14:33 +08:00 一个请求 900M ? 换成 G1 试试 |
![]() | 3 chendy 2019-10-16 13:21:07 +08:00 ![]() 一个请求 900M…要么极端需求要么设计不合理… |
![]() | 4 jimrok 2019-10-16 13:23:59 +08:00 把数据拆出来,放在 redis 或者 memcached 里面。否则堆会太大,一旦需要扫描堆就会比较耗时。 |
![]() | 5 reus 2019-10-16 13:24:15 +08:00 换最新版 jvm 和最先进的 GC 算法 不换就不要奢求太多。 |
![]() | 6 coolcfan 2019-10-16 14:45:38 +08:00 也许可以从生命周期的角度检查下请求处理过程中消耗的内存。 |
7 lihongjie0209 2019-10-16 14:54:22 +08:00 那也就是说你的这个服务的并发数量 = 10 ?? |
8 summer7 OP @lihongjie0209 是的,这个项目基本等于没有并发。 10 并发也到不了。 |
9 summer7 OP @chendy 需求大概是这样的:从数据库查出 30000 条数据,数据部分字段会包含长文本,单条数据约 200+字段,查出后需要对数据解析,据观察内存涨幅约等于 900m |
10 summer7 OP @jimrok 数据流程大概是这样,第一步 jdbc 查库,第二步:对每条数据做解析,第三步:入 redis。 目前问题主要集中在解析这个过程 32 核机器跑满也需要 2-4s,稍微来个并发(不到 10 个),10g 的堆因为 gc 导致解析时间会上升到 20s+ |
![]() | 11 yidinghe 2019-10-16 16:15:57 +08:00 1、确认有没有内存泄漏,也就是多次 GC 后程序还是能正常运行并且长期运行; 2、能够对老年代进行非 STW 回收的垃圾收集器只有 G1。建议换 G1 ; 3、进一步对内存使用进行剖析,减少不必要的对象持有; 4、如果缓存数据占大头,那么换用 Redis/memcached 等独立进程的缓存方案。 |
![]() | 12 yidinghe 2019-10-16 16:17:05 +08:00 “从数据库查出 30000 条数据,数据部分字段会包含长文本” 这时候应该改为读取一条处理一条的方式。 |
![]() | 14 misaka19000 2019-10-16 16:21:07 +08:00 用 C 语言重写 |
![]() | 15 learnshare 2019-10-16 16:22:44 +08:00 这需求或实现逻辑并不合理吧 |
![]() | 16 Immortal 2019-10-16 16:24:46 +08:00 单条数据约 200+字段= = 猛老哥 |
![]() | 17 u823tg 2019-10-16 16:26:13 +08:00 换语言? 我胡说的 |
18 memedahui 2019-10-16 16:28:35 +08:00 @IamNotShady 我记得 java8 默认就是 G1 吧...还是我记错了 |
19 lihongjie0209 2019-10-16 16:31:46 +08:00 @misaka19000 #14 你用 c 语言管理 10G 内存试试, 没啥区别的。 |
20 lihongjie0209 2019-10-16 16:32:20 +08:00 @summer7 #9 一次查 3000 条试试? |
21 hikikomorimori 2019-10-16 17:51:36 +08:00 考虑 Nosql? |
22 lihongjie0209 2019-10-16 18:03:35 +08:00 @hikikomorimori #21 不管底层存储怎么样, 加载 30000 条数据就需要这么多的内存 |
23 babyvox5th 2019-10-16 18:09:27 +08:00 补充一下技术优化之外的,SSD 上 3000MB. |
![]() | 24 ipwx 2019-10-16 18:11:40 +08:00 via Android 不该想办法维护中间结果的表,降低每次请求计算量么 |
![]() | 25 bk201 2019-10-16 18:17:06 +08:00 取少点不行吗? |
26 bobuick 2019-10-16 18:21:56 +08:00 假设你其他措施都做了, 然后单次要是 900m 是必要的数据. 然后也无法重新设计, 然后请求完数据后内存里这些数据就不用保存了的话, 不是应该在 young 区被回收么. 把 young 设的足够大一些. 老年代应该保持一天都不到一次的水平 |
27 summer7 OP @ipwx 嗯,以前 hbase 做存储时已经是结构化的数据了,但是后来换了数据库必须要调用方去解析这些数据。其实整体数据流程很简单,jdbc 查询,查完解析入库。 |
![]() | 28 bookit 2019-10-16 18:39:57 +08:00 用 C 写一遍,最简单的那种,看需要多少内存 |
![]() | 29 sadfQED2 2019-10-16 18:40:58 +08:00 via Android 考虑 nosql?另外必须实时吗,不需要实时的话定时脚本计算,然后存缓存呢。最后,如果都不行就升级机器? 80 核以上,500+G 内存那种? |
![]() | 30 sadfQED2 2019-10-16 18:42:18 +08:00 via Android @sadfQED2 楼上的换语言没什么意义啊,数据已经那么大了,你用什么语言加载到内存都那么大,除非改算法 |
![]() | 31 BBCCBB 2019-10-16 18:45:21 +08:00 先用 G1 试试,然后增大堆内存再试试 |
![]() | 32 l8g 2019-10-16 18:48:15 +08:00 1. 一次查询 3W 条记录,可以拆分一下 2. 调整一下 Young 和 Old |
33 summer7 OP @bobuick 感谢回复。young 设置大一点我会验证试一下的。这是第一次遇到这种 GC 问题,不知像互联网那些高并发项目,Full GC 频率的合理范围是多少呢?还请指教 |
34 summer7 OP @babyvox5th 数据 JDBC 查询完,就是内存操作了目前。 |
35 summer7 OP @l8g 感谢回复。楼上也有大佬提到修改 young 和 old,我会试一下的。 说起来,拆分查询也是一个头疼的事情,目前底层存储库不支持这种比如 limit 这种拆分查询 |
37 summer7 OP @bobuick 目前我单纯的本地写个 for 循环去触发查询接口,基本上每调用 9 次就会触发一次 Full GC,之后内存迅速下降,基本等于刚启动时候的内存。 改改参数看吧,也是第一次遇到这种问题 |
39 summer7 OP @yidinghe 感谢回复。 1、确认后没有内存泄漏,一次 FullGC 之后内存基本和刚启动时一样.但是一次 10 并发,基本就要 Full GC 一次,楼下也有大佬提出修改 young 和 old,我试一下看看,将 young 设置大一点。 2、对于取一条数据解析一条,嗯。。其实一直有个疑惑,while(resultSet.next ())的时候,这个 resultSet.next 是不是就相当于一个游标,其实数据还是存在于数据库端的?还是说不同数据库有不同的实现方式? |
![]() | 40 shakoon 2019-10-16 19:08:41 +08:00 如果能在数据库上实现,用视图把表拆小、用存储过程进行你所说的解析,就试一下看看 |
41 semut 2019-10-16 19:28:17 +08:00 一次请求 30000 条数据,设计不太合理,可以简单说说这个任务的目的,看下有没有其他方案实现 |
42 0NF09LJPS51k57uH 2019-10-16 20:07:42 +08:00 长文本存 elasticsearch,其他 200 字段该拆表拆表,将 30000 条数据水平分割到不同 机器 /进程 流式处理,全部处理完毕再汇聚。 |
43 Jonz 2019-10-16 20:15:58 +08:00 关注下进展 |
![]() | 44 uyhyygyug1234 2019-10-16 20:54:28 +08:00 |
![]() | 45 af463419014 2019-10-16 21:02:13 +08:00 用堆外内存 处理的数据单独放在堆外内存,用完手动释放,可以跟 JVM 的 GC 分开 JVM 堆内存就不需要 10G 这么大了 |
![]() | 46 pangliang 2019-10-16 21:08:52 +08:00 3 万条数据为啥要一次性读入内存? 用了 orm 吧? 查完库只能返回一个 3 万的 list, 然后遍历 list? 直接 jdbc 里查, 完了用 jdbc 的 result.next 去循环直接处理, 不要拼到 list 再遍历; 这样就不会有 3 万行在内存了 如果还是不行, 检查下 jdbc 返回数据的方式; 可以设置, next 一次返回一行 php 都有, java 不可能没有 |
![]() | 47 SoloCompany 2019-10-16 21:14:27 +08:00 via iPad 加并发控制,限制 slow operation 的并发数,预留 30%以上的空闲 heap |
![]() | 48 mxalbert1996 2019-10-16 21:15:54 +08:00 via Android @sadfQED2 换语言的意义在于减少 GC 的时间啊 |
49 summer7 OP @phantomzz 其实在之前用 hbase 存储是分表的,但是每个表也是 200+字段的,之后底层数据库弃用了 hbase,把所有表数据汇聚在一张超大表中,数据量相当的大。 感觉“合久必分”这句话太适合描述这种存储方式变化了 |
51 summer7 OP @pangliang 感谢老哥的回复。源于对 JDBC 理解的不是很深,目前我的做法是 next 遍历完,30000 条数据存入 list 再交给具体的解析方法解析。 也有其他大佬和老哥你一样说,可以 next 遍历时一条一条解析,或许这个就是解决问题的办法吧,待我一试。 |
52 neoblackcap 2019-10-16 23:04:29 +08:00 为什么需要一次读 3W 条数据?流式处理嘛,你业务逻辑不改,换 JVM 也未必有成效。 毕竟假如全部都是新生代对象,但是在 GC 被触发的时候,这些对象很有可能还是活的,有其他业务代码在使用这它们。你这个整体并发一样上不去。 这个东西还是得结合你的业务逻辑进行分析才行 |
53 Buffer2Disk 2019-10-17 00:01:22 +08:00 改架构吧,这业务设计就不合理。。。 |
![]() | 54 swulling 2019-10-17 01:24:13 +08:00 via iPhone 每次请求结束后都主动触发一次 GC 吧 |
55 v2orz 2019-10-17 08:31:32 +08:00 换语言和堆外内存方案建议别搞 换语言作用不大 堆外内存,既然你们都设计成这样了,想想堆外内存管理估计也很难做好,不建议去踩 一次少查点数据或者流式处理就差不多了,GC 换 G1 |
![]() | 56 softtwilight 2019-10-17 09:01:13 +08:00 把 resultSet 包装为流,一条一条解析,十分省内存; return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>( Long.MAX_VALUE,Spliterator.ORDERED) { @Override public boolean tryAdvance(Consumer<? super Record> action) { try { if(!resultSet.next()) return false; action.accept(createRecord(resultSet)); return true; } catch(SQLException ex) { throw new RuntimeException(ex); } } }, false).onClose(() -> closeConnectionAnd...()) |
57 haochih 2019-10-17 09:20:16 +08:00 jdbc 默认情况下的读会把 sql 语句的结果全部加载到内存中,这种大数据量的读可以考虑采用 stream read。详情参见 https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-implementation-notes.html 中 ResultSet 一节。 |
58 tairan2006 2019-10-17 11:12:36 +08:00 这架构有问题,数据拆开,或者改成流处理 |
![]() | 59 sorra 2019-10-17 12:10:09 +08:00 JDBC ResultSet 有 vendor 差异性,默认情况下: - MySQL 和 PostgreSQL 会一次加载所有行到 JVM - Oracle 每次只加载 10 行到 JVM,DB 这边维持一个会话和偏移量,JVM 可以不停地推进偏移量,直到读完所有行 这个行为可配置(ResultSet 可以 setFetchSize),详情见 vendor 的文档 如果 vendor 支持 setFetchSize,你可以流式处理数据,不要都堆到内存里才全部处理 如果 vendor 不支持 setFetchSize 和 LIMIT,也可以想想能不能在 SQL 语句上想想办法 |
![]() | 60 sorra 2019-10-17 12:15:03 +08:00 Statement 也可以 setFetchSize,为了防止来不及,可以在 Statement 就设上 |
![]() | 61 jimrok 2019-10-17 13:41:22 +08:00 你的数据里有大文本是不太能降低内存的,大文本被装在进 java 要转换成 String 的对象,这些对象再进行解析,肯定要再产生若干小对象,gc 的负担肯定重。你这种还是提前做好,例如你要从小说里面找某个情节,那你把整篇小说读进来再查找肯定耗费内存。最后提前做好索引,这样根据索引,直接找到段落,返回段落信息就不需要消耗很多内存了。 |
62 lazyfighter 2019-10-17 14:21:05 +08:00 可以写个定时任务建立中间表,甚至最终数据表,这样你的并发也能上来,把数据处理拆出来 |
![]() | 63 TJT 2019-10-17 16:52:14 +08:00 流处理。 |