3.3 MMP服务器开发和维护
William Dalton, Maxis
bdalton@maxis.com
|
对 |
于交付一个稳定的在线游戏来说,最主要的困难在于缺乏一个对终点的良好定义。虽然游戏开发人员已经对MMP游戏至少应该具备的功能达成共识,但是要使一个游戏获得成功,除了需要进行日常维护以外,还需要在整个生命周期中进行持续的开发。本文介绍了一些管理开发的技术,适用于那些要对运营中的产品进行持续维护和功能开发的情况。开发人员应该在游戏的生命期中尽早采用这些方法。一旦第一台服务器开始运营并且接受客户连接就是在提供一个运营中的服务了。要让开发人员、制作人员、美工、游戏构筑人员、设计人员和行政人员每天都能正常使用服务器已经很不容易,要是在此基础上再增加5000甚至500 000个付费用户,那麻烦就更多了。
在线游戏开发中的大部分工作都是在服务端完成的。这样做主要是因为以下几个原因。
安全性:服务器是开发人员能够绝对控制的组件。本文不会对安全性进行任何深入的讨论,但是要让游戏变得更安全,最重要的一步就是把所有敏感数据都从客户端移到服务端中。
用户偏好:用户讨厌客户端补丁,因为它们会延长进入游戏的时间,而服务端补丁则不会。
开发简便:如果游戏客户端在多个操作系统/硬件配置上运行,为每个可能的组合打补丁并且进行测试,不仅会耗费大量的人力而且非常容易出错。通常,在线游戏的服务器架构在整个服务中都是一致的,因此更容易进行可靠地测试和更新。
因此,本文将着重讨论那些能够让开发团队、测试人员、内容编写人员、设计人员、制作人员以及行政人员在整个游戏的开发过程中,甚至在游戏实际运营后一直有效地进行工作的方法。
3.3.1 基本问题
先回答一些基本问题将会使开发过程更为顺利。
1.正在使用什么服务器?
这是一个在在线游戏开发初期非常常见的问题。大多数游戏程序员习惯于在PC上进行开发,他们倾向于相信对网络库的调用会“正确地工作”
并且让他们可以和服务器进行通讯。如果他们不能确定服务器的特征,那么即使是与游戏服务器进行连接也会很麻烦。譬如说,服务器和客户端是否兼容?用来构建(build)它的代码是否最新?这个服务器是否与正确的数据库相连接?
客户端程序员和其他用户应该能够自己回答这些问题。这不仅为游戏的开发减轻了负担,还可以使任何针对游戏的错误报告更为有用。一个简单的方法是,让系统的每个组件都向与它连接的其他组件报告版本。因为每个组件都维护了一个与它连接的组件列表,当它向其他组件发起连接时,它会把整个列表转发给那个组件。与服务器连接的客户必须能够向所连接的组件查询这个列表,并把它显示给用户。这可以让用户拥有一个他们正在使用的组件的精确列表(包括版本)。
图3-5的例子中有一个错误的服务器组件。可以看到其中的一个数据库服务器已经过时了。假设游戏使用了上面所说的版本报告机制,用户甚至不需要直接登录到任何服务器就可以立即检测到这个问题,这也避免了用户偶然发现这种情况所带来的错误。

