◆ 实战:使用AWS平台实现Serverless架构
本例将演示利用AWS平台的Serverless架构来让游戏实现全球同服。
全球同服的游戏架构有以下需求。
·全球所有玩家的持久化信息(包括用户基本信息、等级、装备、进度等状态信息)都保存在中心站点。玩家统一通过HTTP(S)登录中心站点并获取状态信息。
·对战初始,由中心站点对玩家进行重定向到对应的Game Server。
在对战过程中,使用TCP长连接从而保证更好的游戏体验。
·对战结束后,客户端与Game Server中断TCP连接,对战结果数据回滚到中心站点并保存最终的状态信息。
基于上述的架构,游戏完全构建在统一的“大世界”中(唯一中心站点),并且由分布在全球的Game Server来保证游戏的低延迟。由于Game Server分布在全球不同的地区,如何做到资源的快速扩展和按需伸缩将是一个难点。下面将以Serverless架构的方式阐述实现这一需求。
首先,AWS平台提供了非常完整的API接口,开发者可以选择各种语言的SDK完成对资源的调度,这里我们可以将代码运行在Lambda中。如下所示,我们的中心站点(即Lambda部署的站点)选择的是Virginia(弗吉尼亚,美国东部地区),通过Node.js SDK跨地区到Tokyo(东京,日本首都)来启动EC2服务器。
代码语言:javascript复制var AWS = require('aws-sdk');
exports.handler = function (event, context) {
console.log("Received data as:", event);
var ec2 = new AWS.EC2({region: 'ap-northeast-1'});
var params = {
ImageId: 'ami-29160d47',
InstanceType: 't2.micro',
KeyName: 'Tech-labs',
SecurityGroupIds: ['sg-d0aa1bb4'],
IamInstanceProfile: {Name: 'EC2-Admin'},MinCount: 1,
MaxCount: 1
};
// 创建实例
ec2.runInstances(params, function (err, data) {
if (err) {
console.log("Could not create instance", err);
context.fail(err);
}
var instanceId = data.Instances[0].InstanceId;
console.log("Created instance", instanceId);
// 存储实例id并设置状态
context.succeed(instanceId);
});
};
由于启动EC2的过程是一个异步过程,所以我们需要记录相关的服务器启动信息,并定义另一接口接收Game Server在服务就绪后返回的回执信息,代码如下。
代码语言:javascript复制var AWS = require('aws-sdk');
exports.handler = function (event, context) {
console.log("Received data as:", event);
var instanceId = event.instanceId;
var region = event.region;
var publicIp = event.publicIp;
var version = event.version;
...
// 检查instanceId并在线更新实例状态
};
同时,这种回执接口的API(包括其他API)都可以考虑使用Amazon API Gateway服务进行部署。API Gateway可以帮助我们将现有函数快速发布为RESTful的API接口,并同时利用CloudFront的边缘节点进行部署,以保证访问端能获得更低的延迟。按照上例的回执,Lambda函数可以构造API Gateway的配置,如图10-5所示。
图10-5 API Gateway的配置
请求示例如下。
代码语言:javascript复制/game/servers/i-dd861842
{
"region":"ap-northeast-1",
"publicIp":"52.193.34.102",
"version":"110"
}
接下来,为了确保Game Server的状态是正常的,使得玩家能被路由到正确的服务器上,可以构造另一个类似心跳的Lambda函数,用来接收Game Server的状态信息。心跳频率可根据需求进行调整,当然,如果在频率不需要很高的情况下(≥1min),也可以利用CloudWatch来发起报警,并同时发起SNS通知Lambda函数以更新Game Server的状态。
最后,在Game Server具备了自动按需扩展(Scale out)的能力后,我们就需要考虑如何解决Game Server的缩减(Scale in)了。在这里,我们采用CloudWatch->SNS->Lambda(cross region)的方式来实现GameServer的缩减,具体流程说明如下。
(1)Game Server自定义指标(Custom Metrics)将当前服务器的在线人数发送到CloudWatch中。
代码语言:javascript复制#!/bin/bash
#get instance-id from local meta-data id=$(curl -s http://169.254.169.254/
latest/meta-data/instance-id)
#get current onlinePlayers players=$(...) aws cloudwatch put-metric-data --metric-name
"OnlinePlayers" --namespace "GameServer" --dimension InstanceId= $id --value $players
(2)设定CloudWatch的报警规则,当服务器在线人数为零时,会触发SNS通知,如图10-6所示。
图10-6 CloudWatch自定义指标报警
在实际场景中,需要通过以下脚本自动建立报警。
代码语言:javascript复制aws cloudwatch put-metric-alarm --alarm-name $id-players--alarm-description "Alarm when
online players less than 0" --metric-name "OnlinePlayers" --namespace "GameServer" --dimension
"Name=InstanceId,Value=$id" --statistic Maximum --period 300 --threshold 0
--comparison-operator LessThanOrEqualToThreshold --evaluation-periods 2 --alarm-actions
arn:aws:sns:ap-northeast-1:111111111222: ScaleInTopic
(3)订阅了SNS服务通知的中心站点的Lambda函数,用于终止服务器,如图10-7所示。
图10-7 Lambda函数订阅SNS服务通知
用于终止服务器的Lambda函数如下。
代码语言:javascript复制var AWS = require('aws-sdk');
exports.handler = function (event, context) {
console.log("Received data as:", event);
var message = JSON.parse(event.Records[0].Sns.Message);
var region = event.Records[0].EventSubscriptionArn.split(":")[3];
console.log("Need to terminate the server in region:", region);
var ec2 = new AWS.EC2({region: region});
console.log("Need to terminate the server:", message);
var instanceId = message.Trigger.Dimensions[0].value;
console.log("Need to terminate the server:", instanceId);
// 检查实例的状态是否可以从DynamoDB中终止,并在终止时更新状态
var params = {InstanceIds: [instanceId]};
// 终止实例
ec2.terminateInstances(params, function (err, data) {
if (err) {
console.log("Could not terminate instance", err);
// 回滚终止的实例
context.fail(err);
}
for (var i in data.TerminatingInstances) {
var instance = data.TerminatingInstances[i];
console.log('TERM:t' instance.InstanceId);
// 删除终止的实例
}
context.succeed(data.TerminatingInstances);
});
};
通过以上方法,我们已基本实现了基于事件触发的Serverless架构对全球分布的Game Server的调度,Serverless全球同服游戏架构如图10-8所示。
图10-8 Serverless全球同服游戏架构
来源:
https://www.toutiao.com/i6967972069267259937/
“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com