tencent cloud

文档反馈

游戏聊天系统

最后更新时间:2024-12-02 20:36:45

    客户简介

    社交,是游戏文玩家的一项基本需求。在游戏中,成熟稳定的聊天系统担负着玩家交流的重要使命。本文分享了江娱互动使用腾讯云云函数的真实案例,介绍如何通过云函数升级游戏中的聊天系统。
    江娱互动在《世界争霸》和《农场小镇》两款产品中都使用了自研聊天系统。随着在线人数逐渐增多,系统的稳定性和成本面临着更多考验。需进一步升级技术栈,以保障性能为前提,同时尽量减少人力和资源成本的花费。最终选择了使用腾讯云云函数,原因如下:
    云函数无需服务器,仅需关注业务逻辑代码,省去运维烦恼。
    按量付费的计费模式,避免业务低谷期的资源浪费,减少了成本开支。非常适用于游戏聊天系统 API 这种复杂度低的中小型需求。
    接下来我们只需关注如何将现有系统无缝迁移,即云函数如何满足目前所有的特定需求。

    客户需求

    需求1:仅修改少量代码

    原有的部分 API 采用 swoole 作为底层扩展,部署在腾讯云云服务器 CVM 上,并使用腾讯云负载均衡接收外部请求。代码层面使用了 composer 进行包管理,HTTP 业务框架采用了开源框架 easywoole。
    若变更为云函数方案,则非代码层面将由腾讯云 API 网关及云函数来提供服务,代码层依然需使用 composer 进行包管理。而原有基于 swoole 的 HTTP 框架将无法继续使用,修改代码的重点则集中在框架部分。如下图所示:
    
    主要修改思路如下:
    1. 确定逻辑入口,确保用一个云函数来处理所有请求。 入口其实是一个路由,我们需要定义一种简单的路由格式,在云函数入口代码处得到需要的信息,转给原有的类进行处理并返回特定的内容。以下是一个简单的 url 格式示例:
    https://url/controller/action?query
    只需要解析云函数给出的 path,就能得到 controller 和 action,做出判断后调用相应类的方法后返回。基于这样的入口,原有的逻辑处理类就可以被调用到了。
    2. 处理原来的逻辑处理类的父类,弃用框架后需要实现一个基本功能的父类。例如,获取 qerystring 内容、解析 body、返回统一格式的返回值等。 下图中以 PHP 为示例,不同的开发语言思路类似。代码还需进一步修改,例如,增加数据库配置信息(可使用云函数中的环境变量来传递)及原有耗时任务的异步操作等。
    date_default_timezone_set('Asia/Shanghai');
    require_once__DIR__ . "/vendor/autoload.php";
    function run($event, $context)
    {
    $path = $event->path;
    //解析path
    list($controller, $action) = parsePath($path);
    $controllerClassName = "\\\\App\\\\HttpController\\\\" . ucwords($controller);
    if(!class_exists($controllerClassName)){
    return return404();
    }
    $controllerClass = new $controllerClassName($event, $context);
    //避免在 HttpController 目录下放置的不应该被外部调用的类被调用
    if(!controllerClassName instanceof \\App\\BaseController){
    return return404();
    }
    if(!method_exists($controllerClass, $action)){
    return return404();
    }
    try{
    return $controllerClass->$action();
    }catch(Throwable $e){
    return return500();
    }
    }

    需求2:快速发布

    快速发布的能力是不可或缺的,在迁移过程中,会反复进行各种测试。在进行迁移时云函数的本地测试功能尚未支持 PHP,所以在使用 API 网关与云函数组合时,发布流程为:
    1. 开发代码
    2. 部署云函数 $LATEST 版本
    3. 基于 $LATEST 版本生成新版本号
    4. API 网关对应路径切换版本
    5. API 网关发布测试版本
    6. API 网关线上使用版本切换
    发布的过程较为复杂,在迁移开始时,步骤3尚未支持 API 调用,无法实现自动化部署,目前已支持了该能力。但也可以使用 API 网关直接指向云函数的 $LATEST 版本,再部署云函数的方案。该方案较为简单,但仅适用于测试阶段,不适用于线上阶段的发布。 我们采用了两者结合的方式,稳定的功能使用稳定版本的发布流程,而新功能则新建一个 API 路径并指向 $LATEST 版本,随时进行发布也不会影响线上功能。

    问题及解决方案

    在发布 API 网关时,有时会遇到资源超限的情况,查明是由于云函数的并发实例数量限制,当发布新的 API 版本时,会请求进入新的实例而旧的实例未释放。两种实例数量和超过限制,此时需向腾讯云申请提高限额。

    需求3:内网互通

    此次为系统迁移,但原有的系统中仍然有部分内容需要继续使用。因此需要云函数可以与原有的 CVM 内网进行通信,结合云函数本身支持部署到已有的内网中,内网互通很容易实现。

    问题及解决方案

    由于我们的 API 服务需要向外发出请求,而内网云函数在系统迁移时不具备直接访问外网的能力,通过 NAT 网关实现了云函数访问公网的功能。详情请参见 在私有网络中配置 NAT 网关
    注意:
    通过 NAT 网关的解决问题时,建议给云函数单独分配一个子网。因为使用已有的子网绑定 NAT 网关,会导致出口 IP 变化。若该子网下的机器 IP 恰好在某些白名单下,则会造成影响。

    需求4:日志查询

    原有的日志采用直接落盘,定期压缩转储的方式。而系统迁移到云函数后,需采用云函数的日志机制。云函数可直接投递日志到腾讯云日志服务中,任何输出的信息都会直接作为日志被投递,需要根据实际需求规划日志内容。
    我们把函数入口的原始信息、URL 路径、客户端 IP、解析后的参数以及业务日志等都进行了输出,方便快速定位和查询。另外,不需要单独输出一次返回值,云函数可自行打印。
    注意:
    在开启日志投递后,需打开索引才可查看日志。若日志内容包含索引分词符,则设置索引时需从分词符中删除对应关键字,否则内容会被分割。
    在系统迁移时,云函数日志部分还存在着不足之处,例如云函数与 API 网关的日志是分离的。HTTP 的原始入口是 API 网关,这就导致一些问题跟进较困难。目前了解到云函数和 API 网关日志的 RequestId 已经打通,可以很方便地通过同一个 ID 查询到同一次请求的日志。

    需求5:耗时任务处理

    原有的方案是使用 swoole 的 task 处理耗时任务,由于云函数的 PHP 环境支持 swoole,初步尝试了如下图所示的改造方案:
    $p = "1";
    $process = new \\swoole_process(function (\\swoole_process $worker) use ($p){
    echo $p;
    sleep(3);
    echo $p;
    });
    $pid = $process->start();
    但通过该方案制造出的进程不能完全掌控。经测试发现,打印出的日志属于其他请求,同时也无法确定进程的计时及生命周期,所以采用了更为保险的“消息队列”方案。
    选用了腾讯云的消息队列服务 CKafka,通过封装一个通用结构的消息体并发给 CKafka,CKafka 会触发另一个云函数(运行着耗时任务的代码)。采用通用结构时,可以忽略消息队列的主题,若有任何想要异步操作的任务,只需写在被 CKafka 触发的云函数中,再把需要触发的云函数名和参数发给 CKafka 即可。如下图所示:
    

    问题及解决方案

    Ckafka 主题默认仅创建一个分区,如消费速度不理想,可尝试新增分区。新增后,云函数侧需删除触发器再重新添加。

    需求6:配置文件更新

    该系统中的配置文件是需要经常更新的大文本。例如,聊天服务中会涉及到的屏蔽词库,该文件容量大且会频繁更新。
    原有方案为:配置文件单独具备 git 库,策划提交后执行 jenkins,再由 jenkins 上传文件到 CVM 并进行 reload。 系统迁移到云函数后,无法单独上传配置文件,仅支持将文件放置在代码中。方案也更新为:策划提交 git,jenkins 从 git 获取文件后上传至 COS,云函数再从 COS 拉取。如下图所示:
    

    问题及解决方案

    性能问题,云函数拉取 COS 有一定耗时,因此不能每一个请求就拉取一次文件。意味着需要把每一次拉取的内容存放在内存中,但无法统一管理云函数的内存,无法保证实时变更。 采用了折中方案,比较内存中保存文件内容和上一次拉取时间,如果超过5分钟,则重新拉取。可以保证相对的实时性和性能,也可满足目前的需求。如下图所示:
    private static $fileContent = null;
    private static $lastTime = 0;
    
    public static function refresh(){
    self::$fileContent = self::readFromCos("words.txt");
    }
    
    public static function getFileContent(){
    if(time() - self::$lastTime > 300 || empty(self::$fileContent)){
    self::refresh();
    self::$lastTime = time();
    }
    return self::$fileContent;
    }

    客户价值

    至此,系统迁移过程中的需求都已实现,迁移工作得以顺利推进。迁移到云函数之后的优势如下:
    无需维护 API 服务器,无需考虑 CPU、内存的问题,请求量增加时也无需考虑是否增加 CVM。
    监控内容比较详细,可以更好地查看整体的运行效率。例如,查看是否存在慢请求、访问趋势,是否有错。
    使用消息队列拆分后,解耦彻底,且可确保消息不会丢失。消息队列触发云函数的方法适用于不断累积形式的慢任务。
    版本管理功能改进后可随时切换版本,无需再重新拉取代码分支发布。
    腾讯云云函数给江娱互动带来了众多优势及便利,江娱互动也在规划还有哪些功能可以继续使用:
    无状态的 HTTP 服务。例如,客服消息接收、支付回调接口。
    无须返回的异步任务。例如,微信小游戏上报玩家排名。
    定时任务。例如,定期给玩家推送相关的活动信息。
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持