V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
LemonLeon
V2EX  ›  Go 编程语言

付费求一个 go 项目程序优化方案,给系统提高并发

  •  
  •   LemonLeon · 141 天前 · 3726 次点击
    这是一个创建于 141 天前的主题,其中的信息可能已经有所发展或是发生改变。
    这是一个开源的 chatgpt 接口转发系统,使用 Go 程序编写。

    https://github.com/songquanpeng/one-api

    在实际使用过程中,高并发已经达到极限,但是系统的资源利用率非常低。我需要一个工程师协助我,找出并发限制的瓶颈,加以优化。

    背景参考:
    centos 4h8g oneapi
    实测并发量 2000 rpm 附近

    提升系统性能可以有效增加并发量,另一个系统 6h8g 并发量可以达到 5000 rpm 。
    但是高并发的时候,cpu 资源利用率并不高,不清楚限制的因素到底是什么。

    -
    大家的时间都很宝贵,我会为此付费。评论区留下你的联系方式

    也欢迎各位佬在评论区提提建议!!
    41 条回复    2023-12-18 10:10:41 +08:00
    Immortal
        1
    Immortal  
       141 天前
    有没有可能是 gpt 到达限制了
    试试其他平台的接口然后测下并发?
    ETiV
        2
    ETiV  
       141 天前 via iPhone   ❤️ 1
    ???
    你直接问 GPT 不就好了
    LemonLeon
        3
    LemonLeon  
    OP
       141 天前
    @ETiV 不行,gpt 做不了这个任务
    LemonLeon
        4
    LemonLeon  
    OP
       141 天前
    @Immortal 不会是 GPT 限制,后台官方接口的数量成倍增加,并发量没有变化。

    目前可以确定是服务器资源的问题,更准确说是 oneapi 程序没有很好的利用系统资源
    dxb848295325
        5
    dxb848295325  
       141 天前
    我有兴趣,微信 18628069445
    kidlj
        6
    kidlj  
       141 天前
    瓶颈八成在 IO ,特别是网络 IO 。
    deorth
        7
    deorth  
       141 天前 via Android
    加服务器
    idblife
        8
    idblife  
       141 天前
    关注一下,这个项目不错
    tonywangcn
        9
    tonywangcn  
       141 天前
    感兴趣 wx dG9ueXdhbmdpbw==
    gongquanlin
        10
    gongquanlin  
       141 天前
    之前研究了下这个项目源码,到处都是 case xxxx ,维护起来真 tm 费劲。
    能开源确实很值得鼓励,单纯的是我认为里面的设计很难接受,正在开发替代品
    lzgshsj
        11
    lzgshsj  
       141 天前
    作者就在 v2
    fengxsong
        12
    fengxsong  
       141 天前
    ```
    if err != nil {
    fatalLog(...)
    }
    return err
    ```

    😄
    kuaner
        13
    kuaner  
       141 天前
    直接联系作者给出个 Pro 版本
    RockChinQ
        14
    RockChinQ  
       141 天前
    估计是 上游渠道较慢 导致的利用率低,这几天 OpenAI 确实经常 429 。建议你,后端搭建多个 oneapi 实例 分别接入不同的 OpenAI 账号,最好是分布在不同的服务器上,前端再用一个 oneapi 来做负载均衡。
    voidmnwzp
        15
    voidmnwzp  
       141 天前 via iPhone
    用单个 g 重新封装 epoll 试试
    luguhu
        16
    luguhu  
       141 天前
    既然明确资源利用率非常低, 那不如直接起多个进程做负载均衡. 简单快捷
    lifei6671
        17
    lifei6671  
       141 天前
    你是怎么确定并发已经达到了极限的?
    cosiner
        18
    cosiner  
       141 天前
    cpu, mem 都没满,正常应该是可以支撑更多请求的,不会是带宽不够吧
    monsterxx03
        19
    monsterxx03  
       141 天前   ❤️ 3
    确定上游没到极限的话,大概率是锁竞争过多导致 cpu 利用率上不去。
    给个思路,runtime.SetMutexProfileFraction(5) 打开 mutex profiling
    然后看 pprof 的 mutex 火焰图,看瓶颈在哪。

    简单看了下代码 https://github.com/songquanpeng/one-api/blob/01f7b0186fae589e0e5fb83ab0e6d033ba5339aa/controller/relay-text.go#L376 比如这个地方直接用了一个全局的 httpClient, 里面是从连接池里取连接时候是有锁的,之前碰到过这个问题,优化办法是根据 cpu 核心数实例化多个 client, 每次随机挑一个发请求。

    另外 http.Client 的 MaxIdleConnsPerHost 默认值是 2 ,conn 会被频繁回收,试试设置成几百。
    1423
        20
    1423  
       141 天前
    看起来程序员确实是太多了
    ppto
        21
    ppto  
       141 天前
    @LemonLeon 客户端的端口数占满了嘛?
    cosiner
        22
    cosiner  
       141 天前
    还不如先看日志,确定问题发生在哪个阶段
    liuzonghao
        23
    liuzonghao  
       141 天前
    gngppz
        24
    gngppz  
       141 天前
    vw50 算一卦
    dw2693734d
        25
    dw2693734d  
       141 天前
    io 的问题吧,用这个 pprof 一下: github.com/felixge/fgprof
    lasuar
        26
    lasuar  
       141 天前
    绝大部分项目最快遇到瓶颈的位置都是 IO (一般是上行带宽/DB ),其次是某个/些对象的锁竞争激烈,最后是第三方 API 频率限制,最最后是各种原因导致的 CPU/内存资源不足。

    一种简单的定位方式是在对应 API 内可能耗时的调用位置前后添加 log 打印时间,观察耗时。
    waltcow
        27
    waltcow  
       141 天前
    @gongquanlin https://github.com/MartialBE/one-api/tree/new

    最近看了下这个 fork 的重构,感觉还可以
    isSamle
        28
    isSamle  
       140 天前
    应该是项目做了限制吧,项目里面故意加锁避免高频用量?还没看源码随机猜的。
    LemonLeon
        29
    LemonLeon  
    OP
       139 天前
    @tonywangcn 空的,麻烦重新发一下
    LemonLeon
        30
    LemonLeon  
    OP
       139 天前
    @RockChinQ 不在上游,但是分布式后面是要考虑的
    LemonLeon
        31
    LemonLeon  
    OP
       139 天前
    @monsterxx03 你好,方便远程帮我排查一下吗
    LemonLeon
        32
    LemonLeon  
    OP
       139 天前
    这里统一感谢大家的回复!还没有找到优化方案,目前正在尝试增加机器性能。

    如果你有时间远程协助我,可以添加我的联系方式 v:bitdark
    tonywangcn
        33
    tonywangcn  
       139 天前
    @LemonLeon base64 转下
    ryalu
        34
    ryalu  
       139 天前
    简单看了下,项目挺简单的,代码量也不多。直接找个有经验深点的重写下应该就行了
    kuanat
        35
    kuanat  
       138 天前
    信息有点少,最好能把部署环境、压测方式都贴上来,这样便于分析。

    主要是 4c8G -> 6c8G 就能提升并发,这个现象很难解释。表面上只能判定说,内存不是瓶颈,与 GC 或者内存泄漏无关。

    而且 2000/5000 rpm 的尺度是分钟,每秒也就是 30~80 的水平。这个数值过于低了,一般是业务逻辑造成的,而不是技术栈造成的。

    另外关于 #19 讨论的锁,相关代码是 oneapi 向外发起链接的逻辑。虽然 net/transport 内部维护的连接池确实是有互斥锁的,但这里 race condition 非常弱。最差的情况等价于是退化到 openai 服务器不支持 keepalive ,每次都需要建立连接。而且每秒不到 100 的并发,基本不可能触发死锁。
    LemonLeon
        36
    LemonLeon  
    OP
       138 天前
    @kuanat 感谢回复,我调整了测试方法。以下是最新的情况:

    1.1 )部署环境:Centos7 Mysql8 nodev16.16.0 go1.20.2
    1.2 )测试方法:
    Windows Python3.8.10 locust 1000 用户并发,测试结果稳定在 300qps 附近(单个 locust 终端)




    2 ) 2c4g 4c8g 6c16g ,测出来的并发量都是 300 附近。内存,磁盘读写,带宽压力并不大

    3 )我推测的原因有两个,要么在 cpu ,要么在数据库。通过提高数据库的最大连接数,并发线程有一点提高。

    4 )当用户端发起超多请求时候,数据库提前打开连接等待,然后系统就超出 mysql 最大连接数,并发降低下来。这是我的推测。

    如果是这样,那一定有一种方案,是把这些任务都放在内存中处理完了,最后再批量插入数据库中。

    5 )我看到有一些中转系统就是基于内存处理的,欢迎大家指正和指路
    LemonLeon
        37
    LemonLeon  
    OP
       138 天前
    xabclink
        38
    xabclink  
       138 天前
    @LemonLeon 你说的是那个 https://proxyxai.com 吧 , 确实很强大
    unfurl
        39
    unfurl  
       138 天前
    看到用的是 locust… 我们内部是禁止使用该工具的,它自身存在性能问题,压不上去
    kuanat
        40
    kuanat  
       138 天前
    @LemonLeon #36

    我做了个简单推理,可能需要你实际测试一下。

    既然加 cpu 和内存不影响 rps 说明大概率瓶颈应该是在 IO 了。我大致看了一下处理流程,涉及到 IO 的操作就是日志相关的。

    https://github.com/songquanpeng/one-api/blob/366b82128f89a328f096da6951cbafebb6b0060f/controller/relay-text.go#L410

    这段代码主要功能是在请求结束后结算用量,然后记录。记录的内容本地文件有一份,数据库有一份。本地那一份肯定比数据库快,所以不考虑了。

    数据库 IO 涉及三个操作:

    https://imgur.com/a/OAPZXsl

    如果是默认配置( common.LogConsumeEnabled=true ),RecordConsumeLog 会产生一次插入操作;

    默认 BATCH_UPDATE_ENABLED=false ,那么 UpdateUserUsedQuotaAndRequestCount 和 UpdateChannelUsedQuota 各自都会产生一次查找更新操作。

    等于说每个请求都伴随三次数据库写操作。默认 SQL_MAX_OPEN_CONNS = 1000 ,理论并发在 333 左右,和实测比较接近。

    当然前提是这三个数据库操作能够在 1s 之内完成(大概率是的)。这个事情不太容易确定,因为 locust 记录的是完成请求的总时间,并不知道中继请求和数据库操作的时间占比。

    如果做个粗略估计的话,观察第响应时间的中位数,在测试刚刚开始的时候是 2s ,之后略低于 3s 。猜测当数据库 IO 达到瓶颈的时候,平均要多花接近 1s 等待。

    要验证这个猜想,简单的方式是调整 BATCH_UPDATE_ENABLED 启用批量更新,单请求的数据库写入会降至 1 次。或者再提高 SQL_MAX_OPEN_CONNS 的数值。同时可以查看本地日志和 sql 服务器的日志,辅助确认 sql 服务器不是瓶颈。

    假如上面的方法无效,那就要考虑 pprof 之类的方式来定位瓶颈了。
    guoguoyu
        41
    guoguoyu  
       131 天前
    可以试试我的方案,我从 8 9 月就开始处理这个问题了,经过几轮优化,现在 2h2g 都能有很高 QPS ,也帮过好多个人做了优化,看下优化效果: https://www.bilibili.com/video/BV1sj411W71m/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2749 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 10:24 · PVG 18:24 · LAX 03:24 · JFK 06:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.