V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 2 页 / 共 65 页
回复总数  1298
1  2  3  4  5  6  7  8  9  10 ... 65  
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
> 江湖不是技术来技术去 是打打杀杀 人情世故 天变了

@ugpu 其实除了元素数量太时 gc 的 cpu 压力, 以及其他类型的强 cpu 压力类的项目, go 还真是都能做. 街篮那种非重度 fps+类 moba 的, 房间人数不多, 也没有农药那种满地图建筑/怪物/兵之类的各种压力, 用 go 都绰绰有余. 那些中小游戏甚至便单机的游戏就更不在话下了. 所以很多团队用 go 是非常合理的技术选型, 跟人情世故没啥关系, 不用这么委屈自己
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@librasolo #36

leaf 看上去甚至比 zinx 还差一些, 可能 zinx 毕竟要做课程所以毕竟略带着点"学院派"的工整. leaf 的年代更早点, 那时候 go 社区里轮子少, 国内 go 社区也非常活跃, 论坛和技术群都大把人, 随便写点什么宣传下, star 就上来了.
但那个年代的玩具项目多. 游戏行业的 go 服务端更是属于很多团队在摸索中的状态, 不能怪社区水平, 而是历史局限性
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@shellus #32
能对框架选型发问的用户, 绝大部分人不是用来做大型游戏, 这类需求的主要部分其实就是个网络库. 性能需求不大, golang 足够, 而且相比于 c++或者 rust, 开发效率爽死

真要是做大型游戏, 基本都自家团队内解决选型和研发问题了, 自家负责技术的人如果连框架架构的能力都没有的话还做毛的大型游戏, 如果大项目还要外面咨询选型的说明这项目从立项就凉了 80%了, 除非中间把技术的人换掉
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@guanzhangzhang #33

Call 是请求需要对方响应, Notify 是只发消息不需要对方响应, Call 是同步的, CallAsync 是异步的.
client/server 两端都可以主动发起 Call/Notify.
arpc 是涵盖了传统游戏服务器网络库和 rpc 模式的, 所以支持的业务场景更广更好用.

要不你先看下 arpc 的例子吧, 有提问这功夫, 花几分钟看下 Readme 早就懂了都可以开始写逻辑了:
https://github.com/lesismal/arpc?tab=readme-ov-file#quick-start
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@guanzhangzhang #29

如果你们客户端使用的引擎语言支持标准 js, 可以试试用 arpc, 处理你说的"同步"这种问题, 比 msgHandler 简单方便多了.
13 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@guanzhangzhang #29

客户端的交互, 绝大多数项目, 本质上都是异步的.
因为客户端发消息基本上是在 eventloop 主线程触发的, 如果不是异步那就要卡渲染了, 客户端引擎的 eventloop 线程里的操作不应该有阻塞. 除非有特定需求是不需要主线程逻辑触发, 才可以考虑异步线程去同步 IO 的模式, 不影响 eventloop 主线程就行.

如果想"同步"代码, 要依赖不同的游戏引擎, 或者说, 主要是依赖你编写这部分逻辑使用的语言以及这个语言对"同步"支持的程度.
这里说的"同步"不是指同步 IO, 而是指类似协程这些让代码顺序看上去同步的语言特性. 比如 lua 可以用协程, js 可以用 Promise 甚至 async await? 一些引擎自带的语言并非标准语言而是变体, 那么则要看这些具体语言的特性了.

传统的游戏客户端服务端网络协议主要是命令号, 命令号对应 handler, 但每个 msg 和可能收到的响应不方便一一对应, 所以即使是协程/Promise 之类的看上去"同步"代码, 传统游戏协议也不方便把发送的 msg 和响应对应起来做这个同步.

arpc 的 js client 是默认支持了 Promise, 并且本身是通过 rpc 的方式实现的协议, 每个 call 都与响应一一对应, 所以可以这样用, 这样看上去就比较方便了:
https://github.com/lesismal/arpc/blob/master/examples/webchat/chat.html#L81
14 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@asuraa 再补充一下, nginx 的多进程也有不均衡的问题, 比如很多连接均匀连到不同的 worker 进程上, 但是很多断开了, 剩下的大量连接分布在少量 worker 进程上, 仍然是不均衡的.
14 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@asuraa BTW, nbio 每个 conn 上一个执行队列配合通用协程池的方案, 对于其他语言也是适用的. 传统 c++框架, 为了保证时序, 多数是逻辑单线程, 对于有状态和多连接交互类并且 cpu 高消耗的业务就未必能充分发挥 cpu 了, 都可以考虑我这种方式优化.
nginx 这种为了提升 cpu 利用率搞 fork 多进程, 因为业务是 web 类, 在 nginx 这一层面连接之间没什么交互, 多进程的方式又可以避免多线程的锁竞争, 是非常合理的. 但游戏类业务多数涉及交互广播之类的, 就不太适合 fork 多进程的方式了
14 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@asuraa #24

