游戏开发论坛

 找回密码
 立即注册
搜索
楼主: qinglan

游戏服务器技术交流

[复制链接]

1

主题

39

帖子

39

积分

注册会员

Rank: 2

积分
39
发表于 2007-9-20 02:25:00 | 显示全部楼层

Re:游戏服务器技术交流

很有奉献精神,让我等小辈大开眼界. 希望前辈继续无私奉献出自己的经验!感谢前辈

2

主题

55

帖子

55

积分

注册会员

Rank: 2

积分
55
发表于 2007-9-20 10:14:00 | 显示全部楼层

Re:??蚍?掌骷夹g交流

老大 是epoll不是epool ????`??e人喔

11

主题

102

帖子

123

积分

注册会员

Rank: 2

积分
123
发表于 2007-9-20 12:12:00 | 显示全部楼层

Re:游戏服务器技术交流

这个论坛里面做图文混排不方便,建议楼主发表在blog里,方便大家拜读

7

主题

82

帖子

110

积分

注册会员

Rank: 2

积分
110
QQ
 楼主| 发表于 2007-9-20 16:57:00 | 显示全部楼层

Re: Re:??蚍?掌骷夹g交流

hamk: Re:??蚍?掌骷夹g交流

老大 是epoll不是epool ????`??e人喔


汗死

虚心接受批评  [em17]

149

主题

4981

帖子

5033

积分

论坛元老

Rank: 8Rank: 8

积分
5033
QQ
发表于 2007-9-20 17:13:00 | 显示全部楼层

Re:游戏服务器技术交流

服务器方面我有两个问题,可能的话请楼主在后续篇章里解答一下吧:
1。网络协议的设计问题。作为一个外行我一直不清楚这是否有一定的方法和技巧或者规则和约定。就我个人而言,目前我只是掌握了单个数据包的传送和解析,单个数据包在网上的格式是“len-type-data”。但是我发现很多时候,对于一个事务,它在逻辑上是一个整体,但是却需要在服务器和客户端之间有多次的数据往返才能完成,目前我的做法是某一端在处理某个数据包时如果认为需要有进一步的通信就往对方发出一个数据包,但是我发现这样的话在逻辑上是整体的一个事务需要设计多个数据包,而且这个事务的处理流程会分散在多个数据包的处理函数里,这样的代码就很不清晰,维护更新也很麻烦。不知道业内在做的时候有什么方法没有?
2。持续运行的问题。有没有什么办法,使得服务器端有所更新时能尽量减少服务器端需要重新启动的概率?

7

主题

82

帖子

110

积分

注册会员

Rank: 2

积分
110
QQ
 楼主| 发表于 2007-9-20 21:30:00 | 显示全部楼层

Re:游戏服务器技术交流

len-type-data 大都是这样,一个业务逻辑要分散在我个消息码中处理,也大都这样,当然你可以用状态机的方式,进入一项业务的时候就是一个状态,这样使得该业务的逻辑只在一个状态中

减少服务器重启,比较好的就是通过脚本了,简单实用,至于ACE的那个配置框架,可以支持组件的热插拔,好像没见人用过,多一事不如少一事,万一插出毛病来了,得不偿失

至于wow最近在国外测试的热重启,还不清楚他采用的什么技术

7

主题

82

帖子

110

积分

注册会员

Rank: 2

积分
110
QQ
 楼主| 发表于 2007-9-20 22:11:00 | 显示全部楼层

服务器公共组件实现 -- 消息队列

  既然说到了消息队列,那我们继续来稍微多聊一点吧。

  我们所能想到的最简单的消息队列可能就是使用stl的list来实现了,即消息队列内部维护一个list和一个互斥锁,putMessage时将message加入到队列尾,getMessage时从队列头取一个message返回,同时在getMessage和putMessage之前都要求先获取锁资源。

  实现虽然简单,但功能是绝对满足需求的,只是性能上可能稍稍有些不尽如人意。其最大的问题在频繁的锁竞争上。

  对于如何减少锁竞争次数的优化方案,Ghost Cheng提出了一种。提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。

  这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。

  这里为每个队列设了个最大消息数,看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一个队列,而逻辑线程又没有数据可处理的情况,特别是当数据量很少时可能会很容易出现。Ghost Cheng在他的描述中没有讲到如何解决这种问题,但我们可以先来看看另一个方案。

  这个方案与上一个方案基本类似,只是不再提供队列容器,因为在这个方案中只使用了两个队列,arthur在他的一封邮件中描述了这个方案的实现及部分代码。两个队列,一个给逻辑线程读,一个给IO线程用来写,当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。所以,这种方案下加锁的次数会比较多一些,IO线程每次写队列时都要加锁,逻辑线程在调换队列时也需要加锁,但逻辑线程在读队列时是不需要加锁的。

  虽然看起来锁的调用次数是比前一种方案要多很多,但实际上大部分锁调用都是不会引起阻塞的,只有在逻辑线程调换队列的那一瞬间可能会使得某个线程阻塞一下。另外对于锁调用过程本身来说,其开销是完全可以忽略的,我们所不能忍受的仅仅是因为锁调用而引起的阻塞而已。

  两种方案都是很优秀的优化方案,但也都是有其适用范围的。Ghost Cheng的方案因为提供了多个队列,可以使得多个IO线程可以总工程师的,互不干扰的使用自己的队列,只是还有一个遗留问题我们还不了解其解决方法。arthur的方案很好的解决了上一个方案遗留的问题,但因为只有一个写队列,所以当想要提供多个IO线程时,线程间互斥地写入数据可能会增大竞争的机会,当然,如果只有一个IO线程那将是非常完美的。

