@yangfch3
2018-06-14T01:26:31.000000Z
字数 8662
阅读 4357
Pomelo
Pomelo 生态 
pomelo-synccomponent 构建) pomelo-jsclient-websocketpomelo-jsclient-socket.iopomelo-protocolpomelo-protobufsocket.io-client
chatofpomelochatofpomelo-websocket - 聊天lordofpomelo - MMO 中大型游戏treasures - H5 游戏Node.js 版本推荐:6.x
pomelo init 后的 shared 目录:可以前后端共用的工具或者算法代码
TODO web-server 存在的一个小问题:
node app.js后,localhost:3001能访问,但127.0.0.1:3001无法访问
pomelo --help pomelo xxx --help 查看 pomelo 及其子命令的帮助信息
使用 pomelo stop,规避 pomelo kill
port 与 clientPortport 是面向“后”的端口(即用于 rpc 调用的端口)clientPort 是面向“前(客户端)”的端口(即面向 client 的端口)gate 服务器和 connector 服务器又都被称作前端服务器
gate 服务器只有 clientPort
connector 服务器有 clientPort 和 port
后端服务器只有
port
connector 做为前端服务器
rpc 调用后端服务器,并获得 rpc 调用的结果前端服务器的职责:
session 信息后端服务器(backend)的职责:
remote)以及处理前端请求(handler)的逻辑 本质上前端服务器route到后端的handler也是 RPC
remote 与 handlergate 服务器只有 handler
connector 服务器也只有 handler
后端服务器可以有 handler 和 remote(且此两部分是同一个进程中),其中 handler 用于客户端对应路由消息的处理(由前端服务器 forward),remote 用于前端服务器进行 RPC 调用
不管是前端服务器还是后端服务器,handler 必须调用 next 来传递 response 的消息(否则会报 request 超时未回复的错误)
服务器端路由是 前端服务器用以寻找客户端消息对应处理方式的框架设计,路由形式:<server>.<handlerFile>.<method>
客户端路由则可以 简单地理解为 pomelo client 订阅的事件,常见形式 onXXX
client ---request route-->
<server>.<handlerFile>.<method>
client <---response route---onXXX
同一个服务器下:
remote 与 handler 处于同进程,可互相调用(但一般不会发生调用)不同后端服务器之间:
remote 远程调用 
后端服务器的 handler 与前端服务器的 handler 参数一致:(msg, session, next);
后端服务器 remote 的参数则一般自己定制,推荐:(params, cb),RPC 调用时 app.rpc...<remoteMethod>(session, params, cb)
后端服务器的 handler 与 remote 内可以 RPC 调用其他后端服务器的 remote
路由器:根据 route 的 session/routeParam 决定指定处理该 route 的服务器
在 Pomelo 下,router 通常就是一个 dispatch 函数
exp.chat 即为路由器函数


路由器函数有四个参数位供 pomelo 传入:
1· routeParam - 通常(但可以不)是 session
msgapp - ctxcb路由器使用 app.route 配置
sys namespace
session 信息(BackendSession 的 push, pushAll 操作)channel 推送消息时对前端服务器发起的 rpc 调用<backendServer>.<handlerFile>.<method>user namespace:pomelo.app.rpc.xxx.xxxRemote...
前后端服务器之间的通信基本都是基于 RPC 调用,同时后端服务器之间也可以使用 RPC 进行通信
RPC 调用(例如:app.rpc...<remoteMethod>(session, params, cb))的第一个参数是用于 Pomelo 进行路由使用的(一般都是使用前端 session),会传递给对应后端服务器使用 app.route 注册的路由函数(或 Pomelo 默认的路由策略函数),最后一个参数是 RPC 调用结果的回调函数,调用的错误或是结果全部通过该回调函数返回,且这个参数不能省略,中间有多少个参数一般没有限制(但推荐使用 params 对象的形式来批量传参)
proxy 与 remote 两个组件分别用于生成 RPC 通信的 client 与 server
对于后端服务器,remote 使用 servers.json 配置中的 port 创建 RPC 服务器
一个服务器(不论是 connector 还是后端服务器)都能与所有后端服务器进行 RPC 通信,所以 proxy 能够 懒创建 许多个 RPC Client,保持与 RPC Server 的长连接
三个 session,两个 sessionService
session - 底层、隐藏的,开发者不需要关注FrontendSessionSessionService(前端服务器中 app.get('sessionService'))BackendSessionBackendSessionService(后端服务器中 app.get('backendSessionService'))获取 session 的途径
handler 的 session 参数:FrontendSessionhandler 的 session 参数:BackendSessionsessionService 的 get 与 getByUidremote 中 BackendSessionService 的 get 与 getByUid:BackendSessionFrontendSession 与 BackendSession 的属性与方法基本一致,但由于真实底层的 session 只在前端服务器,所以 BackendSession 的方法调用(push, pushAll 等) 都是 RPC 调用(所以会有回调)
session 的 __socket__ 是底层原生 socket 的引用
服务器端下发消息的几种方式
handler 的 next - 做为 pomelo.request 的 responsechannel.pushMessage - channel 级 pushchannelSessionService.pushMessageByUids - channel 内选择性地 pushchannelSessionService.broadcast - 服务器级别的广播几种消息类型的消息头

