[关闭]
@yangfch3 2018-06-14T01:26:31.000000Z 字数 8662 阅读 3550

Pomelo 文档笔记

Pomelo


介绍

  1. Pomelo 生态

安装与开箱使用

  1. Node.js 版本推荐:6.x

  2. pomelo init 后的 shared 目录:可以前后端共用的工具或者算法代码

  3. TODO web-server 存在的一个小问题:

    node app.js 后,localhost:3001 能访问,但 127.0.0.1:3001 无法访问

  4. pomelo --help pomelo xxx --help 查看 pomelo 及其子命令的帮助信息

  5. 使用 pomelo stop,规避 pomelo kill


服务器与路由

portclientPort

  1. port 是面向“后”的端口(即用于 rpc 调用的端口)
  2. clientPort 是面向“前(客户端)”的端口(即面向 client 的端口)

前端服务器

  1. gate 服务器和 connector 服务器又都被称作前端服务器

  2. gate 服务器只有 clientPort
    connector 服务器有 clientPortport

    后端服务器只有 port

  3. connector 做为前端服务器

    1. 路由客户端消息到后端服务器
    2. rpc 调用后端服务器,并获得 rpc 调用的结果
    3. 返还消息给客户端

服务器职责

前端服务器的职责:

后端服务器(backend)的职责:

remotehandler

  1. gate 服务器只有 handler

  2. connector 服务器也只有 handler

  3. 后端服务器可以有 handlerremote(且此两部分是同一个进程中),其中 handler 用于客户端对应路由消息的处理(由前端服务器 forward),remote 用于前端服务器进行 RPC 调用

  4. 不管是前端服务器还是后端服务器,handler 必须调用 next 来传递 response 的消息(否则会报 request 超时未回复的错误)


路由(route)

  1. 服务器端路由是 前端服务器用以寻找客户端消息对应处理方式的框架设计,路由形式:<server>.<handlerFile>.<method>

  2. 客户端路由则可以 简单地理解为 pomelo client 订阅的事件,常见形式 onXXX

client ---request route--> <server>.<handlerFile>.<method>
client <---response route--- onXXX

后端服务器 handler 的使用情景

后端服务器的 handler 与 remote

  1. 同一个服务器下:

    • remotehandler 处于同进程,可互相调用(但一般不会发生调用)
  2. 不同后端服务器之间:

    • 只能进行 remote 远程调用
      image_1c7b6johk1l2s1k1u1rrt15bd12p7m.png-42.8kB
  3. 后端服务器的 handler 与前端服务器的 handler 参数一致:(msg, session, next)
    后端服务器 remote 的参数则一般自己定制,推荐:(params, cb),RPC 调用时 app.rpc...<remoteMethod>(session, params, cb)

  4. 后端服务器的 handlerremote 内可以 RPC 调用其他后端服务器的 remote


路由器(router)

  1. 路由器:根据 routesession/routeParam 决定指定处理该 route 的服务器

  2. 在 Pomelo 下,router 通常就是一个 dispatch 函数
    image_1c7b83fg5g02da719kc1pmfeph1p.png-6kB
    exp.chat 即为路由器函数
    image_1c7b85ckvd2c1bu7hij6g61pfd26.png-21.4kB

    image_1c7b85q3vi2t1a6a1llk19u71f4i2j.png-10kB

  3. 路由器函数有四个参数位供 pomelo 传入:
    routeParam - 通常(但可以不)是 session

    1. msg
    2. app - ctx
    3. cb
  4. 路由器使用 app.route 配置


