网易说pomelo很容易扩容,那么就来看看到底是不是这样?
前面2篇文章里面,只有一个前台connector和后台服务,那么当一个前台服务和一个后台服务无法支撑大量客户端的时候该怎么办呢?
显然需要对服务器进行扩容。
前面单个服务器的框架大概就是这样的:
所有的客户端都连接到一个connector,然后这个connector就把所有的请求转发给一个后台服务chat。
现在来尝试先扩展前台connector。
扩展前台connector
修改配置文件,增加2个connector,并且新增一个gate,如:(servers.json)
"gate":[ {"id": "gate-server-1", "host": "115.28.53.90", "port": 3200,"clientPort": 3020, "frontend": true} ], "connector": [ {"id": "connector-server-1", "host": "115.28.53.90", "port": 3150, "clientPort": 3010, "frontend": true}, {"id": "connector-server-2", "host": "115.28.53.90", "clientPort": 3011, "frontend": true}, {"id": "connector-server-3", "host": "115.28.53.90", "clientPort": 3012, "frontend": true} ],
因为新增了一个前台服务器,所以,还得改一下adminServer.json
[ { "type":"gate", "token":"agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rm1" }, { "type": "connector", "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" }, { "type":"chat", "token":"agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rmm" } ]
最后还要在game-server/app.js里面增加几行代码 (一开始没注意到,所以,客户端老是连不上gate,最后发现这里也要改)
var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', 'HelloWorld'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, heartbeat : 3, useDict : true, useProtobuf : true }); }); app.configure('production|development', 'gate', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, useProtobuf : true }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); });
就增加了app.configure这段,现在有两个app.configure了。
新增一个gateHandler.js,放在game-server/app/servers/gate/handler/下
代码:
var crc = require('crc'); module.exports = function(app) { return new Handler(app); }; var Handler = function(app) { this.app = app; }; var handler = Handler.prototype; /** * Gate handler that dispatch user to connectors. * * @param {Object} msg message from client * @param {Object} session * @param {Function} next next stemp callback * */ handler.queryEntry = function(msg, session, next) { var uid = msg.uid; if(!uid) { next(null, { code: 500 }); return; } // get all connectors var connectors = this.app.getServersByType('connector'); if(!connectors || connectors.length === 0) { next(null, { code: 500 }); return; } // select connector var index = Math.abs(crc.crc32(uid)) % connectors.length;//计算CRC值,然后挑选一个前台connector var res = connectors[index]; next(null, { code: 200, host: res.host, port: res.clientPort });//把挑选出来的前台connector地址和端口告诉客户端。 };
gateHandler的任务就是分流,将某个客户端按照一定规则,挑选一个前台connector,然后客户端根据返回的前台connector地址,端口再进行连接。
客户端
客户端需要走两步:
1. 连接gate
2. 根据返回的connector地址,端口再进行连接。
代码就像:
var pomelo = window.pomelo; var host = "115.28.53.90"; var port = "3020"; var router = "gate.gateHandler.queryEntry"; function connect(host, port)//连接connector服务器,这是个长连接 { var router = "connector.entryHandler.entry"; pomelo.disconnect(); pomelo.init({ host: host, port: port, log: true }, function() { pomelo.request(router, {uid:myform.uid.value}, function(data) { myform.login_status.value= 'all users:' + data.users; }); } ); } function onLogin() {//当用户点下“login”的时候,先连接gate pomelo.disconnect(); pomelo.init({ host: host, port: port, log: true }, function() {//连接成功,那么就向gate发送uid pomelo.request(router, {uid:myform.uid.value}, function(data) { alert('try to connect server, host: ' + data.host + ', port: ' + data.port); connect(data.host, data.port);//得到前台connector的地址,端口,重新连接 }); } ); return false; }
OK, 现在每当客户端发起连接的时候,gate都会选择一个connector,并且回传地址,端口,这样客户端就可以连接到适当的connector了。看起来就像:
接下来就是扩展后台服务器了。
扩展后台服务器
同样,先配置servers.json
{ "development":{ "gate":[ {"id": "gate-server-1", "host": "115.28.53.90", "port": 3200,"clientPort": 3020, "frontend": true} ], "connector": [ {"id": "connector-server-1", "host": "115.28.53.90", "port": 3150, "clientPort": 3010, "frontend": true}, {"id": "connector-server-2", "host": "115.28.53.90", "port": 3151, "clientPort": 3011, "frontend": true}, {"id": "connector-server-3", "host": "115.28.53.90", "port": 3152, "clientPort": 3012, "frontend": true} ], "chat":[ {"id":"chat-server-1", "host":"115.28.53.90", "port":6050}, {"id":"chat-server-2", "host":"115.28.53.90", "port":6051}, {"id":"chat-server-3", "host":"115.28.53.90", "port":6052} ] }, "production":{ "gate":[ {"id": "gate-server-1", "host": "115.28.53.90", "port": 3200,"clientPort": 3020, "frontend": true} ], "connector": [ {"id": "connector-server-1", "host": "115.28.53.90", "port": 3150, "clientPort": 3010, "frontend": true}, {"id": "connector-server-2", "host": "115.28.53.90", "port": 3151, "clientPort": 3011, "frontend": true}, {"id": "connector-server-3", "host": "115.28.53.90", "port": 3152, "clientPort": 3012, "frontend": true} ], "chat":[ {"id":"chat-server-1", "host":"115.28.53.90", "port":6050}, {"id":"chat-server-2", "host":"115.28.53.90", "port":6051}, {"id":"chat-server-3", "host":"115.28.53.90", "port":6052} ] } }
增加2个后台服务器chat
如果现在就运行的话,我们会发现前端服务在转发请求给后端服务的时候,有时是chat-server-1,有时是chat-server-2,有时是chat-server-3.
其实pomelo有个默认的路由,如果我们自己不指定的话,那么就会使用默认的路由来选择后端服务。
通常我们都要提供自己的路由,这样才可控。增加了后端服务路由后,代码看起来像这样:
var pomelo = require('pomelo'); var crc = require('crc'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', 'HelloWorld'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, heartbeat : 3, useDict : true, useProtobuf : true }); }); app.configure('production|development', 'gate', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, useProtobuf : true }); }); var chatRouter = function(session, msg, app, cb) { var chatServers = app.getServersByType('chat'); if(!chatServers || chatServers.length === 0) { cb(new Error('can not find chat servers.')); return; } console.log('session uid ' + session.get('uid')); var index = Math.abs(crc.crc32(session.get('uid'))) % chatServers.length; var res = chatServers[index]; cb(null, res.id); }; // app configure app.configure('production|development', function() { // route configures app.route('chat', chatRouter); // filter configures // app.filter(pomelo.timeout()); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); });
其实代码也很简单,就是获取一下session里面保存的uid,然后计算一个crc值,再选择一个后端chat服务。
session里面的uid是在前端服务里面设置的,如:
Handler.prototype.entry = function(msg, session, next) { var self = this; if(!!msg.chat) {//给所有用户广播 session.set('uid', msg.uid); console.log('chat session uid ' + session.get('uid')); self.app.rpc.chat.chatRemote.chat(session,"c1", msg.uid + ' says: ' + msg.content, null); } else {//处理登录 var sessionService = self.app.get('sessionService'); var s = sessionService.getByUid(msg.uid); if(!!s) {//已经登录过了,不需要做任何事情 next(null, {code: 200, msg: msg.uid +' exists '}); } else { //将当前的session绑定一个ID session.bind(msg.uid); session.set('uid', msg.uid); console.log('login session uid ' + session.get('uid')); //将新登录的用户加入到channel里面。这里使用一个固定的channel: c1. //并且将channel里面所有用户回传。 self.app.rpc.chat.chatRemote.add(session, msg.uid, self.app.get('serverId'), "c1", true, function(users){ console.log('users' + users); next(null, {code: 2, users:users});//把channel里面的所有其他用户告诉给新登录的用户 }); } } };
这样后端服务也扩展好了。
总体来说还是非常简单的,需要做的也就是:
1. 配置servers.json,扩充前端服务和后端服务,同时增加一个gate,用来选择前端服务。
2. 配置adminServer.json
3. 在app.js里面增加前端配置和后端路由。
现在整个系统看起来像:
注意,我在测试的时候,在pushMessage时总失败,老是提示什么fail to connect to remote server: connector-server-2什么的。检查了好久,终于发现是servers.json里面增加connector-server-2的时候忘了写port,只写了clientPort,这样客户端还是可以连接connector-server-2的,但是后端服务chat在pushMessage的时候总出错。后来加上port后就成功了,可能这个port还是需要设置的,这样后端服务在进行rpc通信的时候才能找到前端服务,然后将message发给客户端。
奶奶的,一个大坑,有空再读读pomelo源代码。