消息体

消息头的 flag 字段

route 表示是否 route 压缩
route 压缩时的 flag 与 route 字段

在 pomelo 中,module 特指服务器监控管理模块,会有这四个钩子函数:masterHandler monitorHandler clientHandler start
monitorHandler(agent, msg, cb) - monitor 收到 master 的请求或者通知时的回调masterHandler(agent, msg) - master 收到 monitor 的请求或者通知时回调clientHandler(agent, msg, cb) - master 收到 client 的请求或通知时回调start(cb) - admin module加载完成后,用来执行一些初始化监控时调用 
pomelo 框架是由一些松散耦合的 component 组成的,每个 component 完成一些功能。整个 pomelo 框架可以看作是一个 component 容器,完成 component 的加载、增强 以及生命周期管理(start, afterstart, stop)。
component 的生命周期函数能拿到 pomelo 的引用(
app参数),所以我们可以在 component 对 pomelo 做任何处理和操作;所以 pomelo 的 plugin 也会有编写 component 来处理和增强 pomelo
对于已 app.load 的 component,pomelo 总是先调用其加载的每一个 component 提供的 start 函数(但 pomelo 内置的 component 排在前面),当各个组件 start 全部调用完后,才会去调用其加载的每一个 component 的 afterStart 方法,总是按顺序调用的。
一个 plugin 是由多个 component 以及一些事件响应处理器组成。
master、monitor 相关模块与组件以及 consoleService错综复杂的关系理清

admin-client 是指像 pomelo-cli 这样的服务器群管理客户端工具
master moniter Server

