工作5年C++服务器,转GO的感触,GO服务器业余时间整理的,用时2个月,框架到细节

weixin_38060297 2019-09-20 10:49:00
之前c++服务器,是端游转过手游来的,在我不断的优化下,成功从c++98到c++11,以及32位到64位的转变,从端游服务器,转到手游服务器,上线人数单服1W+没太大压力。
go是我业余的时候接触到的,在现在大环境下,高并发和跨平台是现在开发游戏服务器的主流,之前那套c++说实在是够用,只要能达到那个量,单服性能极致,我只服c++。
skynet+lua,c底层,lua逻辑,我也过来过c++底层,lua逻辑,但实际效果是lua并不是我想要去写的,可能我对lua脚本不感兴趣。go做的事情和skynet做的大同小异,都是携程,抢占式g调度模式,go 有个goroutine和channel,skynet lua虚拟机。
当时设计go的时候,由于跟c++大轮子有点出路,c++是循环轮子模式,单逻辑处理能力有目共睹,再设计go的时候我选择了actor模式,每个角色都相互独立,这点携程和信道立功了。全服中,没用到锁的地方不用锁,全服用到锁的地方就一两处,acotr模式立功了,每个角色有各自的消息队列,接受和处理信息。
大体主流网络模式是rpc,不过不是第三方的rpc,而是自己的rpc,不用protbuf那样定义消息体,只需要类型即可,通过refeclt go里面神奇的东西,用好事半功倍,在c++里面只能用宏来实现,或者用template,当然通信方面也支持protobuf客户端与服务器之间的传输。
[image.png](https://static.studygolang.com/180113/aad06eeec4c428b80ac82d46a4cff3af)

...全文
56 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
weixin_38092506 2019-09-20
  • 打赏
  • 举报
回复
hi,channel本身内部有锁。不过效率比较高点。本身gorotinue就是抢占模式,假如gorotinue异步驱动,在高并发的情况下。轮训到每个玩家就少了,我看了你的代码,你的比较像workman,生产者消费者。你的是io过来,放消息队列,然后各个模块去取消息队列。我现在是一个玩家一个actor,能轮训的机会大一点,在同步的时候可以考虑一个actor取推,比如地图,里面的objcet全部由map来推送。主要还是抢占模式。。。所以要考虑分布式。。。地图可以不支持内存同步(本身逻辑太依赖内存),但可以地图分布。账号服务器,游戏有些服发现,踢人等,不能分布也没事。做游戏可以考虑,多开几个服务器的方向,一个服务器1000,2个服务器就是2000,但是他们是分布式同一服务器
weixin_38096317 2019-09-20
  • 打赏
  • 举报
回复
还有就是信道设计不合理也是个问题。假如你多个actor取同一信道,避免这种,可以多个actor发送信道内容,但是只能取一个,不然其他会锁死。。。生产者和消费者,生产者可以比消费者快,不然其他actor会锁死
weixin_38098817 2019-09-20
  • 打赏
  • 举报
回复
golang 1.11 源代码runtime\chan.go:181行 channel使用lock的地方.这个lock就是lock_sema.go实现的.而golang标准库提供的sync/mutex包也是使用lock_sema.go实现的lock.这个就是乐观互斥锁.我不知道"channel锁比外面的cas 无锁队列要强的"这个结论是怎么得出来的.或者说你根本没有看过channel的源代码.
weixin_38104933 2019-09-20
  • 打赏
  • 举报
回复
其实到底是用channel还是sync 有个决策树可以判断的 并不是说channel就比sync好了
weixin_38111164 2019-09-20
  • 打赏
  • 举报
回复
@cynic 差4倍,在考虑要不要用,go的channel的锁,已经被抽象成cas的模式,就是还有个信号量在。。。性能差一点就估计是这个信号量,锁住了流程。用disruptor,select的模式又要考虑修改下,channel是简单
weixin_38112450 2019-09-20
  • 打赏
  • 举报
回复
NOW go 信道是 cas模式,除了不是环形队列,加信号量以外和 java 著名的 disruptor相差不大,在多生产者和单消费者模式下。下图是对比数据 go disruptor和chan的性能对比,第一个是disruptor
weixin_38112462 2019-09-20
  • 打赏
  • 举报
回复
package main import ( "time" "fmt" "sync" "runtime" "github.com/smartystreets/go-disruptor" ) const ( RingBufferSize = 1024 * 64 RingBufferMask = RingBufferSize - 1 ReserveOne = 1 ReserveMany = 16 ReserveManyDelta = ReserveMany - 1 DisruptorCleanup = time.Millisecond * 10 ) var ringBuffer = [RingBufferSize]int64{} var num = 5 func main() { NumPublishers := num //runtime.NumCPU() totalIterations := int64(1000 * 1000 * 20) iterations := totalIterations / int64(NumPublishers) totalIterations = iterations * int64(NumPublishers) fmt.Printf("Total: %d, Iterations: %d, Publisher: %d, Consumer: 1\n", totalIterations, iterations, NumPublishers) runtime.GOMAXPROCS(NumPublishers) var consumer = &countConsumer{TotalIterations: totalIterations, Count: 0} consumer.WG.Add(1) controller := disruptor.Configure(RingBufferSize).WithConsumerGroup(consumer).BuildShared() controller.Start() defer controller.Stop() var wg sync.WaitGroup wg.Add(NumPublishers + 1) var sendWG sync.WaitGroup sendWG.Add(NumPublishers) for i := 0; i < NumPublishers; i++ { go func() { writer := controller.Writer() wg.Done() wg.Wait() current1 := disruptor.InitialSequenceValue for current1 < iterations { current := writer.Reserve(1) ringBuffer[current&RingBufferMask] = current writer.Commit(current, current) current1++ } sendWG.Done() }() } wg.Done() t := time.Now().UnixNano() wg.Wait() //waiting for ready as a barrier fmt.Println("start to publish") sendWG.Wait() fmt.Println("Finished to publish") consumer.WG.Wait() fmt.Println("Finished to consume") //waiting for consumer t = (time.Now().UnixNano() - t) / 1000000 //ms fmt.Printf("opsPerSecond: %d\n", totalIterations*1000/t) main1() } type countConsumer struct { Count int64 TotalIterations int64 WG sync.WaitGroup } func (cc *countConsumer) Consume(lower, upper int64) { for lower <= upper { message := ringBuffer[lower&RingBufferMask] if message != lower { warning := fmt.Sprintf("\nRace condition--Sequence: %d, Message: %d\n", lower, message) fmt.Printf(warning) panic(warning) } lower++ cc.Count++ //fmt.Printf("count: %d, message: %d\n", cc.Count-1, message) if cc.Count == cc.TotalIterations { cc.WG.Done() return } } } func main1() { NumPublishers := num //runtime.NumCPU() totalIterations := int64(1000 * 1000 * 20) iterations := totalIterations / int64(NumPublishers) totalIterations = iterations * int64(NumPublishers) channel := make(chan int64, 1024*64) var wg sync.WaitGroup wg.Add(NumPublishers + 1) var readerWG sync.WaitGroup readerWG.Add(1) for i := 0; i < NumPublishers; i++ { go func() { wg.Done() wg.Wait() for i := int64(0); i < iterations; { channel <- i i++ } }() } go func() { for i := int64(0); i < totalIterations; i++ { select { case msg := <-channel: if NumPublishers == 1 && msg != i { //panic("Out of sequence") } } } readerWG.Done() }() wg.Done() t := time.Now().UnixNano() wg.Wait() readerWG.Wait() t = (time.Now().UnixNano() - t) / 1000000 //ms fmt.Printf("opsPerSecond: %d\n", totalIterations*1000/t) }
weixin_38116177 2019-09-20
  • 打赏
  • 举报
回复
亲测,直接加锁要比使用通道效率高,不过我项目一般都是使用通道,因为这是go的设计哲学,Don’t communicate by shared memory. Instead, share memory by communicating, 使用通道可以更好的解耦合,并发代码统一API入口。 但实际上是直接加锁,运行速度要快点!
weixin_38121667 2019-09-20
  • 打赏
  • 举报
回复
看了看大佬的讨论,我还是用加锁的channel好了🤓
weixin_38128732 2019-09-20
  • 打赏
  • 举报
回复
@xuelike</a> <a href="/user/huang2287832" title="@huang2287832">@huang2287832</a> 嗯,性能差不了多少,推介还是channel,简单,写法也简单。chan一般在多生产者,单一消费者的情况下。。。楼上说的枷锁。。。是什么操作。。。chanenl哪不是 cas 信号量的 数组。。。数组要比链表访问快,数组利用cpu cache,寄存器一般是64位。。访问数据就不会伪共享。。数组是连续的,cpu cache访问更快" name="content" class="comment-textarea" rows="8" style="width: 100%;"><a href="/user/xuelike" title="@xuelike">@xuelike</a> <a href="/user/huang2287832" title="@huang2287832">@huang2287832</a> 嗯,性能差不了多少,推介还是channel,简单,写法也简单。chan一般在多生产者,单一消费者的情况下。。。楼上说的枷锁。。。是什么操作。。。chanenl是 cas 信号量的 环形数组。。。数组要比链表访问快,数组利用cpu cache,寄存器一般是64位。。访问数据就不会伪共享。。数组是连续的,cpu cache访问更快。打错字了,见谅下
weixin_38135390 2019-09-20
  • 打赏
  • 举报
回复
LMAX的disruptor么? disruptor在多消费者多生产者的情境下性能本来就不高,单消费者单生产者的情况下才实现了无锁,这个队列类似于linux的内核的一个队列,kfifo.h源文件中.生产者消费者队列,优化性能的重点不在于选择什么样的锁,而在于如何解决频繁调用锁.即便你锁的消耗很低,但是每次出队都要调用锁,性能一样上不去.就算使用互斥锁,每次锁消耗时间很长, 但是每次出队100个item,那么平均到单次操作消耗也很低.
weixin_38149210 2019-09-20
  • 打赏
  • 举报
回复
不再与出队的速度,在于多读多写,读也是进多个,出队也是多个,有个lower和upderr
weixin_38149740 2019-09-20
  • 打赏
  • 举报
回复
看到很多类似的游戏服务器,和楼主一样对于TCP的发送处理有隐患:如果客户端卡顿之类的导致不进行recv,而服务器的write超过TCP滑动窗口大小后会一直死等,导致逻辑层出问题 正确的方式都是把send单独拿出来维护,不要随便用go的write
weixin_38150931 2019-09-20
  • 打赏
  • 举报
回复
channel中的锁效率肯定是不高的,etcd的raft用的就是channel, Dragonboat使用两个slice来回切换而构成的一个可以整体访问的queue,效率提升100倍
weixin_38069570 2019-09-20
  • 打赏
  • 举报
回复
666
weixin_38072180 2019-09-20
  • 打赏
  • 举报
回复
c++ 和lua这是个好模式啊
weixin_38078293 2019-09-20
  • 打赏
  • 举报
回复
各有各的好处,go用起来更快。效率更高点。lua太脚本语言了
weixin_38084861 2019-09-20
  • 打赏
  • 举报
回复
C++高手啊!
weixin_38085456 2019-09-20
  • 打赏
  • 举报
回复
能说点实际的一些技术就更好了。
weixin_38087592 2019-09-20
  • 打赏
  • 举报
回复
https://studygolang.com/p/golang游戏服务器 看看这个,游戏服务器也九简单了
加载更多回复(1)

433

社区成员

发帖
与我相关
我的任务
社区描述
其他技术讨论专区
其他 技术论坛(原bbs)
社区管理员
  • 其他技术讨论专区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