7

主题

82

帖子

110

积分

注册会员

Rank: 2

积分
110
QQ
 楼主| 发表于 2007-9-20 23:01:00 | 显示全部楼层

服务器公共组件实现 -- 环形缓冲区

  消息队列锁调用太频繁的问题算是解决了,另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的服务器长期稳定运行。也许我们可以使用内存池,比如SGI STL中附带的小内存分配器。但是对于这种按照严格的先进先出顺序处理的,块大小并不算小的,而且块大小也并不统一的内存分配情况来说,更多使用的是一种叫做环形缓冲区的方案,mangos的网络代码中也有这么一个东西,其原理也是比较简单的。

  就好比两个人围着一张圆形的桌子在追逐,跑的人被网络IO线程所控制,当写入数据时,这个人就往前跑;追的人就是逻辑线程,会一直往前追直到追上跑的人。如果追上了怎么办?那就是没有数据可读了,先等会儿呗,等跑的人向前跑几步了再追,总不能让游戏没得玩了吧。那要是追的人跑的太慢,跑的人转了一圈过来反追上追的人了呢?那您也先歇会儿吧。要是一直这么反着追,估计您就只能换一个跑的更快的追逐者了,要不这游戏还真没法玩下去。

  前面我们特别强调了,按照严格的先进先出顺序进行处理,这是环形缓冲区的使用必须遵守的一项要求。也就是,大家都得遵守规定,追的人不能从桌子上跨过去,跑的人当然也不允许反过来跑。至于为什么,不需要多做解释了吧。

  环形缓冲区是一项很好的技术,不用频繁的分配内存,而且在大多数情况下,内存的反复使用也使得我们能用更少的内存块做更多的事。

  在网络IO线程中,我们会为每一个连接都准备一个环形缓冲区,用于临时存放接收到的数据,以应付半包及粘包的情况。在解包及解密完成后,我们会将这个数据包复制到逻辑线程消息队列中,如果我们只使用一个队列,那这里也将会是个环形缓冲区,IO线程往里写,逻辑线程在后面读,互相追逐。可要是我们使用了前面介绍的优化方案后,可能这里便不再需要环形缓冲区了,至少我们并不再需要他们是环形的了。因为我们对同一个队列不再会出现同时读和写的情况,每个队列在写满后交给逻辑线程去读,逻辑线程读完后清空队列再交给IO线程去写,一段固定大小的缓冲区即可。没关系,这么好的技术,在别的地方一定也会用到的。

89

主题

822

帖子

847

积分

高级会员

Rank: 4

积分
847
发表于 2007-9-21 00:14:00 | 显示全部楼层

Re: 游戏服务器技术交流

  对于如何减少锁竞争次数的优化方案,Ghost Cheng提出了一种。提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。

  这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。


对于第一条,如果网络IO线程长时间填不满一个队列,那么这个队列的消息是不是就无法放回容器中?假设你的网络线程和逻辑线程的处理能力是一样的,那么实际上每次网络线程在处理消息的时候,逻辑线程总是取不到有消息的队列。

锁碰撞的机会并不取决于你加锁的队列有多长,是一个还是十个,而在于你加锁和解锁之间代码执行的时间,尽管你只有一个队列,队列有一百个消息,但是如果只是在其中的几行代码加锁,碰撞的机率依然很小。

对于第二条,你的前提是逻辑线程和网络IO线程本身是各自同步执行的,但实际上就拿网络线程来讲本身就有可能有多个线程在同时处理网络消息,那么他们都会去你所谓的容器中寻找空的队列,这本身就是资源竞争,更不用说其它的逻辑数据了。

我个人认为锁的频繁程度并不是系统所能控制的,但是加锁执行的代码却是可以控制的。
我们可以为长时间处理的代码所需要的资源创建一个副本,当我们复制副本的时候,可以将原信息加锁,复制完成即刻解锁,而不必要在整个处理信息的时间全部加锁。

7

主题

82

帖子

110

积分

注册会员

Rank: 2

积分
110
QQ
 楼主| 发表于 2007-9-21 09:30:00 | 显示全部楼层

Re:游戏服务器技术交流

每次取一个空队列出来后就从容器里拿掉,IO线程自己保存着使用,不是每次要写数据都去取队列

至于队列未满不会主动放回容器的问题,逻辑线程可以在容器中所有队列都为空的时候给IO线程发消息,要求所有的IO线程都立即调换队列
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-2-25 19:58

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表