RPC 通信

  1. sys namespace

    • 后端服务器向前端服务器请求 session 信息(BackendSessionpush, pushAll 操作)
    • 后端服务器通过 channel 推送消息时对前端服务器发起的 rpc 调用
    • 前端服务器将用户请求路由给后端服务器时也是 sys rpc 调用(见路由一节)—— <backendServer>.<handlerFile>.<method>
  2. user namespacepomelo.app.rpc.xxx.xxxRemote...

  3. 前后端服务器之间的通信基本都是基于 RPC 调用,同时后端服务器之间也可以使用 RPC 进行通信

  4. RPC 调用(例如:app.rpc...<remoteMethod>(session, params, cb))的第一个参数是用于 Pomelo 进行路由使用的(一般都是使用前端 session),会传递给对应后端服务器使用 app.route 注册的路由函数(或 Pomelo 默认的路由策略函数),最后一个参数是 RPC 调用结果的回调函数,调用的错误或是结果全部通过该回调函数返回,且这个参数不能省略,中间有多少个参数一般没有限制(但推荐使用 params 对象的形式来批量传参)


RPC 通信的 proxy 与 remote

  1. proxyremote 两个组件分别用于生成 RPC 通信的 clientserver

  2. 对于后端服务器,remote 使用 servers.json 配置中的 port 创建 RPC 服务器

  3. 一个服务器(不论是 connector 还是后端服务器)都能与所有后端服务器进行 RPC 通信,所以 proxy 能够 懒创建 许多个 RPC Client,保持与 RPC Server 的长连接


