Colyseus是比较著名的后端多人在线游戏框架,基于nodejs环境运行,本指南旨在帮您实现在docker的镜像打包、发布,配合阿里云的容器镜像、云redis、弹性集群k8s服务实现集群化部署,在集群容器内部使用pm2实现多进程运行,并通过redis实现跨服务器的房间通讯;
为什么要集群化部署?
- 当下主流的后端服务配置方案;
- 单台服务器提供的算力有限,有升级瓶颈,集群化则可以通过增加机器数量,来突破这个瓶颈,理论上没有扩容的上限;
- 更安全可靠的服务支撑能力,一个服务器或一个进程出现故障,其他服务器可以继续提供服务,避免服务宕机;
- 不停机发布和更新代码,容器不会被全部重启,而是轮流提供服务支撑,直到全部服务更新完成;
前置知识
您需要掌握colyseus的基础使用,本指南不再对colyseus的基础使用部分作出说明。
所有教程基于这里的样板间源码改造:github.com/endel/colys…
上面的教程已经帮您做好了基础部分,包括创建、加入房间,以及在房间里面如何互动,唯一的缺点就是不能部署在集群服务上面,这是因为上面的样板间代码,是把房间数据维护在服务器内存里面,多个服务器则存在内存不共享的问题,接下去了我们一步步来,看下如何解决这个问题;
接入Redis:
(如何申请阿里云redis请看文章结尾)
我们通过redis来同步每个服务器的房间列表和房间内的数据,代码具体涉及到对server部分和端口部分的改造,presence参数代表房间类的数据,driver属性代码同步房间列表数据,具体改造如下(改造发生在server/src/index.ts文件下):
server部分原来的代码
const server = http.createServer(app);
const gameServer = new Server({
server: server
});
server部分改造后的代码
import { RedisDriver } from "@colyseus/redis-driver";
import { Server, RedisPresence } from "colyseus";
import { redis_url } from "./redisConfig";
//添加RedisPresence和RedisDriver
const gameServer = new Server({
presence: new RedisPresence({
url: redis_url
}),
driver: new RedisDriver({
url: redis_url
}),
});
PM2配置
我们使用PM2来做nodejs多进程管理,以下是ecosystem.config.js的配置部分:
7000端口为程序的起始端口,如下的配置会根据系统的cpu数量划分进程数量,如:容器有4个cpu,则会启动4个服务,端口分别为7000、7001、7002、7004;
script是ts代码运行tsc之后的js代码;
// ecosystem.config.js
const os = require('os');
const {redis_url} = require('./lib/redisConfig');
module.exports = {
apps: [
{
name : "colyseus",
script : "lib/index.js", // your entrypoint file
watch : true, // optional
instances : os.cpus().length,
exec_mode : 'fork', // IMPORTANT: do not use cluster mode.
env: {
PORT:7000,
IP:"0.0.0.0",
DEBUG: "colyseus:errors",
NODE_ENV: "production",
}
},
{
name : "proxy",
script : "lib/proxy.js",
instances : 1, // scale this up if the proxy becomes the bottleneck
exec_mode : 'cluster',
env: {
PORT:9001,
IP:"0.0.0.0",
REDIS_URL: redis_url
}
}
]
}
proxy则是完成了代理部分,为什么要设置代理?这是因为我们如果出现A服务器加入B服务器的房间,或A进程加入B进程的房间,这可能导致两个服务的socket链接不互通导致加入房间失败,所以我们通过http-proxy完成了此处的代理工作;
proxy的代码参考这里:github.com/colyseus/pr…
其次,我们需要对程序入口文件(index.ts)做改造,以适配pm2的动态端口号;
const PORT =( Number(process.env.PORT) + Number(process.env.NODE_APP_INSTANCE))||9001;
...略
gameServer.listen(PORT);
至此关于服务多进程的改造完成,我们可以通过下面的命令运行服务,来进程多进程部署:
tsc && pm2-docker start ecosystem.config.js
如果不是docker环境下,则使用这个命令:
tsc && pm2 start ecosystem.config.js
容器镜像相关操作
我们使用Docker虚拟机来维护我们的项目,容器镜像文件则是把项目编译成一个带有环境和代码的简易服务器操作系统的安装文件,到时候只要服务器安装这个系统,就可以跑我们的服务了。
配置package.json添加docker模式的启动命令:
"scripts": {
"start": "ts-node-dev src/index.ts",
"start2": "ts-node src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"pm2": "pm2 start",
"docker": "npm run build && pm2-docker start ecosystem.config.js",//添加这个
"pm2stop": "pm2 delete all",
"proxy": "ts-node src/proxy.ts"
},
配置DockerFile文件
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 9001
CMD [ "npm","run","docker" ]
.dockerignore配置(和.gitigonere一样,排除一些不需要的文件)
node_modules
npm-debug.log
编译镜像(上面说过了,编译成docker虚拟机的镜像文件,相当于带有代码和环境的服务器操作系统安装包)
docker build . -t renjianfeng/1
使用vscode的docker插件把编译好的镜像tag到阿里云上面申请的仓库(如何申请阿里云容器镜像存储请看文章结尾)
push到线上镜像仓库
创建集群和应用
到这里,我们代码中的相关指南就结束了,接下来看下如何部署到阿里云的k8s severless集群;
创建集群
创建应用:
点击上图创建好的集群,点击集群名称,在工作负载中选择【无状态】-【使用镜像模板】
填写应用名称,副本数量只得就是用多少个分布式容器跑应用:
选择刚刚上传的镜像和对应的版本:
端口选择我们代码中的代理端口,协议是TCP:
创建外网网关访问ip
请按照这个配置来:
看到下面的配置就说明应用创建成功:
查看应用详情:
获取外部ws公网ip:
把公网ip配置在客户端就可以通过这个集群来跑服务了:
配套服务的相关入口:
申请阿里云redis:
申请容器镜像服务:
注意事项
1.每次测试完,记得运行下面的代码清理redis历史数据,否则会出现被代理到不存在的服务器上:
npm install -g redis-cli
rdcli -u 【redisurl】 flushall
2.如果出现redis连接失败相关的报错,大概率是redis没有添加白名单,添加后会恢复正常连接;
Http-proxy的应用原理说明
我们可以从ecosystem.config.js的配置中看到,我们除了给colyseus的框架本身设置了redis的链接地址用于同步房间列表、跨房间的消息之外,还给colyseus-proxy设置了redis地址,其中colyseus-proxy的redis的作用就是用于保存pm2在当前服务器中的进程链接地址列表和房间id之间的匹配关系,如:ws:x.x.x.x:7001/roomid 这样的地址,这样多台服务链接到一个redis数据库,代理就会通过数据库的地址和玩家加入的房间号,把同一房间的玩家代理到同一个服务器里面去,避免跨服务器、跨进程的房间加入情况出现。
注意,redis不会负责跨进程、跨服务器的帧数据同步,即便redis性能再高也不适合做帧数据同步,帧数据同步只能放在服务器自己的内存里面,redis里面的数据只是负责把你正确的拉入到对应的房间,匹配到正确的集群子服务器和子进程;