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

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


服务器与路由

port 与 clientPort

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

前端服务器

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

  2. gate 服务器只有 clientPort
    connector 服务器有 clientPort 和 port

    后端服务器只有 port

  3. connector 做为前端服务器

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

服务器职责

前端服务器的职责:

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

remote 与 handler

  1. gate 服务器只有 handler

  2. connector 服务器也只有 handler

  3. 后端服务器可以有 handler 和 remote(且此两部分是同一个进程中),其中 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. 同一个服务器下:

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

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

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


路由器(router)

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

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

    image_1c7b85q3vi2t1a6a1llk19u71f4i2j.png-10kB

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

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


RPC 通信

  1. sys namespace

    • 后端服务器向前端服务器请求 session 信息(BackendSession 的 push, pushAll 操作)
    • 后端服务器通过 channel 推送消息时对前端服务器发起的 rpc 调用
    • 前端服务器将用户请求路由给后端服务器时也是 sys rpc 调用(见路由一节)—— <backendServer>.<handlerFile>.<method>
  2. user namespace:pomelo.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. proxy 与 remote 两个组件分别用于生成 RPC 通信的 client 与 server

  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 的途径

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

  4. session 的 __socket__ 是底层原生 socket 的引用


request, response, notify, push

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

    • handler 的 next - 做为 pomelo.request 的 response
    • channel.pushMessage - channel 级 push
    • 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. master 和 monitor 分别对应用于管理与监控的 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 配置

对 filter 与 handler 的 next 处理流程发生的异常集中处理

  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. handler 中 next 传递的错误

    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. Pomelo 的 handler 中如果多次调用 next() 则会导致 后面传递的消息没有 msg.id 等元信息,因为这些信息在第一次调用 next 时已经被消耗了

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

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

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