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

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

  •  
  •   dsvshx · 17 天前 · 2026 次点击

    背景:

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

    方案

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

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

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

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

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

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