> 1.2048 这个值是有考究的

硬件配置差别很大, 1c1g/16c64g/36c/512g, 对于这点协程数量的压力完全不同, 所以不论什么吋间规格都用 2048 写死了, 真没什么解释的必要.

> 2.worker 分配不均匀问题

可以参考下我的 nbio 里的实现:
https://github.com/lesismal/nbio/blob/master/conn.go#L185
原理是, 每个 conn 上一个执行队列, 每个 job 入队列并判断是不是队首, 如果是队首, 就用通用的协程池去异步循环处理挨个取出直到没有任务; 如果不是队首, 说明已有协程在循环执行, 则入队后直接返回并等待被执行即可. 这种实现下, 协程池的部分是不受限制的, 如果对协程池 size 没有限制就直接 go func(), 如果对协程池 size 有限制, 则协程池实现的部分限制即可.
游戏服务, 通常对最大在线量本身就有限制, 在 Acceptor 或者 login 的部分就可以做限制, 而且通常单节点在线量不会超大, 例如单节点 5k-5w 在线量, 通常都是 1w 以内, 而这种在线量在 8c16g 的机器上即使对应的每个连接一个异步任务的协程也压力不大, 而且 go func()执行完当前任务队列没任务后即可退出所以不需要持续占用协程, 再考虑实际并行和下游基础设施连接池之类的, 临时异步任务并发度对应的协程数量也多数小于实际连接数.
协程池的实现大概几种, 早期 ants 那种自己用 cond_t+队列或者 chan 配合 idle time idle size 的常驻协程方案并没有性能优势而且常驻协程的释放不及时, 而且 cond_t+毒烈或者 chan 的性能都有些损失, 除了可以限制协程数量, 绝大多数场景不如 go func().
字节家的 workerpool 是 size 限制 + list + go func() , 新任务判断当前协程池 size 并发度, 如果没达到限制就直接 go func()去循环执行 list 队列里的 job, 如果达到 size 限制里就入 list 队列等待被执行, 每个 go func()循环执行直到 list 队列里没有 job 里就退出. 这种方案是通用协程池, size 设置为 1 则也可以直接作为我上面说的 nbio 里每个 conn 的执行队列的方案, nbio 每个 conn 的执行队列没有用 list 而是用 slice, 是因为考虑单个 conn 任务数量不会太大所以 slice len 也不会太大, 所以用 slice 做队列复用并且省去了 pool get/put 之类的额外逻辑, 相比于 list 可能略有优势.
较高版本的 go, runtime 协程复用很好了, 如果不考虑 size 限制, 其他协程池实现基本都不如直接 go func()性能好, 字节家的这种 go func()好处是只比 go func()多了一点点逻辑但非常接近 go func()了, 但也有个缺点, 就是对 list 队列 size 没有限制, 如果下游阻塞并且上游不断有新的 job 进来, 理论上是存在无限增长的风险的.
lxzan https://github.com/lxzan/concurrency 与字节的类似, 用于队列的数据结构略有不同.
nbio 的协程池为了更均衡, 达到 size 限制前直接 go func(), 达到 size 了, 用 chan 做队列, chan 的好处是满了就阻塞了可以让上下游自动均衡, 规避了字节家 workerpool 的缺陷:
https://github.com/lesismal/nbio/blob/master/taskpool/taskpool.go
golang runtime, 例如 8c16g 这种配置, 通常的场景下, 10w 个协程数量都算还好, 压力不大. 而且 nbio 的协程池 size 默认是按核心数初始化, 多数也是跑不满 size 的. 而且用户也可以自己随便配置用什么协程池, 因为不管用什么协程池, nbio 的每个 conn 已经保证了自己的有序所以不需要协程池去保证顺序

> 3.代码估计没看仔细,是只有获取没创建的才需要初始化加锁的,后续获取是不需要的

这个确实没仔细看, 只是简单扫了几眼, 你说的是对的

> 这模块我们自己项目也在用,我们一百多万 dau ,跑的好好的。不过代码是比较简单,大家相互讨论印证也没问题。挺好的技术交流

因为 golang 本身性能还不错, 即使框架代码一般, 对于绝大多数的非性能和开销敏感的业务而言也是过剩的.
但这并不意味着这种框架本身优秀.
而且如 OP 在#4 和我在#11 说的, 简单游戏业务, 游戏脚手架就那几个简单的组建, 网络库的部分算是大头的了, 其他的太多现成的甚至标准库直接就够用了, 真的挺简单的 😂😂😂
15 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
> 还是 sqlx ,看到 gen 类型的框架我就头痛

