V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dsvshx
V2EX  ›  Java

Grpc 服务优化 GC 的一个问题,请教一下大佬们

  •  
  •   dsvshx · 2024-04-12 17:36:56 +08:00 · 2888 次点击
    这是一个创建于 372 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:

    客户端用的是 grpc 协议,这个动不了。所以服务端必须用 grpc 协议,但是我们的服务流量非常大。proto 中的 bytes 会比较大,会卡在 gc 上,性能上不去。

    方案

    服务端不用 grpc 框架,自己用 netty 来实现,就是两层,一层用 Netty Http2 相关的封装,一层自己手动解析 proto 的二进制,遇到 bytes 字段直接用 ByteBuf ,省掉了拷贝到堆内存的 byte[],进而节省这部分的 gc 。返回的时候直接编码成二进制 proto 的格式。

    有人做过类似的吗,有什么坑吗? 或者有没有其他更好的方案解决 gc 问题,提高性能?

    第 1 条附言  ·  2024-04-12 19:34:05 +08:00
    补充一下信息:
    java17
    ZGC
    堆内存 40GB
    grpc 请求单个最大几十 M
    31 条回复    2024-04-18 17:54:39 +08:00
    haython
        1
    haython  
       2024-04-12 17:45:50 +08:00
    服务端非用 java 不可吗?
    selca
        2
    selca  
       2024-04-12 17:47:03 +08:00   ❤️ 2
    外包给 1 楼
    lifei6671
        3
    lifei6671  
       2024-04-12 17:55:42 +08:00
    我以为你说 Golang 呢。原来是说的 Java 。Java 的瓶颈也不在 GC 上吧。
    lmshl
        4
    lmshl  
       2024-04-12 18:18:32 +08:00
    一个大部分连续的 bytes 卡住 gc ,听起来还是很离谱的,除非你给出详细测试步骤,不然很难证明你的前提是对的。
    即便你前提成立,也可以简单粗暴的直接上 ZGC 来解决,不需要你重复发明任何东西。
    me1onsoda
        5
    me1onsoda  
       2024-04-12 18:26:58 +08:00
    gc stw 时长是多少
    chendy
        6
    chendy  
       2024-04-12 18:29:31 +08:00
    > proto 中的 bytes 会比较大,会卡在 gc 上
    比较大是多大,卡在 gc 是怎么卡呢?

    最简单的办法就是不动任何其他参数然后使劲拉内存
    dsvshx
        7
    dsvshx  
    OP
       2024-04-12 19:26:48 +08:00
    @lifei6671 流量大概快 2GB/s 了,每秒生成的垃圾也比较大。然后 grpc 用堆内存也得拷贝一份,cpu 也会高
    dsvshx
        8
    dsvshx  
    OP
       2024-04-12 19:29:54 +08:00
    @lmshl 目前是 ZGC ,流量太大 2GB/s ,堆都用了 40G 了,偶尔还是会有内存分配暂停。单个 grpc 请求比较大,可能会有几十 M
    dsvshx
        9
    dsvshx  
    OP
       2024-04-12 19:30:29 +08:00
    @me1onsoda 用的 ZGC ,stw 比较短,但是会有 Allocation Stall ,几百毫秒
    dsvshx
        10
    dsvshx  
    OP
       2024-04-12 19:32:25 +08:00
    @chendy 最大可能几十 MB ,单机流量快 2GB/s 。用的 ZGC ,主要是 Allocation Stall 内存分配暂停。
    dsvshx
        11
    dsvshx  
    OP
       2024-04-12 19:34:36 +08:00
    @haython 对,必须得用 java
    wenhuibrave
        12
    wenhuibrave  
       2024-04-12 19:46:00 +08:00
    zgc 设置了每隔多久强制 gc 吗?我猜测需要根据目前 gc 的具体情况做下 gc 调优,调节一些 gc 参数
    dsvshx
        13
    dsvshx  
    OP
       2024-04-12 19:53:20 +08:00
    @wenhuibrave 都不用强制 GC ,每分钟十几次,主要是流量大 2GB/s ,垃圾多,算下来也是这么个 GC 频率。gc 调优治标不治本。所以才想不用 grpc ,自己去解析 ByteBuf
    luozic
        14
    luozic  
       2024-04-12 20:23:14 +08:00   ❤️ 2
    描述:单个请求最大几十 MB ,单机流量 2GB/s ?
    +++++++++++++++
    我觉得这种还是去你们自己测试环境复现一下,把 jvm 的内存占用详细的用 arthas jfr 等工具抓一下,用那种火焰图工具看一下。
    +++++++++++++++++++++++++
    现在这隔靴瘙痒的,并不清楚,到底是那部分占用内存过多。
    最近看到的最详细的 Java 极限性能调优过程的 1brc 挑战的 blog ,参考一下大牛是怎么极限调优的。

    https://questdb.io/blog/billion-row-challenge-step-by-step/
    luozic
        15
    luozic  
       2024-04-12 20:23:47 +08:00
    流量录制+回放,就是一种不错的复现方式
    zhady009
        16
    zhady009  
       2024-04-12 22:09:00 +08:00
    可以试试 Vertx, 不过我认为并不能解决你现在的问题
    https://github.com/LesnyRumcajs/grpc_bench/discussions/354
    momo24672
        17
    momo24672  
       2024-04-12 22:13:46 +08:00
    whx
        18
    whx  
       2024-04-12 23:15:10 +08:00 via Android
    https://inside.java/2023/11/28/gen-zgc-explainer/

    不知道分代 zgc 能解决不,得升级到 jdk21
    ccde8259
        19
    ccde8259  
       2024-04-13 03:12:27 +08:00
    都知道卡 gc 了,直接的方法就是不让他参与 gc 嘛……unsafe 去 malloc 到非堆上,然后自己做对象生命周期管理就行啊……
    laminux29
        20
    laminux29  
       2024-04-13 04:00:56 +08:00
    堆机器,负载均衡,试试看?
    iseki
        21
    iseki  
       2024-04-13 05:13:01 +08:00 via Android
    arena ,把 payload 弄到堆外面去,手动管理。但是这么一来…protobuf 自己解析想想就很麻烦。
    长期来看这只能是个权宜之计,以后不能这么设计 API ,搞出大量大体积的 bytes
    GenericT
        22
    GenericT  
       2024-04-13 18:58:11 +08:00 via Android
    试试 intern ,和上面的 arena 一个路数,但是实现简单一些,本质都是避免多次分配。
    flyqie
        23
    flyqie  
       2024-04-14 14:38:19 +08:00 via Android
    挺好奇,是什么场景会有如此巨大的 grpc bytes 需求给到 java 服务端?
    dsvshx
        24
    dsvshx  
    OP
       2024-04-15 10:55:38 +08:00
    @ccde8259 思路是这个,就是说目前 grpc 的 api 不支持分配到非堆,所以想看看我说的那个用 netty 实现的思路行不行
    dsvshx
        25
    dsvshx  
    OP
       2024-04-15 10:55:51 +08:00
    @flyqie 消息队列的场景
    dsvshx
        26
    dsvshx  
    OP
       2024-04-15 10:57:13 +08:00
    @momo24672 这个解决不了,protobuf 里面的 bytes 还是会分配对堆内存的问题
    dsvshx
        27
    dsvshx  
    OP
       2024-04-15 11:02:08 +08:00
    @iseki proto 编解码工作量还行,接口不多,而且工作是一次性的。至于 API 这个,说多了都是泪,需要适配客户端,客户端升级不了。
    能再详细说一下 arena 是什么原理,怎么用吗?
    ccde8259
        28
    ccde8259  
       2024-04-15 12:19:25 +08:00
    @dsvshx 想完全避免内存分配的话……从 mmap 把数据往外撸的地方就得下手……TCP 你还不知道读多长……unsafe malloc 难写
    dsvshx
        29
    dsvshx  
    OP
       2024-04-15 16:44:34 +08:00
    @ccde8259 这个我理解 netty 已经给封装好了?各种 handler ,直接处理 ByteBuf 就行了
    iseki
        30
    iseki  
       2024-04-15 22:37:43 +08:00
    话说,你的序列化反序列化 API 不支持按流处理吗?比如说我经常处理大体积的 JSON ,大一点一个对象可能 1G+,数据会在反序列化过程中被处理,比如说可能有去重之类的逻辑。 @dsvshx
    dsvshx
        31
    dsvshx  
    OP
       2024-04-18 17:54:39 +08:00
    @iseki 主要还是 grpc 框架的限制,目前是不支持这种方式的。然后就算是在流中处理,他也是需要占用堆内存,还是解决不了 GC 的问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2619 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 06:08 · PVG 14:08 · LAX 23:08 · JFK 02:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.