运行架构说明:
客户端通过 websocket 长连接连到 connector 服务器群。
connector 负责承载连接,并把请求转发到后端的服务器群。
后端的服务器群主要包括按场景分区的场景服务器 (area)、聊天服务器 (chat) 和状态服务器等 (status),这些服务器负责各自的业务逻辑。真实的案例中还会有各种其它类型的服务器。
后端服务器处理完逻辑后把结果返回给 connector,再由 connector 广播回给客户端。 master 负责统一管理这些服务器,包括各服务器的启动、监控和关闭等功能。
pomelo 包括以下几部分:
框架, 框架是 pomelo 最核心的部分。
库,pomelo 提供了很多库,有些是跟游戏逻辑完全相关的,如 AI , AOI ,寻路等;也有与游戏逻辑无关的,如定时任务执行, 数据同步。
工具,pomelo 提供了管理控制台、命令行工具、压力测试工具等一系列工具。
各类客户端, pomelo 提供了各类平台的客户端,包括 js, C, android, iOS, unity3d 等,这些都可以从 pomelo 的官方主页查到。
Demo, 一个框架需要强大的 demo 来展示功能,pomelo 提供了全平台的聊天 demo 和基于HTML5 的捡宝Demo ,系统还提供了一个强大的基于HTML5 开发的强大的MMO 游戏demo Lord Of Pomelo 。
而最妙的地方在于所有这些组件都是松耦合的,所有这些组件都可以独立使用。
6.支持动态增加和移除服务器进程机制,并提供相应的命令行工具。但由此而导致的路由规则的变化应当由具体的应用自己来维护。
//动态添加服务器
pomelo add host=[host] port=[port] id=[id] serverType=[serverType]
//动态移除服务器
pomelo stop [id]
{
"development":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true}
]
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":6050, "auto-restart": true}
]
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
]
}
}
{
"development":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true, "cpu": 2}
]
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":6050, "cpu": 1}
]
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true, "cpu": 3}
]
}
}
app.configure('production|development', 'connector', function() {
var fun = function(app, cb){
//do something
cb();
}
app.beforeStopHook(fun);
});
//客户端
javascript pomelo.init({ host:'127.0.0.1', port:3014, encrypt:true }, function() { // do something connected });
//服务端
app.set('connectorConfig', {
connector: pomelo.connectors.hybridconnector,
heartbeat: 3,
useDict: true,
useProtobuf: true,
useCrypto: true
});
{ "type": "file", "filename": "./logs/rpc-debug-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups":5, "category": "rpc-debug" }
##rpc filter提供对外接口 根据网友的建议,在新版本中对外提供了添加rpc filter的接口,包括rpcBefore、rpcAfter、rpcFilter,其功能分别为添加before filter, 添加after filter,同时添加两种filter;使用方法与handler的filter类似。上面提供的enableRpcLog选项,可以通过添加rpc filter代替。
app.configure('production|development', function() {
// configurations
app.filter(pomelo.filters.time());
app.rpcFilter(pomelo.rpcFilters.rpcLog());
});
{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort":3050, "frontend":true, "max-connections": 100}
首先在game-server/app/servers/chat目录下增加cron目录,在game-server/app/servers/chat/cron目录下编写具体的执行的任务的代码chatCron.js,例如:
module.exports = function(app) {
return new Cron(app);
};
var Cron = function(app) {
this.app = app;
};
var cron = Cron.prototype;
cron.sendMoney = function() {
console.log(’%s server is sending money now!’, this.app.serverId);
};
然后在game-server/config/目录下增加定时任务配置文件crons.json,具体配置文件如下所示:
{
“development”:{
“chat”:[
{“id”:1, “time”: “0 30 10 * * *”, “action”: “chatCron.sendMoney”},
{“id”:2, “serverId”:“chat-server-1”, “time”: “0 30 10 * * *”, “action”: “chatCron.sendMoney”}
]
},
“production”:{
“chat”:[
{“id”:1, “time”: “0 30 10 * * *”, “action”: “chatCron.sendMoney”},
{“id”:2, “serverId”:“chat-server-1”, “time”: “0 30 10 * * *”, “action”: “chatCron.sendMoney”}
]
}
}
在配置文件crons.json中,id是定时任务在具体服务器的唯一标识,且不能在同一服务器中重复;time是定时任务执行的具体时间,时间的定义跟linux的定时任务类似,一共包括7个字段,每个字段的具体定义如下:
* * * * * command to be executed
| | | | | |
| | | | | ±---- day of week (0 - 6) (Sunday=0)
| | | | ±------ month (0 - 11)
| | | ±-------- day of month (1 - 31)
| | ±---------- hour (0 - 23)
| ±------------ min (0 - 59)
±------------ second (0 - 59)
0 30 10 * * * 这就代表每天10:30执行相应任务;serverId是一个可选字段,如果有写该字段则该任务只在该服务器下执行,如果没有该字段则该定时任务在所有同类服务器中执行;action是具体执行任务方法,chatCron.sendMoney则代表执行game-server/app/servers/chat/cron/chatCron.js中的sendMoney方法。
通过pomelo-cli的addCron和removeCron命令可以动态地增加和删除定时任务,其中addCron的必要参数包括:id,action,time;removeCron的必要参数包括:id;serverId和serverType是两者选其一即可。例如:
addCron id=8 ‘time=0 30 11 * * *’ action=chatCron.sendMoney serverId=chat-server-3
removeCron id=8
在之前pomelo的版本中,filter是在后端服务器进行消息拦截并进行相应处理;根据网友的意见,在0.7版中在前端服务器增加了filter,请求在前端服务器就可以进行统一处理。请求的处理过程由之前的:前端服务器 -> beforeFilter -> 后端服务器 -> afterFilter 变为:globalBeforeFilter -> 前端服务器 -> beforeFilter -> 后端服务器 -> afterFilter -> globalAfterFilter。同之前的filter的错误处理过程一样,全局filter的错误全部进入globalErrorHandler处理。
全局filter与以前的filter可以相互通用,具体的配置样例如下:
app.configure(‘production|development’, function() {
app.globalFilter(pomelo.serial());
});
app.configure('development', 'connector', function() {
app.set('pushSchedulerConfig', {
scheduler: [
{
id: 'direct',
scheduler: pomelo.pushSchedulers.direct
},
{
id: 'buffer5',
scheduler: pomelo.pushSchedulers.buffer,
options: {flushInterval: 5000}
},
{
id: 'buffer10',
scheduler: pomelo.pushSchedulers.buffer,
options: {flushInterval: 10000}
}
],
selector: function(reqId, route, msg, recvs, opts, cb) {
// opts.userOptions is passed by response/push/broadcast
console.log('user options is: ', opts.userOptions);
if(opts.type === 'push') {
cb('buffer5');
return;
}
if (opts.type === 'response') {
cb('direct');
return ;
}
if (opts.type === 'broadcast') {
cb('buffer10');
return ;
}
}
});
新的pushScheduler配置与原有的一个connector仅仅支持一个pushScheduler的配置方式保持兼容。
"connector":[
{"host":"127.0.0.1", "port":"4050++", "clientPort": "3050++", "frontend": true, "clusterCount": 3}
],
"chat":[
{"host":"127.0.0.1", "port":"6050++", "clusterCount": 3}
],
"gate":[
{"host": "127.0.0.1", "clientPort": 3014, "frontend": true, "clusterCount": 1}
]
同时在采用pomelo-cli进行动态增加服务器的时候,同样可以使用add host=127.0.0.1 port=9000++ serverType=chat clusterCount=3 这样的形式同时增加多台服务器。
{
......
"levels": {
"pomelo" : "INFO",
"rpc-log" : "INFO",
"forward-log": "ERROR",
"con-log": "INFO"
},
"replaceConsole": true,
"reloadSecs": 60 * 3
}
该配置表示每3分钟检查一次配置文件是否有更新。
原理
RPC服务端每接受一个连接都会抛出一个连接事件, 这个事件中含有该连接的socket.id和RPC客户端IP. RPC服务端会捕获该连接事件, 并调用用户传入的获取IP白名单的函数, 如果该RPC客户端IP不在白名单中, 则立刻将对应的socket断开. 以此来实现RPC调用白名单过滤功能.
使用
使用时只需要向remoteConfig的配置中传入一个获取IP白名单的函数(whitelist: rpcWhitelist.whitelistFunc)即可, 这个函数需要接受一个回调函数作为其参数, 该回调函数形如function(err, tmpList) {…}. 在获取IP白名单的函数内, 拿到IP白名单时(该白名单应为一维JS Array), 以类似于cb(null, self.gWhitelist)的形式调用IP过滤回调函数.
./game-server/app/util/whitelist.js
... ...
var self = this;
self.gWhitelist = ['192.168.146.100', '192.168.146.101'];
module.exports.whitelistFunc = function(cb) {
cb(null, self.gWhitelist);
};
... ...
./game-server/app.js
var rpcWhitelist = require('./app/util/whitelist');
... ...
// configure for global
app.configure('production|development', function() {
... ...
// remote configures
app.set('remoteConfig', {
cacheMsg: true
, interval: 30
, whitelist: rpcWhitelist.whitelistFunc
});
... ...
}
./game-server/app/util/whitelist.js
... ...
var self = this;
self.gWhitelist = ['192.168.146.100', '192.168.146.101'];
module.exports.whitelistFunc = function(cb) {
cb(null, self.gWhitelist);
};
... ...
./game-server/app.js
var adminWhitelist = require('./app/util/whitelist');
... ...
// configure for global
app.configure('production|development', function() {
... ...
app.set('masterConfig', {
authUser: app.get('adminAuthUser') // auth client function
, authServer: app.get('adminAuthServerMaster') // auth server function
, whitelist: adminWhitelist.whitelistFunc
});
... ...
}
具体使用方法:
安装zeromq
在app.js中进行配置,具体配置如下所示:
var zmq = require(‘pomelo-rpc-zeromq’);
app.configure(‘production|development’, function() {
app.set('proxyConfig', {
rpcClient: zmq.client
});
app.set('remoteConfig', {
rpcServer: zmq.server
});
});
具体使用示例可以参考 chatofpomelo zmq分支
pomelo-rpc-zeromq性能测试报告与原有的pomelo-rpc的性能测试报告可以参考。
原理
connector每接受一个连接都会抛出一个连接事件, 这个事件中含有该连接的客户端IP. connector会捕获该连接事件, 并调用用户传入的获取IP黑名单的函数, 如果该客户端IP在黑名单中, 则立刻将对应的socket断开. 以此来实现连接服务器的黑名单过滤功能.
使用方法
静态添加黑名单
使用时只需要向在connector的connectionConfig配置中传入一个获取IP黑名单的函数即可, 这个函数需要接受一个回调函数作为其参数, 该回调函数形如function(err, list) {…}. 在获取IP黑名单的函数内, 拿到IP黑名单时(该黑名单应为一维JS Array), 以类似于cb(null, self.list)的形式调用IP过滤回调函数,具体使用方法如下:
./game-server/app/util/blackList.js
... ...
var self = this;
self.blackList = ['192.168.100.1', '192.168.100.2'];
module.exports.blackListFun = function(cb) {
cb(null, self.blackList);
};
... ...
./game-server/app.js
var blackList = require('./app/util/blackList');
... ...
app.configure('production|development', function() {
... ...
app.set('connectorConfig', {
blacklistFun: blackList.blackListFun
});
... ...
}
动态添加黑名单
动态添加黑名单可以通过pomelo-cli完成,其中运行输入具体ip或者正则表达式,具体命令如下:
blacklist 192.168.100.1
blacklist (([01]?d?d|2[0-4]d|25[0-5]).){3}([01]?d?d|2[0-4]d|25[0-5])
add(key, value, cb)
add key value pairs
remove(key, value, cb)
remove key value pairs
load(key, cb)
load all values
removeAll(key, cb)
remove all values
具体的使用方法如下所示:
var store = require(’./store’);
app.set(‘channelConfig’, {
store : store,
prefix : ‘pomelo’
});
//store.js
var redis = require(‘redis’);
var StoreManager = function() {
this.redis = redis.createClient(6379, ‘127.0.0.1’, {});
};
module.exports = new StoreManager();
StoreManager.prototype.add = function(key, value, cb) {
this.redis.sadd(key, value, function(err) {
cb(err);
});
};
StoreManager.prototype.remove = function(key, value, cb) {
this.redis.srem(key, value, function(err) {
cb(err);
});
};
StoreManager.prototype.load = function(key, cb) {
this.redis.smembers(key, function(err, list) {
cb(err, list);
});
};
StoreManager.prototype.removeAll = function(key, cb) {
this.redis.del(key, function(err) {
cb(err);
});
};
使用方法:
// app configuration
app.configure('production|development', 'connector', function() {
app.set('connectorConfig',
{
connector : pomelo.connectors.udpconnector,
heartbeat : 3
});
});
由于pomelo中提供的客户端还没有支持udp的,所以在1.0.0中提供一个node版本的udp client,配合通过pomelo init出来的demo使用,具体可以参考udpclient
配置方法:
// rr
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
routerType: 'rr'
});
});
//基于权重的rr,在servers.json中对rpc目标服务器进行权重配置
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
routerType: 'wrr'
});
});
"read": [
{"id": "read-server-1", "host": "127.0.0.1", "port": 4150, "weight": 1},
{"id": "read-server-2", "host": "127.0.0.1", "port": 4151, "weight": 5},
{"id": "read-server-3", "host": "127.0.0.1", "port": 4152, "weight": 8}
]
//la
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
routerType: 'la'
});
});
//consistent_hash
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
routerType: 'ch',
replicas: '100', //虚拟节点数量
algorithm: 'md5', //hash算法
hashFieldIndex: 0 //根据rpc参数列表中的具体参数进行hash
});
});
ps: 如果使用toServer(‘chat-server-1’)这种指定rpc目标服务器的rpc调用则不会使用相关的负载均衡算法,如果是指定了routerType则之前在application对象中设置的route函数则无效;综合这三种方式的优先级是toServer > 指定routerType > 指定路由函数。
在新版本中,pomelo-rpc模块提供了相关的容错机制,包括failover,即失败自动切换,当出现失败,重试其它同类型服务器,这种模式通常用于读操作,但重试可能会带来更长延迟;failfast是快速失败,其策略为只发起一次rpc调用,失败后就立即将错误信息返回;failsafe则是一种安全策略,也是rpc默认采用的,即根据rpc的不同类型错误进行不同的处理策略,主要是发起连接重试和发送重试操作。
配置方法:
//快速失败
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
failMode : 'failfast'
});
});
//切换服务器
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
failMode : 'failover'
});
});
//安全策略
app.configure('production|development', 'connector', function() {
app.set('proxyConfig',
{
failMode : 'failsafe',
retryTimes: 3, //重试次数
retryConnectTime: 5 * 1000 //重连间隔时间
});
});
ps: rpc默认采用安全策略。
使用方法:
支持tls客户端,pomelo现在提供的tls客户端有libpomelo.
app.configure('production|development', 'connector', function() {
app.set('connectorConfig',
{
connector : pomelo.connectors.hybridconnector,
heartbeat : 3,
useDict : true,
useProtobuf : true,
ssl: {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
}
});
});
支持wss客户端, pomelo提供的wss客户端有js客户端.
app.configure('production|development', 'connector', function() {
app.set('connectorConfig',
{
connector : pomelo.connectors.hybridconnector,
heartbeat : 3,
useDict : true,
useProtobuf : true,
ssl: {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
}
});
});
支持socket.io的wss客户端, pomelo提供的socket.io的wss客户端有js客户端.
app.configure('production|development', 'connector', function() {
app.set('connectorConfig',
{
connector : pomelo.connectors.sioconnector,
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt')
});
});
在pomelo 1.0中增加了通过pomelo init 获取wss和socket.io的wss两种客户端及服务端的初始化项目,同时初始化的项目中提供了相应的密钥及证书。注意由于证书是和域名绑定的,所以在打开客户端的时候输入的ip地址为 https://127.0.0.1:3001
使用方法:
//app.js配置方法
app.configure('production|development', 'master', function() {
app.use(scale, {
scale: {
cpu: {
chat: 5,
interval: 10 * 1000,
increasement: 1
},
memory: {
connector: 5,
interval: 15 * 1000,
increasement: 1
},
backup: 'config/development/backupServers.json'
}
});
});
//backupServer.json配置
{
"connector":[
{"id":"backup-connector-server-1", "host":"127.0.0.1", "port":4053, "clientPort": 3053, "frontend": true},
{"id":"backup-connector-server-2", "host":"127.0.0.1", "port":4054, "clientPort": 3054, "frontend": true},
{"id":"backup-connector-server-3", "host":"127.0.0.1", "port":4055, "clientPort": 3055, "frontend": true}
],
"chat":[
{"id":"backup-chat-server-1", "host":"127.0.0.1", "port":6053},
{"id":"backup-chat-server-2", "host":"127.0.0.1", "port":6054},
{"id":"backup-chat-server-3", "host":"127.0.0.1", "port":6055}
]
}
配置参数说明:
现在监控指标包括cpu和memory两项,在每一个监控指标内可以有监控的服务器类型,例如chat:5,这样就表示chat类型的服务器的阈值为5%,当chat类型的服务器cpu的平均值超过5%后,系统将自动扩展服务器,服务器一次扩展的数量由increasement参数决定,例如increasement参数为1,则表示每次超过阈值后扩展1个服务器,扩展服务器的列表由用户指定,backup参数就是扩展的服务器列表;另外interval参数表示系统检测时间,单位是秒,例如interval: 15 * 1000表示系统每15秒检测一次相应的指标,如果超过该指标则进行相应的扩展。
github地址: pomelo-scale-plugin