@securityCoding sqlx 也是挺麻烦的, 一些没必要的搞得太多, 懒得浪费时间学它, 所以我自己搞了个. 性能最好的肯定是生成代码的这类了, 但用起来也是真不爽
15 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@guanzhangzhang 游戏客户端跟 golang 没太大直接关系了, 一些开源项目客户端和服务端都提供了, 但都是游戏功能本身比较多, 游戏业务逻辑本身并不算是通用的框架部分. 游戏客户端引擎是框架的主要部分, 其他的加上网络和热更之类的就算足够做普通项目的脚手架了能对标 golang 这些忽悠人的脚手架项目, 复杂游戏的也是业务类的特殊需求也是不同类型不一样, 大型的多人海量元素同屏动作类之类的优化也是一大坨, 特效需求多的各种技术美术的需求 shader 优化.
不管是客户端还是服务端, 除了通用的简单脚手架的部分, 没有统一的框架, 各种类型游戏的特殊需求都加上的话就显得太重了不适合中小游戏, 比如 bigworld 去做卡牌和简单 arpg 完全是杀鸡用牛刀, 客户端服务端用这种大框架都很浪费反而开发效率和维护成本低. 如果特殊的不加上吧, 那些业务需求复杂的功能又都得自己造轮子, 简单脚手架提供的这点功能微不足道用处不大
15 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@asuraa 补充 #12

timer 里这个 IDGen 是存在溢出可能性的, 运气不好的话可能遇到任务丢了或者相关的:
https://github.com/aceld/zinx/blob/master/ztimer/timerscheduler.go#L69
https://github.com/aceld/zinx/blob/master/ztimer/timerscheduler.go#L32
而且 addTimer 相关的逻辑的锁粒度比较大, 这个锁完全可以在操作数据结构的小范围加上, 其他的那些时间计算与判断的应该放在锁的范围外边并发竞争的 cpu 浪费
15 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
@asuraa #6
zinx 属于脚手架, 和其他几个早期开源的游戏框架类似, 都是比较简单的封装, 而且有些封装其实是垃圾封装, 既没必要, 又可能浪费性能.

刚简单扫了几眼 zinx, 举下例子:
比如 timer, 标准库 time 包足够用了, 四叉堆性能足够好, 而且精确度最高, zinx 里面自己用时间轮自己搞个没必要, 而且时间轮一是精度低, 二是如果想精度高就要更小的轮间隔, 则 tick 频率高空转有点浪费 cpu. 另外, 看代码实现, 作者并不太注意性能细节, 例如创建一个新的定时器, 多次调用 time.Now(), 而这本是可以只 time.Now()一次复用的:
https://github.com/aceld/zinx/blob/master/ztimer/timerscheduler.go#L79
https://github.com/aceld/zinx/blob/master/ztimer/timer.go#L71
https://github.com/aceld/zinx/blob/master/ztimer/timewheel.go#L137
https://github.com/aceld/zinx/blob/master/ztimer/timewheel.go#L96
https://github.com/aceld/zinx/blob/master/ztimer/timer.go#L58

再看下异步任务协程的吧, 讲真, 刚看了几眼, 之前没看过, 都没想到能写这么差.
固定 2048 个, 够不够用另说, 写死这个本身就是挺不可思议的设计了:
https://github.com/aceld/zinx/blob/master/zasync_op/async_op.go#L45
按 opId 取模作 index 取对应 worker, 业务层对 opId 可以各种定义, 模块 id userId 之类的, 如果大量任务的 hash 不均匀则会导致单个 worker 协程忙死其他的 worker 闲着, 任务调度分配不均衡, cpu 利用率和性能存在不稳定的可能:
https://github.com/aceld/zinx/blob/master/zasync_op/async_op.go#L53
https://github.com/aceld/zinx/blob/master/zasync_op/async_op.go#L67
而且就这个 getCurWorker()方法, 你都 array 了, 抛开上面说的调度分配不均衡问题, 考虑到满载性能利用, 还不如启动阶段创建好都跑起来, getCurWorker()直接就 index, 连锁都不用加

更多的不看了, 这是属于造屎山的项目, 但作者是作技术培训, 用于简单教学之类的也无可厚非.
如果是游戏小白, 或者做中小项目的团队拿来用, 可以拿来用用图个省事, 也算凑合吧, 毕竟解决了一些人的轮子问题.
如果是对自己技术有追求的, 或者规模大点的项目对代码性能有要求的, 就不建议用了, 也不建议推荐给其他人用.
15 天前
回复了 ugpu 创建的主题 Go 编程语言 Golang 游戏开发框架选型
> 主要是网络 & 日志 & orm & 分布式. 这几个点 不知道有啥可以匹配的.