图3-5 服务端版本报告
除了版本以外,游戏还可以使用完全相同的逻辑在系统间传递其他信息。譬如说,让某个客户知道他所连接的每个服务器组件的进程标识和硬件标识会很有用。如果一个集群(cluster)中的服务器使用了多种硬件,这类功能会为调试工作提供非常大的帮助。
2.应该和谁建立连接?
客户端程序员及其他用户怎样才能在任意给定时刻确定哪台服务器最适合他们呢?如果上面的第一个基本问题已经有了答案,那么他们可以对所有已知的服务器进行尝试直到获得他们心目中服务器组件和客户端代码的最佳组合。他们也可以通过口口相传或是电子邮件来获得所需的信息。但是更好的方法是把他们进行选择所需要的信息集中放在一个随处可以访问的地方,譬如说一个网页上。这个资源不仅应该提供所有已知服务器的列表,还应该告诉用户他们的版本以及当前是否正在运行中,此外,这个资源还应该提供做出一次可靠的连接选择所需要的任何信息。
最后的警告:把服务器地址硬编码在客户端代码中实际上是对大多数用户隐藏了这一信息,因此这是一个馊主意。
3.怎样才能知道角色出了什么问题?
当用户与服务器的连接/游戏会话发生简单的问题时,他们是否可以进行调试?这里的可能性几乎是无穷的,每个团队都必须面对一个选择:在开发这个功能上应该投入多少努力,并且他们必须承受由这个选择所带来的后果。
如果开发团队中的某些成员并不进行服务端开发,甚至都不能接触到服务器硬件,下面这些建议或许有用。
服务端应该尽可能地向客户端提供有用的出错反馈。这种做法可以防止很多关于“服务器崩溃”的抱怨。如果管理人员可以确定客户端正在使用一个过时的协议,或是发送了一个有问题的数据包,都应该向客户端返回一个有意义的出错信息。如果服务端可以告诉客户端它的版本过时了或是服务器正在关机(故意的),就不应该只发送一个通用的“服务器连接断开”的错误消息。尽可能向客户端提供精确的出错反馈,这可以使开发团队节约大量的调试时间。
让用户可以看到服务器日志,或是日志的某些合理部分。更好的方法是,允许用户对服务器日志进行搜索(这也是游戏开发人员应该把游戏日志写得更为简洁有用的原因之一)。游戏管理人员可以在需要时把某些信息从服务器发回给客户端,而使用一个能够访问到所有服务器进程日志的网页就是一个很好的方法。
不断监视所有服务器。服务器工程师应该在大多数团队成员之前发现存在于服务端的问题。有些系统可以周期性地检查所有已知的服务器以发现错误,这非常有用。服务器工程师还可以对日志文件进行分析来检查服务器是否发生错误,并且确保在发现了任何问题后都能够及时通知开发团队中的相关人员。服务器工程师应该对任何可能发生的硬件问题进行周期性的检查,譬如说网络问题、进程失控(由于CPU或内存问题)、硬盘空间不足等。可以编写一些脚本来周期性地搜集这些数据并且转发给开发团队。此外,把这类信息放在网页上也是一个不错的主意。
下面这段简单的bash脚本可以用来进行监视和报告。在Unix系统中,程序员可以把它放到cron表中来对它进行周期性的调用。类似的脚本可以用来检查游戏日志中是否有重要错误发生,甚至可以用来检查每个服务器的硬件情况(譬如说,可用磁盘空间、内存等)。
#! /bin/bash
###
Intention: Find all of the core files in ~/production directory on ### a set of
server clusters.
HOME_DIR=/home
HOSTNAME=`hostname`
SERVER_LIST= devdaily devtest livedaily livetest liveregress
OUTPUT_FILE="coreReport.txt"
MAIL_LIST="bdalton@maxis.com"
SUBJECT_LINE="Core Sweeper Report: ${HOSTNAME}"
### Init file nulling out results from previous run
cp /dev/null ${OUTPUT_FILE}
for SERVER in ${SERVER_LIST}; do
echo "—— -cores on ${SERVER}:" > ${OUTPUT_FILE} 2>&1
FILELIST=`ssh ${SERVER}"find ${HOME_DIR}/prod96 -name core*"`
if [ "${FILELIST}" != "" ]; then
for FILE in ${FILELIST}; do
REPORT=`ssh ${SERVER} "ls -l ${FILE}; file ${FILE}"`
### Format output in whatever way is useful to you
FIELDS1=`seq -s"," 1 9`
FIELDS2=`seq -s"," 16 19`
echo $REPORT | cut -delim=" " -
fields=$FIELDS1,$FIELDS2 | column -t > \
${OUTPUT_FILE} 2>&1
done
fi
echo "——————————————————————" > ${OUTPUT_FILE} 2>&1
echo "" > ${OUTPUT_FILE} 2>&1
done
mutt -s "$SUBJECT_LINE" "$MAIL_LIST" < "$OUTPUT_FILE"
3.3.2 对复杂度进行管理
下面这些技术有助于让复杂度最小化。
1.分支
在游戏发布前的某个时刻,程序员需要对代码进行分支(branch)。要在维护一个运营游戏的同时还进行持续的开发,这可能是最好的方法了。通常,开发团队应该建立一个运营分支和一个开发分支,这里本文会对它们进行详细讨论。
对于大多数组织来说,对一个客户服务器游戏进行分支开发并不简单。通常,开发团队对游戏服务器内部机制的了解并不比他们对其他游戏组件的了解多。这不仅会增加他们的挫折感,还会产生很多错误的缺陷报告(defect report),这最终会使游戏的开发失去大量的时间。所以,应该尽量简化这个过程。
坚持使用命名规范。用于开发的服务器必须在它们的名字里包含“Dev”或是类似的字符串,这有助于向潜在用户提供信息。所有运行中的分支服务器/测试中心都必须进行相应的命名(如果游戏系统已经实现了在“基本问题”这一节中所讨论的第一个方案,那么任何连接上的客户都可以很方便地知道这个命名规范)。
为开发服务器和运营服务器建立独立的环境。不要把运营分支中的代码放在开发服务器上运行,也不要把开发分支中的代码放在运营服务器上运行。
应该使用一个自动构建系统来进行每日构建和用于检查的构建。应该用两台独立的电脑来构建运营和开发版本,并且这些电脑应该是它们所对应的分支中所有构建的惟一来源,最好让它们都不能从其他分支获得代码。
图3-6是一个比较简单的开发环境布局示意图。由于开发团队所使用的开发技术和规范不同,有些可能需要使用更多的资源,但是应该不能再少了。
需要注意的是这仅仅是服务端的开发环境。客户端也应该使用专门的电脑来进行构建,并且它们应该符合服务端对开发团队中客户端安装程序进行更新的机制。简单地把客户端软件的镜像放在一个共享的网络服务器上,或使用更复杂的机制都可以达到这个目的,譬如说一系列补丁频道(patch channel)。
在图3-6中,本文假设游戏的入口点会显示不同分支和不同环境中的所有服务器。如果这个入口只是一个非常简单的进程,那么这样做也够了。如果入口比较复杂,需要把它和其他代码一起进行分支。譬如说,如果入口点需要决定客户端是否应该安装补丁或是它还包含了其他有趣的游戏特性,可能就需要这样做。
图中还假设每个分支只需要一个数据库实例。这并不一定适用于所有的游戏,但是无论采用什么方法,都必须准备好为所有的服务器提供持久化支持。记住这还包括那些最先进的开发服务器,它们可能会对数据库提出新的要求。
2.共享代码
游戏开发人员应该怎样构建客户端软件和服务端软件呢?它们是不是在相同的操作系统上进行构建?它们是否会在同样的操作系统和硬件环境中运行?在决定是否让客户端和服务端进行代码共享时,这些重要问题必须首先得到回答。

