汽车 营销 网站建设,下载宝硬盘做网站,峰峰做网站,wordpress多站点子域名大家好#xff01;我是长三月#xff0c;一位在游戏行业工作多年的老程序员#xff0c;专注于分享服务器开发相关的文章。
本文是通用程序设计主题下的第二篇。这个主题主要探讨如何编写高效、健壮、易读的游戏业务代码#xff0c;每篇从一个小点切入。本次讨论的要点是我是长三月一位在游戏行业工作多年的老程序员专注于分享服务器开发相关的文章。
本文是通用程序设计主题下的第二篇。这个主题主要探讨如何编写高效、健壮、易读的游戏业务代码每篇从一个小点切入。本次讨论的要点是如何合理地应对异常。
异常错误处理是游戏服务器开发中绕不开的一个话题。合理地处理异常能够将故障对玩家的影响降到最低水平快速从错误中恢复并提供充足的信息避免以后再出现类似的问题。
有些编程语言例如Golang会把异常和错误分开这里为了方便说明用异常来统一指代这两种类型。因为这两者在处理方法上是相通的。
总的原则是根据异常的严重程度和影响范围在不同层次处理。对于服务器启动时的严重异常应向顶层抛出立即中止服务器运行而对于仅影响个别玩家的异常应及时捕获异常避免影响其他玩家。
看下面一个Java写的例子。服务器在启动时调用start方法做一些初始化工作其中包括对于Redis客户端的初始化测试Redis服务器是否可连通如果可连通初始化连接等。如果发现Redis服务器连接失败抛出异常这里不会对异常捕捉而是逐级抛到顶层让服务器进程中断运行。
public void serverStart() { // 服务器启动时初始化// do some init work// ...RedisClient.init(); // 初始化Redis客户端如果失败直接抛出异常不要捕捉// do other init work// ...
}这样做的好处是让程序员及时检查问题并做修正避免后续问题的发生。试想如果我们改成捕捉异常并恢复程序运行那么当服务器启动完成后玩家正常进入可是需要使用到Redis存取数据时才发现功能不可用必然会引起玩家的不满。所以对于这种影响全局的严重问题不如在停服重启的时候就彻底解决好解决了再对玩家开放服务器。
这种异常处理策略被称为快速失败fail-fast即检测到异常后直接抛出并中止程序运行。它适用于严重而且无法恢复的异常情形好处是能第一时间发现和定位问题并且避免错误对后续逻辑的影响。JDK中也有对fail-fast思想的运用例如ArrayList是一个非线程安全的容器如果在使用for-each遍历元素的同时又修改了它的结构那么会第一时间抛出ConcurrentModificationException快速而干净地失败而不是冒着在未来不确定的时间出现不确定行为的风险。
对于全局性的严重异常我们固然要fail-fast。但是对于仅影响部分玩家或者部分场合的异常我们要及时捕捉并恢复避免不恰当地扩大影响范围。
在游戏中经常会有活动排行榜结算的场景。活动结束时按照参与活动玩家在排行榜的名次依次发奖。通常这样的操作放在定时任务中执行。如果某一个玩家发奖出现异常我们需要及时捕捉异常并记录详细的日志避免影响到其他玩家。
public void giveAwards() { // 为活动结算发奖for (Rank rankData : rankList) {int playerId rankData.getPlayer();int rank rankData.getRank();try {giveAwards(playerId, rank); // 为单人发送排名奖励} catch (Exception e) {logger.error(e, 发送奖励失败角色id是{}排名是{}, playerId, rank); // 错误日志中包含详细的信息}}
}对比一下如果我们不对单个玩家发奖捕捉异常那么出现异常时会导致所有人的发奖失败势必会引发更多玩家不满而且需要做更多补偿。由于我们及时捕捉异常因此发奖失败仅仅被限制在单个玩家范围内事后处理也要容易得多。
另一个限制异常影响范围的例子是服务器战斗。在逐帧进行的服务器战斗中每帧运算出现异常时需要及时捕捉这样只会影响当前帧而不会导致战斗直接退出甚至战斗线程挂掉。
public void frameLoop() { // 开启战斗房间的循环运算while (isStart) {long starTime System.currentTimeMillis();try {room.update();} catch (Exception e) {logger.error(e, 战斗房间异常); // 每帧计算有异常则捕捉不影响下一帧}long endTime System.currentTimeMillis();long sleepTime frameInterval - (endTime - startTime);if (sleepTime 0) {Thread.sleep(sleepTime);}}
}捕捉异常时我们要在日志记录充足的信息以便后续追踪修复问题和做线上处理不要忽略异常而什么都不记录。要记录的信息应包括完整的异常堆栈、角色id以及其他重要信息。例如上面提到过的发奖失败日志除了异常堆栈还应包括角色id和排名这样方便事后为对应玩家做补偿。
logger.error(e, 发送奖励失败角色id是{}排名是{}, playerId, rank);