全球 OSS 低延迟解决方案
需求
海外游戏的用户遍布全球,对于一个 OSS,全球各地连接的速度都不一样。为了尽量让玩家能够以更低的延迟进行游戏,减少网络等待的时间,因此需要一个低延迟的 OSS 解决方案。
思路
- 增加位于全球各地的 OSS 服务器。
- 通过统一的接口获取所有 OSS 服务器地址列表,然后同时连接测速,选出耗时最少的服务器。
使用的工具与产品
- OSS
- 云函数
具体步骤
准备工作
1. 创建 OSS 与云函数
创建位于全球各地的 OSS 服务器,并且配套创建云函数服务器。
注意设置 OSS 的跨域、读写权限、子账号等(根据具体项目实行)。
2. 创建测速云函数
在每个云函数服务器上创建 speedTest
函数,该函数只要返回一个客户端能够识别的值即可。
// Http 触发器,nodejs6 环境,GET
'use strict';
module.exports.handler = function(request, response, context) {
console.log('success');
response.send('success');
}
3. 创建云函数地址列表
收集每个云函数服务器的地址,编入一个文件,放在一个可以让全球范围访问的 OSS 上(我们称之为 云函数地址列表)。
{
"USA": "https://xxx.us-west-1.fc.aliyuncs.com/2016-08-15/proxy/wechatgame/",
"India": "https://xxx.ap-south-1.fc.aliyuncs.com/2016-08-15/proxy/wechatgame/",
"Singapore": "https://xxx.ap-southeast-1.fc.aliyuncs.com/2016-08-15/proxy/wechatgame/",
"China": "https://xxx.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/wechatgame/"
}
4. 编写读和写 OSS 的云函数
云函数能够直接对同服务器的 OSS 进行读写,详情参考供应商文档。
游戏客户端
- 玩家进入游戏后检测是否已有云函数服务器地址。
- 如果没有云函数地址,则请求获取云函数地址列表,并且请求列表中云函数服务器上的
speedTest
接口,记录每个接口请求的耗时(这里建议同时请求,队列请求的话整个过程耗时会很大)。 - 获取耗时最少的云服务器地址,缓存(下次登录游戏就不需要重新获取)。游戏中需要进行 OSS 存储的数据,就可以通过该地址,存储在对应的 OSS 上。
this.url = null; // 最后选定出来的云函数
this.costTime = {}; // 记录测速时间
this.stopFlag = false; // 停止测速的 flag
this.cloudList = null; // 云函数地址列表
// 查看缓存中是否已经有云函数地址
checkCacheUrl() {
const url = localStorage.getItem('url');
if (!url) {
this.getUrlList();
} else {
// TODO: 通过 url 获取玩家信息等
cc.loader.load(url, (error, data) => {
if (error) {
// TODO: 重连,超过一定次数后重新调用 this.getUrlList(); 获取能够连接上的服务器
return;
}
// TODO: 游戏内其他逻辑
});
}
},
// 获取云函数列表
getUrlList() {
const listUrl = 'https://xxx.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/pub-read/cloud-list.json';
// 也可以用其他网络请求方式
cc.loader.load(listUrl, (error, data) => {
if (!error) {
this.cloudList = data;
this.length = this.cloudList.length;
this.testListSpeed();
}
});
},
// 对获取的云函数列表的地址进行测速
testListSpeed() {
for (const key in this.cloudList) {
if (this.cloudList.hasOwnProperty(key)) {
const url = this.cloudList(key);
this.speedTest(key, url);
}
}
// 间隔为 1s 的计时器。只要开始检测收到结果就可以停止
// 如果确定网络请求不会因为顺序阻塞,可以处理成收到回复就完成测速
this.intervalId = setInterval(() => {
const keys = Object.keys(this.costTime);
if (keys.length > 0) {
clearInterval(this.intervalId);
this.stopFlag = true;
this.getFastestUrl();
}
}, 1000);
},
// 测速
speedTest(key, url) {
const start = Date.now();
// url: https://xxx.com/projectName/
cc.loader.load(`${url}speedTest`, (error, data) => {
if (data === 'success' && this.stopFlag) {
const end = Date.now();
const delta = end - start;
this.costTime[key] = delta;
}
});
},
// 对比各个地址连接耗时
getFastestUrl() {
let min = Number.MAX_SAFE_INTEGER;
for (const key in this.costTime) {
if (this.costTime.hasOwnProperty(key)) {
const cost = this.costTime[key];
if (cost < min) {
min = cost;
this.url = this.cloudList[key];
}
}
}
// 缓存
if (this.url) {
localStorage.setItem('url', this.url);
}
},
缺陷
- 目前每个 OSS 之间数据不互通。服务器地址缓存在本地,一旦玩家更换设备,或者因为连不上原本的服务器而被连接到新的云函数服务器上,之前的游戏数据无法获取。
- 没有用户系统时更换设备无法获取原本玩家的数据。
改进方案(待实现与验证)
- 使用 IP 进行范围定位,而不是通过请求耗时。
- 研究 OSS 之间数据同步的可行性。
- 网络异常时,再执行一次测速,将玩家数据更新到新筛选出的云函数地址,并更新缓存的地址(或者在玩家每次登录都执行此操作)