Session

  1. 三个 session,两个 sessionService

    • session - 底层、隐藏的,开发者不需要关注
    • ---
    • FrontendSession
    • SessionService(前端服务器中 app.get('sessionService')
    • ---
    • BackendSession
    • BackendSessionService(后端服务器中 app.get('backendSessionService')
  2. 获取 session 的途径

    • 前端 handlersession 参数:FrontendSession
    • 后端 handlersession 参数:BackendSession
    • 前端 sessionServicegetgetByUid
    • 后端 remoteBackendSessionServicegetgetByUidBackendSession
  3. FrontendSessionBackendSession 的属性与方法基本一致,但由于真实底层的 session 只在前端服务器,所以 BackendSession 的方法调用(push, pushAll 等) 都是 RPC 调用(所以会有回调)

  4. session__socket__ 是底层原生 socket 的引用


request, response, notify, push

  1. 服务器端下发消息的几种方式

    • handlernext - 做为 pomelo.requestresponse
    • channel.pushMessage - channelpush
    • channelSessionService.pushMessageByUids - channel 内选择性地 push
    • channelSessionService.broadcast - 服务器级别的广播
  2. 几种消息类型的消息头
    image_1c8sr3guha9g44n0d6tq1npup.png-30.5kB

  3. 消息体
    image_1c8sred8o5v3oasik8k9p147u2g.png-5.5kB
    image_1c8srfb4l70cv6lbtbnui19ap2t.png-27.3kB

  4. 消息头的 flag 字段
    image_1c8srcqb713n913r9v3e1u95154e23.png-9.5kB

    route 表示是否 route 压缩

  5. route 压缩时的 flag 与 route 字段
    image_1c8sr8s3rfkm1qmp15n2a161jem16.png-21.5kB


(admin)module, component 与 plugin

  1. 在 pomelo 中,module 特指服务器监控管理模块,会有这四个钩子函数:masterHandler monitorHandler clientHandler start

    1. monitorHandler(agent, msg, cb) - monitor 收到 master 的请求或者通知时的回调
    2. masterHandler(agent, msg) - master 收到 monitor 的请求或者通知时回调
    3. clientHandler(agent, msg, cb) - master 收到 client 的请求或通知时回调
    4. start(cb) - admin module加载完成后,用来执行一些初始化监控时调用
      image_1c7u0uepfcap1d4dc186aa9oc1m.png-115.4kB
  2. pomelo 框架是由一些松散耦合的 component 组成的,每个 component 完成一些功能。整个 pomelo 框架可以看作是一个 component 容器,完成 component 的加载、增强 以及生命周期管理(start, afterstart, stop)。

    component 的生命周期函数能拿到 pomelo 的引用(app 参数),所以我们可以在 component 对 pomelo 做任何处理和操作;所以 pomelo 的 plugin 也会有编写 component 来处理和增强 pomelo

  3. 对于已 app.load 的 component,pomelo 总是先调用其加载的每一个 component 提供的 start 函数(但 pomelo 内置的 component 排在前面),当各个组件 start 全部调用完后,才会去调用其加载的每一个 component 的 afterStart 方法,总是按顺序调用的

  4. 一个 plugin 是由多个 component 以及一些事件响应处理器组成。

  5. master、monitor 相关模块与组件以及 consoleService错综复杂的关系理清
    QQ图片20180420224024.jpg-1068.7kB


admin-client, monitor 与 master

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

  2. master moniter Server
    image_1c7tseq9d1gdu184r16f7trhon519.png-108.1kB

  3. mastermonitor 分别对应用于管理与监控的 Master 服务器与 Moniter socket 客户端


配置

  1. 可以配置哪些东西?

  2. 组件功能及其配置

  3. 组件概述

配置方法清单

app.set 的框架预留组件/服务配置

  1. connectorConfig

    • connector - Connector
      • hybridconnector
        • handshake - Function
        • heartbeat - Number
        • distinctHost - Boolean
      • sioconnector
        • heartbeats - Boolean
        • closeTimeout - Number
        • heartbeatTimeout - Number
        • heartbeatInterval - Number
      • mqttconnector
        • publishRoute
        • subscribeRoute
      • udpconnector
        • disconnectOnTimeout - Boolean
        • heartbeat - Number
        • timeout - Number
        • udpType - String
      • 自建 connector

    --- 上方每个 connector 下是该 connector 的特异可配置参数
    --- 以下是几个 connector 的共同可配置参数

    • encode - Function
    • decode - Function
    • useCrypto - Boolean
    • useHostFilter - Boolean
    • blacklistFun - Function
    • useDict - Boolean
    • useProtobuf - Boolean
    • ssl - Object
    • disconnectOnTimeout - Boolean
  2. sessionConfig
    • singleSession - Boolean 是否允许一个用户同时绑定到多个session
  3. proxyConfig
    • bufferMsg - Boolean(旧版本使用 cacheMsg
    • interval - Number
    • mailBoxFactory - Function
      --- pomelo-rpc 支持配置(但 pomelo 其实是没用到这些特性的)
    • failMode - String
      • 'failfast' - 快速失败
      • 'failover' - 切换服务器尝试
      • 'failsafe' - 自定制安全策略
    • retryTimes - Number 使用安全策略时的重试次数
    • retryConnectTime - Number 使用安全策略时的重连间隔时间
    • routerType - String
      • rr(ROUNDROBIN)
      • wrr(WEIGHT_ROUNDROBIN)
      • la(LEAST_ACTIVE)
      • ch(CONSISTENT_HASH)
    • replicas - Number (ch)虚拟节点数量
    • algorithm - String (ch)hash 算法
    • hashFieldIndex - Number (ch)根据 rpc 参数列表中的具体(index)参数进行 hash
  4. remoteConfig
    • bufferMsg - Boolean(旧版本使用 cacheMsg
    • interval - Number
    • acceptorFactory - Function
  5. serverConfig
    • reloadHandlers
    • reloadRemotes
  6. pushSchedulerConfig
    • scheduler - Function/Function Array 具体调度策略
    • flushInterval - Number scheduler 为缓冲模式时可配置
    • selector
  7. dictionaryConfig
    • dict - String 字典路径
  8. channelConfig
    • broadcastFilter - Function 前端服务器上消息广播给每个 session 时的前处理

app.enable 配置项

  1. app.enable('systemMonitor') 开启 pomelo 监控(信息可通过客户端 - cli/web 获取)

  2. app.enable('rpcDebugLog') 开启 RPC 日志

error handler 配置

filterhandlernext 处理流程发生的异常集中处理

  1. app.set('errorHandler', function(err, msg, resp, session, next) {
  2. // 记录、处理错误
  3. next(null, resp);
  4. });

客户端配置

  1. pomelo.init(params)

  2. params:

    • host
    • port
    • encode
    • decode
    • encrypt
    • handshakeCallback
    • maxReconnectAttempts
    • reconnect

Pomelo 错误处理的正确姿势

  1. 未预料的程序错误:

    1. process.on('uncaughtException', function (err) {
    2. console.error('进程级异常捕获: ' + err.stack);
    3. });
  2. handlernext 传递的错误

    1. // 统一处理 handler 流程中 next(err) 的错误
    2. app.set('errorHandler', function (err, msg, resp, session, next) {
    3. // 此处进行错误记录、处理
    4. next(null, resp);
    5. });
  3. handler 中对 remote RPC 调用中传递给回调的错误

    • 可以直接在回调中处理(但不推荐)
    • 也可以 next(rpcError) 化归成 2 中对 handler next 错误的处理

日志

  1. pomelo-logger 基于 log4js 0.6.x

  2. config/log4js.json 的特异配置:

    • replaceConsole:决定是否调用 log4js.replaceConsole
      image_1c9np053mgkb1gkrmam73u1iln9.png-1.8kB
    • lineDebug
    • rawMessage
    • reloadSecs - 是否重新载入配置文件
      image_1c9np2hkp17eg1u601l2n14huirt16.png-14.5kB
  3. config/log4js.json filename 的模板写法:${opt:<prop>}/path/to/...pomelo 会在 app.init 流程中进行模板替换

  4. pomelo-logger 也为我们提供了一种 log4js 在集群下的分进程日志写入思路:使用自己添加的 filename 模板特性(相当于每个进程加载的 log4js 都是不同的),达到不同的进程日志文件名区分的效果

  5. 4 中的实现在新版的 log4js 已经得到原生支持:multiFile


pomelo 隐藏 API

  1. app.loadConfigBaseApp - 第二个路径参数可以简写成基于 app.js 的路径,第三个布尔参数决定是否在配置变更时重载

  2. app.transaction(name, conditions, handlers, retry) - 模拟事务

    conditions 可以是对象也可以是数组,只要组成元素是函数即可,并且 conditions 内的元素(函数)都要能接收一个回调函数,condition 运行抛出的错误会传给回调函数的第一个参数 -- async 文档

    handlers 只在 conditions 遍历检验完成且没有抛出错误时才会运行,如果 handlers 遍历运行抛出了错误,则会按照 retry 设置的次数重试。

  3. app.addCrons - 定时任务,用法见链接

  4. app.globalFilter

    消息处理链:globalBeforeFilter -> 前端服务器 -> beforeFilter -> (RPC Before Filter -> RPC 后端服务器 -> RPC After Filter) -> afterFilter -> globalAfterFilter

  5. app.beforeStopHook - 服务器停止前执行的操作


易忘易错备忘录

  1. Pomelo 的请求 body 与响应 body 只要满足能被 JSON.stringify 即可以被发送

  2. Pomelohandler 中如果多次调用 next() 则会导致 后面传递的消息没有 msg.id 等元信息,因为这些信息在第一次调用 next 时已经被消耗了

  3. 官方文档的错误:当 masterha.json 被正确地放在 ./config 路径下时,pomelo masterha 即可(无需像文档那样添加配置路径)

  4. pomelo startpomelo masterha 需要保持一致才能开启高可用
    image_1cbed4d951frg5el6kv1s2jgra9.png-2.4kB
    image_1cbed8bf813b21ksa3kv1v441opa16.png-3.1kB
    image_1cbeda05216sl1upl1o001isarnb1j.png-4.6kB

  5. pomelo 最新版中对非 development 环境下的非 master 进程做了 detached 设置,直接 ctrl + c 无法关闭进程,正确的方法是在 game-server 目录下执行 pomelo stop

  6. 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 的方式)支持 Bufferbuffer.toString()/TextDecoder.prototype.decode,要么让服务器端所有的编码使用 Protocol.strencode()(现在消息体的编码使用 Buffer

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注