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

356 天前
 LemonLeon
这是一个开源的 chatgpt 接口转发系统,使用 Go 程序编写。

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

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

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

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

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

也欢迎各位佬在评论区提提建议!!
4212 次点击
所在节点    Go 编程语言
41 条回复
ppto
356 天前
@LemonLeon 客户端的端口数占满了嘛?
cosiner
356 天前
还不如先看日志,确定问题发生在哪个阶段
liuzonghao
356 天前
0x676e67
356 天前
vw50 算一卦
dw2693734d
356 天前
io 的问题吧,用这个 pprof 一下: github.com/felixge/fgprof
lasuar
356 天前
绝大部分项目最快遇到瓶颈的位置都是 IO (一般是上行带宽/DB ),其次是某个/些对象的锁竞争激烈,最后是第三方 API 频率限制,最最后是各种原因导致的 CPU/内存资源不足。

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

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

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

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

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

另外关于 #19 讨论的锁,相关代码是 oneapi 向外发起链接的逻辑。虽然 net/transport 内部维护的连接池确实是有互斥锁的,但这里 race condition 非常弱。最差的情况等价于是退化到 openai 服务器不支持 keepalive ,每次都需要建立连接。而且每秒不到 100 的并发,基本不可能触发死锁。
LemonLeon
353 天前
@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
353 天前
xabclink
353 天前
@LemonLeon 你说的是那个 https://proxyxai.com 吧 , 确实很强大
unfurl
353 天前
看到用的是 locust… 我们内部是禁止使用该工具的,它自身存在性能问题,压不上去
kuanat
353 天前
@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 之类的方式来定位瓶颈了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/998648

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX