亚马逊AWS官方博客

使用 GameLift 集成 Java Game Server 构建弹性游戏服务集群

背景介绍

很多的游戏公司在运营游戏过程中,为了节约成本,通常会对游戏服务基础设施做弹性化来应对游戏业务的高峰期和低谷期,而亚马逊云科技的 GameLift 服务可以很好地帮助客户来管理游戏服务基础设施,最大限度地帮助客户节省成本的同时,提升游戏用户的体验。目前 GameLift 服务端 SDK 只提供了 C++,C#和 Go 三种语言的接入,而很多公司目前在使用 Java 来开发游戏的服务端,在本文中,我们将详细介绍如何在 Java Game Server 中集成 GameLift Server SDK 来实现游戏服务的动态扩缩容。

前提条件

  1. 一台装有 Amazon Linux2 操作系统 EC2
  2. 下载本文所使用代码:链接

基础设施架构图

GameLift 集成时序图

本文中主要针对 Game Server 集成 SDK 和创建 Game Session 为例,其他相关集成,大家可以根据示例进行扩展。

GameLift Server SDK 打包

登录到 EC2 上安装依赖包

sudo yum install -y gcc-c++ gdb git

在原有的 Server SDK 打包时打的为静态包,无法在 JNI 中使用,所以我们首先需要将 Server SDK 重新打包,打成共享库,先去官方下载 Server SDK 压缩包,本文中下载的 Server SDK 版本为 5.1.0

wget https://gamelift-server-sdk-release.s3.us-west-2.amazonaws.com/cpp/GameLift-Cpp-ServerSDK-5.1.0.zip
unzip GameLift-Cpp-ServerSDK-5.1.0.zip -d GameLift-Cpp-ServerSDK-5.1.0

解压缩后修改如下图所示的 CMake.txt 的文件

我们需要添加-fPIC 的编译选项,来告知编译器生成位置无关的代码,添加方式如下:

# 启用 -fPIC(Position Independent Code)选项
set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)

按照 README.md 中的内容生成 Server SDK

mkdir cmake-build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -S . -B ./cmake-build
cmake --build cmake-build --target all

执行完毕后,可以发现打包文件已经生成在 prefix/lib 目录下:libaws-cpp-sdk-gamelift-server.a

构建 JNI 依赖包

下载示例代码库,配置 JAVA_HOME,复制以下内容至.bashrc 中,本文中使用的 JAVA 版本为 JAVA 11,请根据自己的 Java 版本进行调整

export JAVA_HOME="/usr/lib/jvm/java-11-amazon-corretto.x86_64"#本文中使用的 JDK 版本为 JAVA11 
PATH=$JAVA_HOME/bin:$PATH

然后执行

source .bashrc

切换至 JNIGameliftServerSDK 目录下,执行以下命令打包 JNI 文件

mkdir cmake-build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -S . -B ./cmake-build
cmake --build cmake-build --target all

开发过程中我们可以通过 Visual Studio 远端 EC2 Linux 调试

远程 EC2 上需要安装依赖库

sudo yum install g++ gdb make ninja-build

然后通过 Visual Studio 中配置连接,然后即可通过远端服务器进行调试

构建 Game Server 发布 GameLift Build

拷贝上面环境中打包的 JNI 文件至 Java Game Server 中 resource 下,执行以下命令打包并上传 Java Game Server

build-and-deploy-game-server.sh

在该脚本中,我们会通过 Gradle 来对 Java Game Server 项目进行构建,然后使用 cli 将项目文件上传至 GameLift Build。

注意事项:

  1. 在 Java 项目的构建时需要注意,我们需要将所有的依赖包打进 Jar 包,所以我们在使用 Gradle 构建时引入了github.johnrengelman.shadow 来做依赖包的打包。
  2. 在 Gradle 配置文件中需要指定启动类。
  3. build-and-deploy-game-server.sh 文件中指定我们要上传的 Region 和对应的 Server SDK 的版本。
  4. 最好使用 Linux 的 Cli 来上传 Build,不然无法正确指定 Server SDK 的版本。

部署后台服务

切换至 backend-service 目录下,通过 CDK 来部署后台服务,参考官方文档安装CDK

cdk synth
cdk deploy

待执行完毕后,我们会创建 API Gateway,Lambda,GameLift Session queue 以及 GameLift Alias。

创建 Fleet

然后选择 Fleet 创建的目标 Location 以及机器配置,同时需要配置一个关键的配置每台游戏服务器,运行多少个游戏进程。

关联 Alias

测试接口

创建 Game Session

 curl -X POST \
  -H "x-api-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "placementId": "placementid-30",
    "sessionQueueName": "<session-queue-name>",
    "maximumPlayers": 2
  }' \
  https://aoaqiqp1zc.execute-api.us-east-1.amazonaws.com/dev/game-sessions

查看 Game Session 状态

 curl -X GET \
  -H "x-api-key: <api-key>" \
  -H "Content-Type: application/json" \
  https://aoaqiqp1zc.execute-api.us-east-1.amazonaws.com/dev/game-sessions/placementid-30

如果返回结果如下所示,则说明 Game Session 已经创建成功

{
  "GameSessionPlacement": {
    "PlacementId": "placementid-29",
    "GameSessionQueueName": "java-game-server-session-queue",
    "Status": "FULFILLED",
    "GameProperties": [],
    "MaximumPlayerSessionCount": 2,
    "GameSessionId": "arn:aws:gamelift:us-east-1::gamesession/fleet-d87a1760-6894-49f4-8eb4-dc6243b0ea4b/placementid-29",
    "GameSessionArn": "arn:aws:gamelift:us-east-1::gamesession/fleet-d87a1760-6894-49f4-8eb4-dc6243b0ea4b/placementid-29",
    "GameSessionRegion": "us-east-1",
    "PlayerLatencies": [],
    "StartTime": "2023-09-23T13:11:46.676Z",
    "EndTime": "2023-09-23T13:11:48.391Z",
    "IpAddress": "44.200.x.x",
    "DnsName": "ec2-44-200-8-185.compute-1.amazonaws.com",
    "Port":1234
  }

本地联调方式

在开发过程中,我们经常需要本地联调测试。

创建 Location

aws gamelift create-location --location-name custom-location-1

创建 Fleet

aws gamelift create-fleet --name LaptopFleet --compute-type ANYWHERE --locations "Location=custom-location-1"

注册计算资源

aws gamelift register-compute \
>     --compute-name DevLaptop1 \
>     --fleet-id {上面输出的 FleetID} \
>     --ip-address {public IP}\
>     --location custom-location-1

获取 Token

aws gamelift get-compute-auth-token \
    --fleet-id <fleet-id> \
    --compute-name DevLaptop1

设置当前环境变量,在 JNIGameLiftServerSDK 中已经设置了通过代码来读取这些环境变量

export WEBSOCKET_URL=wss://us-east-1.api.amazongamelift.com
export PROCESS_ID=PID1988
export FLEET_ID=<fleet-id>
export HOST_ID=DevLaptop1
export AUTH_TOKEN=<token>
export GAMELIFT_MODE=ANYWHERE # 声明当前环境为 ANYWHERE 的测试环境 

启动 Game Server Process

java -jar java-game-server-1.0-SNAPSHOT-all.jar

尝试创建 GameSession

aws gamelift create-game-session \
    --fleet-id <fleet-id> \
    --name DebugSession \
    --maximum-player-session-count 2 \
    --location custom-location-1

可以在 Console 上看到 GameSession 已经创建完毕

至此我们的本地测试就成功了。

远程登录游戏服务器调试

获取 Instance 信息

aws gamelift describe-instances --fleet-id <fleet-id>

获取 Instance 的登录凭证

aws gamelift get-compute-access --fleet-id <fleet-id> --compute-name <instance-id>

注:如果使用 server sdk 5.0.0 以下的版本,请参考此文档操作。

对于 Linux Instance:获取到如下的登录凭证,将其导出到环境变量

"Credentials": {
        "AccessKeyId": "<AK>",
        "SecretAccessKey": "<SK>",
        "SessionToken": "<Token>"
    }
export AWS_ACCESS_KEY_ID=<AccessKeyId>
export AWS_SECRET_ACCESS_KEY=<SecretAccessKey>
export AWS_SESSION_TOKEN=<SessionToken>

使用 ssm 登录

aws ssm start-session --target <instance-id>

程序放在/local/game 下

常见问题

  1. API Gateway 时如果传参放到 URL 中时需要使用映射模板来配置,类型为 application/json
    {
        "queryStringParameters": {
            #foreach($param in $input.params().querystring.keySet())
            "$param": "$util.escapeJavaScript($input.params().querystring.get($param))"
            #if($foreach.hasNext),#end
            #end
        }
    }
    
  1. 安装 Cmake3,参考官网文档
    sudo yum groupinstall "Development Tool"
    wget https://cmake.org/files/v3.27/cmake-3.27.5.tar.gz
    tar -xvzf cmake-3.27.5.tar.gz
    cd cmake-3.27.5
    ./bootstrap
    make
    sudo make install
    
  1. OpenSSL 问题
    sudo yum install openssl-devel

-fPIC(Position Independent Code)是一个编译选项,通常用于告诉编译器生成位置无关的代码。位置无关代码是一种可在内存中的任何位置执行的代码,而不依赖于固定的内存地址。这在共享库(动态链接库)的创建和加载过程中非常重要,因为共享库可以加载到不同的内存地址中。

  1. 如果想登录到 GameLift EC2 上时报错 SessionManagerPlugin is not found 问题
    SessionManagerPlugin is not found. Please refer to SessionManager Documentation here: http://docs.thinkwithwp.com/console/systems-manager/session-manager-plugin-not-found

    使用 ssm 登录实例时,遇到上述错误,参考文档链接安装 SessionManagerPlugin。

  1. 配置启动脚本时需要给脚本执行权限
    chmod +x start-game-server.sh

参考文档

https://thinkwithwp.com/blogs/gametech/online-multiplayer-amazon-gamelift-aws-serverless/

https://www.geeksforgeeks.org/shell-scripting-creating-a-binary-file/

https://docs.thinkwithwp.com/gamelift/latest/developerguide/fleets-remote-access.html

https://repost.aws/questions/QU_fx149ibQiaZZEEfaK2rBQ/how-to-remotely-login-to-fleet-instance-using-server-sdk-5-outdated-documentation

本篇作者

郭俊龙

亚马逊云科技解决方案架构师,主要负责游戏行业客户解决方案设计,比较擅长云原生微服务以及大数据方案设计和实践。

张凌钧

亚马逊云科技技术客户经理,负责企业级客户的架构和成本优化、技术支持等工作。在加入 AWS 之前就职于百度、360,拥有多年的服务开发、平台运维经验。