图3-6 分支开发环境的简单示意图
“代码共享”听起来是个好主意,因为它意味着“代码重用”。那些被重用的代码只需要在一个地方进行修改,但可以在多个地方进行测试,因此它的质量很高,这可能是因为一开始设计得很好,也可能是因为后期不断改善的缘故。平台无关性同样也意味着高质量的代码。然而,在客户端和服务端之间进行代码共享也会带来一些负担,开发人员应该在这些负担和代码质量上的好处之间进行权衡。
流行的PC编译器常常鼓励用户使用非ANSI的C++代码。如果客户端程序员使用这样的产品,而服务端开发人员使用一个符合ANSI标准的产品,这些差异会影响服务端构建。
在客户端和服务端进行代码共享是不是还意味着必须对其他数据进行共享?这些数据是否可以在客户端和服务端的操作系统间移植?在客户端和服务端的目标硬件中是否会有字节顺序问题?这会对服务端和客户端的补丁带来什么影响?
最后,还要考虑一下代码共享会为游戏带来哪些安全上的漏洞。对于一个专家来说,客户端程序就好像是一本打开的书。应不应该让别人了解服务端的内部实现,即使只是一部分?
3.3.3 总结
本文最关键的主题就是可见性(visibility)。要尽量让用户可以接触到服务器。游戏开发人员应该让用户在任何时候都可以知道他们使用的是哪些服务器进程。当有问题发生时,游戏开发人员应该为用户提供一些工具来帮助他们解决这些问题。在开发过程中,系统应该向用户提供足够多的提示和线索来帮助他们理解服务端正在做些什么。游戏开发人员应该使用一切可用的方式来告诉用户与系统有关的信息,无论是使用命名规范、使用其他技术还是使用游戏本身。






