skynet cluster with golang rpc
github.com/changlongH/srpc
支持golang和skynet cluster通信。无侵入设计无需修改任何skyne代码,简单的api设计上手即用。
Golang无状态微服务(例如登陆大厅),有丰富的生态和框架可以选择。大量的第三方库。Skynet有状态的服务,使用lua无需编译可以高效业务开发和热更。skynet高的性能沙盒隔离。
- 支持
payload codec配置可选json/msgpack(建议使用msgpack),支持自定义
注意
lua t = {}不区分map/array导致转Go时候类型不匹配。建议 encode_empty_table_as_null
msgpack仓库中的源码已经修改,cjson可以参考cloudwu/cjson 中encode_table_as_array实现修改。
skynet提供libsrpc.lua参考引入即可使用golang支持cluster节点动态变更和自动重连golang支持client级别和call级别的payload codecgolang支持SkynetContext上下文传递更方便做链路追踪和分析golang和skynet都支持profile统计消耗
服务端分发skynet请求:
-
server.Open("127.0.0.1:2531")开启集群端口监听 -
server.Shutdown(eventLoop netpoll.EventLoop, timeout time.Duration)关闭集群监听 -
server.Register(rcvr any, name string, opts ...Option) error注册一个服务server.WithPayloadCodec(&payloadcodec.MsgPack{})指定payload打包方式默认为msgpackserver.WithAccessLog(handler)指定访问日志处理回调,如果传入nil 则使用默认输出日志。不调用则不输出
-
server.GetRegisterMethods(name string) ([]string, error)获取成功注册的方法,可用于开发调试。 -
server.SetRecoveryHandler(handle func(string, any))服务器消息panic 回调 -
更多用法参考 server_test
客户端请求skynet服务:
-
cluster.Register(node string, address string, opts ...client.Option)注册一个远程skynet节点 -
cluster.Remove(node string)移除一个节点 -
cluster.Query(node string) *client.Client查询一个已注册节点 -
cluster.ReloadCluster(nodes map[string]string, opts ...client.Option)批量注册或者更新节点(如何没有变化不会产生影响) -
sprc.Call(node string, addr any, cmd string, args any, reply any) error通过node和addr支持一个简单的rpc请求,返回error表示调用结果 -
srpc.Send(node string, addr any, cmd string, args any) error发送消息,error表示是否失败 -
sprc.Invoke(caller *client.Caller) error支持构建复杂的调用。WithTimeout/WithPayloadCodec等 -
更多用法参考 client_test
skynet_example 提供 libsrpc 一个很简单的封装参考(100行代码)
sprc.set_default_codec(name)设置payload 打包方式,默认msgpack
作为客户端请求Golang服务:
srpc.send(node, sname, cmd, req)发送消息到golang 不阻塞srpc.call(node, sname, cmd, req)阻塞等待,两个返回值:ok表示调用是否成功。ret 表示返回内容
作为服务端分发Golang消息:
srpc.router(svc, cmd, callback)为svr注册一个go cluster消息路由
local db = {}
-- 注册一个PING方法到db
srpc.router(db, "PINGX", function(msg) return msg end)
-- 可以参考example用例做一层简单的封装
db:router("PINGX", function(msg) return msg end)
-- 不影响原生skynet dispatch
function db.PING(msg)
return msg
end
-- Golang sprc.Call 可以通过withPayloadCodec("text")调用
function db.TEXT(text)
skynet.error(text)
return text
endskynet cluster一次rpc请求需要携带的参数为:
addr uint32/string目标地址Id或者namesession uint32分包合并标记或者区分请求和推送cmd string调用的方法payload string携带的参数
我们约定payload为一个请求参数,不支持可变参数,并且payload必须被encode为字符串。这样能简化实现保证稳定,
如果支持可变类型展开,实现golang解析并不复杂。但是支持可变参数需要大量的反射和条件判断会让整体实现变得复杂,没有统一写法的约束也更容易出BUG.
- 数据编码
lua 支持数据
number string boolean nil table等基础类型。table转成golang struct可通过lua tag实现编码和解码。
我们可以选择更为通用的json/msgpack作为codec,经过验证更可靠。
- rpc调用方式
通常rpc会通过代码生成的方式和反射两种方案
这里使用反射更合适,为了提高效率我们约定只有一个请求参数和一个返回参数。同时约定Go函数原型的第一个参数必须为SkynetContext
支持无输入参数和无返回值。返回值error作为可选。必须作为最后一个返回参数
// 标准用法
func (s *Service) Set(*SkynetContext, args) *reply, error
// 同时兼容以下 缺省参数原型
func (s *Service) Set(*SkynetContext) *reply, error
func (s *Service) Set(*SkynetContext) *reply
func (s *Service) Set(*SkynetContext) error
func (s *Service) Set(*SkynetContext, args) *reply
func (s *Service) Set(*SkynetContext, args) error
func (s *Service) Set(*SkynetContext)skynet 请求参数同时约束为一个请求值和一个返回值,使用codec序列化成字符串,codec可以自定义(默认提供json/msgpack)
local req = {k =1, v =1}
local ok, res = pcall(cluster.call, node, sname, "Set", codec.encode(req))
if ok then
res = codec.decode(res)
end如果使用json作为codec 则需要注意lua空table问题,建议encode 为
null, 并且json序列化必须为lua table和golang struct/map,不支持int/string/bool等类型
默认skynet和golang使用msgpack作为codec,支持原子类型和复杂的类型。