master 和 monitor 分别对应用于管理与监控的 Master 服务器与 Moniter socket 客户端
app.configure(<env>[, <serverType>], cb)app.set(<mountProp>, <value>) enableForwardLog - BooleanrpcDebugLog - Boolean(同 app.enable('rpcDebugLog'))app.route(<serverType>, <routerFn>)app.before(<Filter>) app.after(<Filter>) app.filter(<Filter>)app.rpcBefore(<Filter>) app.rpcAfter(<Filter>) app.rpcFilter(<Filter>) 用于注册 RPC 调用的 filter 链
app.enable(<feature>) app.disable(<feature>) app.set(<feature>, true) app.enabled/disabled(<feature>) 来检测特性是否开启app.registerAdmin(<adminModule>, <opts>)app.load(<component>, <opts>)app.loadConfig(<configName>, <configPath>)
等价于:
app.set(<configName>, require(<configPath>))
app.use(<plugin>, opts)
connectorConfig
connector - Connector hybridconnector handshake - Functionheartbeat - NumberdistinctHost - Booleansioconnector heartbeats - BooleancloseTimeout - NumberheartbeatTimeout - NumberheartbeatInterval - Numbermqttconnector publishRoutesubscribeRouteudpconnector disconnectOnTimeout - Booleanheartbeat - Numbertimeout - NumberudpType - String--- 上方每个 connector 下是该 connector 的特异可配置参数
--- 以下是几个 connector 的共同可配置参数
encode - Functiondecode - FunctionuseCrypto - BooleanuseHostFilter - BooleanblacklistFun - FunctionuseDict - BooleanuseProtobuf - Booleanssl - ObjectdisconnectOnTimeout - BooleansessionConfig singleSession - Boolean 是否允许一个用户同时绑定到多个sessionproxyConfig bufferMsg - Boolean(旧版本使用 cacheMsg)interval - NumbermailBoxFactory - Function pomelo-rpc 支持配置(但 pomelo 其实是没用到这些特性的)failMode - String 'failfast' - 快速失败'failover' - 切换服务器尝试'failsafe' - 自定制安全策略retryTimes - Number 使用安全策略时的重试次数retryConnectTime - Number 使用安全策略时的重连间隔时间routerType - String replicas - Number (ch)虚拟节点数量algorithm - String (ch)hash 算法hashFieldIndex - Number (ch)根据 rpc 参数列表中的具体(index)参数进行 hashremoteConfig bufferMsg - Boolean(旧版本使用 cacheMsg)interval - NumberacceptorFactory - FunctionserverConfig reloadHandlersreloadRemotespushSchedulerConfig scheduler - Function/Function Array 具体调度策略flushInterval - Number scheduler 为缓冲模式时可配置selectordictionaryConfig dict - String 字典路径channelConfig broadcastFilter - Function 前端服务器上消息广播给每个 session 时的前处理app.enable('systemMonitor') 开启 pomelo 监控(信息可通过客户端 - cli/web 获取)
app.enable('rpcDebugLog') 开启 RPC 日志
对 filter 与 handler 的 next 处理流程发生的异常集中处理
app.set('errorHandler', function(err, msg, resp, session, next) {// 记录、处理错误next(null, resp);});
pomelo.init(params)
params:
hostportencodedecodeencrypthandshakeCallbackmaxReconnectAttemptsreconnect未预料的程序错误:
process.on('uncaughtException', function (err) {console.error('进程级异常捕获: ' + err.stack);});
handler 中 next 传递的错误
// 统一处理 handler 流程中 next(err) 的错误app.set('errorHandler', function (err, msg, resp, session, next) {// 此处进行错误记录、处理next(null, resp);});
handler 中对 remote RPC 调用中传递给回调的错误
next(rpcError) 化归成 2 中对 handler next 错误的处理pomelo-logger 基于 log4js 0.6.x
config/log4js.json 的特异配置:
replaceConsole:决定是否调用 log4js.replaceConsole 
lineDebugrawMessagereloadSecs - 是否重新载入配置文件 
config/log4js.json filename 的模板写法:${opt:<prop>}/path/to/...,pomelo 会在 app.init 流程中进行模板替换
pomelo-logger 也为我们提供了一种 log4js 在集群下的分进程日志写入思路:使用自己添加的 filename 模板特性(相当于每个进程加载的 log4js 都是不同的),达到不同的进程日志文件名区分的效果
4 中的实现在新版的 log4js 已经得到原生支持:multiFile
app.loadConfigBaseApp - 第二个路径参数可以简写成基于 app.js 的路径,第三个布尔参数决定是否在配置变更时重载
app.transaction(name, conditions, handlers, retry) - 模拟事务
conditions可以是对象也可以是数组,只要组成元素是函数即可,并且conditions内的元素(函数)都要能接收一个回调函数,condition运行抛出的错误会传给回调函数的第一个参数 -- async 文档。
handlers只在conditions遍历检验完成且没有抛出错误时才会运行,如果handlers遍历运行抛出了错误,则会按照retry设置的次数重试。
app.addCrons - 定时任务,用法见链接
app.globalFilter
消息处理链:globalBeforeFilter -> 前端服务器 -> beforeFilter -> (RPC Before Filter -> RPC 后端服务器 -> RPC After Filter) -> afterFilter -> globalAfterFilter
app.beforeStopHook - 服务器停止前执行的操作
Pomelo 的请求 body 与响应 body 只要满足能被 JSON.stringify 即可以被发送
Pomelo 的 handler 中如果多次调用 next() 则会导致 后面传递的消息没有 msg.id 等元信息,因为这些信息在第一次调用 next 时已经被消耗了
官方文档的错误:当 masterha.json 被正确地放在 ./config 路径下时,pomelo masterha 即可(无需像文档那样添加配置路径)
pomelo start 与 pomelo masterha 需要保持一致才能开启高可用

pomelo 最新版中对非 development 环境下的非 master 进程做了 detached 设置,直接 ctrl + c 无法关闭进程,正确的方法是在 game-server 目录下执行 pomelo stop
pomelo-protocol 存在的问题:
Node.js 环境下会走这个分支,从而使用
new Buffer来编码字符串,而浏览器不支持Buffer,所以解码时会走pomelo-protocol自创的解码方案这个分支,而new Buffer()和Protocol.strdecode在出现\uffff+是有问题的
new Buffer('𠮷'); // <Buffer f0 a0 ae b7>
Protocol.strencode('𠮷'); // Uint8Array(6) [237, 161, 130, 237, 190, 183]
解决思路:new Buffer() 与 buffer.toString()/TextDecoder.prototype.decode 是可逆的,Protocol.strencode() 与 Protocol.strdecode() 是可逆的;所以,要么让客户端(通过 polyfill 的方式)支持 Buffer 和 buffer.toString()/TextDecoder.prototype.decode,要么让服务器端所有的编码使用 Protocol.strencode()(现在消息体的编码使用 Buffer)