@ugpu

网络部分可以试试我的这个, 本质是个网络库, 游戏行业缺少统一的技术规范, 所以蹭热度用来 rpc 的名字:
https://github.com/lesismal/arpc
性能应该也足够用:
https://colobu.com/2022/07/31/2022-rpc-frameworks-benchmarks/
网络协议随便你选, tcp,websocket,kcp,quic 各种, 只要实现 net.Listener 和 net.Conn 就可以了
client 只有 go 和 javascript, 其他语言如果自己熟悉这块自己实现一份也不难

golang 没有好用的 orm, 游戏业务的 sql 需求也不是特别大所以 raw sql 足够了, raw sql 确实有点麻烦, 可以试试我这个:
https://github.com/lesismal/sqlw
能省掉字段绑定的很多麻烦

日志:
随便选哪个知名的都可以, zap zerolog 之类的各种, 功能和性能都足够, 自己根据自己喜好稍微封装下细节就可以了

分布式:
除了 bigworld 那种大型游戏有相对固定的框架体系, 其他多数游戏都没有个固定的, 以上所述的几个点, 其实都算是脚手架. 但 bigworld 这种, 单位太多, gc 压力太大了, golang 并不适合.
其他的 fps moba 之类的, 都算是比较容易对房间扩容的, 匹配服调度下然后战斗服堆机器就可以了.
还有一些类型, 大型全球同服 slg, 一些 arpg 之类的挂机游戏, 或者其他一些依赖服务端做大量数学计算的或者服务端渲染的, 也都是需要自家定制优化.
而分布式本身, 抛开这些特殊的业务优化需求, 分布式主要是通信, 仍然是网络库在不同服务之间建立连接收发指令处理逻辑就可以了. 至于大概 10 年前一些 web 领域渗透到游戏里的技术, 例如服务注册与发现, 这至少二十年前游戏行业就有了, 集群里通常都有特定的服务承担特定的集群管理角色, 只是不像 web 领域那样技术栈通用和用户广泛所以没有那么被人熟知.
OP 没有提到我上面说的几种特殊业务类需要, 所以对于 OP 而言, 分布式的需求其实自己随便搞搞就行了, 比如用 arpc 随便写写就好了.
@SeleiXi #7 嗯嗯, 那就完全没问题了
> 然后因为 score 不会动态更新所以就想着用其他方案了

@SeleiXi 更新用 pipeline zrem+zad, 先删再重新添加, 就可以了 🤣
zset 足够了, timestamp 做 score, 没必要用其他的, 别想多了

> 要用键查询对应的值 & 知道前面还有多少个键值对是先于他创建且没有被删除的

zrank 查询对应的值的时候就会返回这个值的排序下标(整个 zset 排序下标从 0 开始), 默认 timestamp 升序, zrank 返回的值减 1 就知道前面有多少个是先于它了.
你保证每组操作原子性, 比如单次如果需要多个操作就 pipeline, 这样就能保证你每组操作在 redis 里是串行执行的, 所以你当前能查到的所有结果肯定就是对应的存在的未被删除的数据集的

> 所以数据结构需要是有序的),只需要先入先出(不需要从中间删元素)

zset 就是按 score 排序的, 你只要自己处理每次先删除第一个就行了
> 为什么两个加起来一个 issue 都没有?

@stabc
不是一个 issue 都没有, 而是一个 Open 状态的都没有. 兄弟你查 Closed 状态的, 到目前 arpc 有 47 个, nbio 有 242 个.
一个 Open 状态的都没有有两个原因:
1. 作者个人形单影只, 宣传力度不够, 吹个牛说, 虽然功能性能个方面都算是同领域 Top 级的, 但知名度太低, 知道的人少, 来 issue 的人也就少. 看看其他一些同领域的, 功能比这个少, 性能不比这个强, 但是 star 比这多的多, issue 不解决比这多的多
2. 每次有 issue, 作者都尽量迅速答复和解决, 所以除了极个别迷案无果而终, 绝大多数都已经解决了然后就关闭了
@prosgtsr 这算是我职业生涯里的最佳命名了

@spritecn 末期末期, 还没全退, 但是准备中...

@kingcanfish "激情探讨", 做清醒的自己~

@huig 来来来, 兄弟一起干 BA
@gongquanlin #2 感谢支持! 欢迎多来交流!
1  2  3  4  5  6  7  8  9  10 ... 65  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5443 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 23ms · UTC 07:41 · PVG 15:41 · LAX 23:41 · JFK 02:41
Developed with CodeLauncher
♥ Do have faith in what you're doing.