Skip to content
云风 edited this page Nov 30, 2020 · 17 revisions

snax 的动机是做一个方便 skynet 服务实现的简单框架,但似乎并没有达到简化 skynet 的目的,反而给使用者带来很多困扰。所以不建议再使用(推荐直接使用 skynet api 构建适合自己的框架),只为了兼容目的而保留。

The intention of snax was for the convenience of implementing a simple Skynet service, unfortunately, it doesn't seem to simplify Skynet, instead it causes a lot of troubles for users. So it's not recommended to use this module anymore (it's recommended to come up with your own framework with Skynet API), we keep it just for backward compatibility.

使用 snax 服务先要在 Config 中配置 snax 用于路径查找。每个 snax 服务都有一个用于启动服务的名字,推荐按 lua 的模块命名规则,但目前不推荐在服务名中包含"点" (在路径搜索上尚未支持 . 与 / 的替换)。在启动服务时会按查找路径搜索对应的文件。

To use a snax service, first set up a snax path in Config, every snax service has its own name to start a service, it's recommended to use lua naming rules, but it's not recommended to have '.' in your service name (it doesn't support switching between . and / in the patch searching).

snax 服务用 lua 编写,但并不是一个独立的 lua 程序。它里面包含了一组 lua 函数,会被 snax 框架分析加载。

snax service is written in lua, but it's not an independent lua module. It includes a set of lua functions and will be loaded by the snax framework.

test/pingserver.lua 就是一个简单的 snax 服务范例:

test/pingserver.lua is a simple snax service example:

local skynet = require "skynet"

local i = 0
local hello = "hello"

function response.ping(hello)
	skynet.sleep(100)
	return hello
end

function accept.hello()
	i = i + 1
	print (i, hello)
end

function response.error()
	error "throw an error"
end

function init( ... )
	print ("ping server start:", ...)
end

function exit(...)
	print ("ping server exit:", ...)
end

snax 服务的启动和退出 (The Start and Exit of Snax Service)

每个 snax 服务中都需要定义一个 init 函数,启动这个服务会调用这个函数,并把启动参数传给它。

Every snax service has its own init function and it will be called when a service started and running parameters will be passed into this function.

snax 服务还可以定义一个 exit 函数用于响应服务退出的事件,同样能接收一些参数。

snax service also has its own exit function for exit event of service and it can also accept parameters.

和标准的 skynet 服务不同,这些参数的类型不受限制,可以是 lua 的复杂数据类型。(而 skynet 服务受底层限制,只可以接受字符串参数)

Unlike standard Skynet service, there is no limit on the type of these parameters, it can be any complicated lua data type.

启动一个 snax 服务有三种方式:

There are 3 ways to start a snax service:

local snax = require "snax"
  1. snax.newservice(name, ...) :可以把一个服务启动多份。传入服务名和参数,它会返回一个对象,用于和这个启动的服务交互。如果多次调用 newservice ,即使名字相同,也会生成多份服务的实例,它们各自独立,由不同的对象区分。

snax.newservice(name, ...) : this function can start multiple instances of a given service. By passing a service name and other parameters, it will return an object, which can be used to interact with this running service. If you call multiple times of newservice function, even with the same name, it'll star multiple service instances and they are unique with different objects.

  1. snax.uniqueservice(name, ...) : 和上面 api 类似,但在一个节点上只会启动一份同名服务。如果你多次调用它,会返回相同的对象。 snax.uniqueservice(name, ...) : similar to the api above, but it will only start one instance with the same name on each node. If you call it multiple times with the same name it'll return the same instance.

  2. snax.globalservice(name, ...) : 和上面的 api 类似,但在整个 skynet 网络中(如果你启动了多个节点),只会有一个同名服务。 snax.globalservice(name, ...) : similar to the api above, but there will be only one instance with the same name in the Skynet network (if you start multiple nodes).

前一种方式可以看成是启动了一个匿名服务,启动后只能用地址(以及对服务地址的对象封装)与之通讯;后两种方式都可以看成是具名服务,之后可以用名字找到它。

The previous function can be treated as an anonymous service, it can only be accessed by address (and the encapsulation of the service address object); the latter two can be treated as a named service and can be found by its name.

后两种方式是对具名服务惰性初始化。如果你在代码中写了多处服务启动,第一次会生效,后续只是对前面启动的服务的查询。往往我们希望明确服务的启动流程(在启动脚本里就把它们启动好);尤其是全局(整个 skynet 网络可见)的服务,我们还希望明确它启动在哪个结点上(如果是惰性启动,你可能无法预知哪个节点先把这个服务启动起来的)。这时,可以使用下面两个 api :

The latter two functions are lazy initialization of named service. if you call service start multiple times, only the first one does the real work, the latter ones only query on existing service. Usually, we want to make sure of the starting of services in starting flow (start them in the starting script); especially those global services (which can be accessed on the Skynet network), we want to make sure which node it starts on (in the lazy start mode, you don't have a way to predict which node a service has started on). In this case, these two api can be used here:

  1. snax.queryservice(name) :查询当前节点的具名服务,返回一个服务对象。如果服务尚未启动,那么一直阻塞等待它启动完毕。

snax.queryservice(name) : query the named service of the current node and return a service object. If a service has not been started yet, it will be blocked until it's started.

  1. snax.queryglobal(name) :查询一个全局名字的服务,返回一个服务对象。如果服务尚未启动,那么一直阻塞等待它启动完毕。

snax.queryglobal(name) : query a global named service and return a service object. If a service has not been started yet, it will be blocked until it's started.

对于匿名服务,你无法在别处通过名字得到和它交互的对象。如果你有这个需求,可以把对象的 .handle 域通过消息发送给别人。 handle 是一个数字,即 snax 服务的 skynet 服务地址。

For anonymous service, you won't be able to get an interactive object by name. If you need this, you can pass .handle with messages to other callers. handle is a number, which is also the address of Skynet service.

这个数字的接收方可以通过 snax.bind(handle, typename) 把它转换成服务对象。这里第二个参数需要传入服务的启动名,以用来了解这个服务有哪些远程方法可以供调用。当然,你也可以直接把 .type 域和 .handle 一起发送过去,而不必在源代码上约定。

Use snax.bind(handle, typename) convert this number to a service object. The second parameter needs to be set as a service start name, so we know which remote methods can be called. Of course, you can pass both .type and .handle, so you don't need to agree on the code level.

如果你想让一个 snax 服务退出,调用 snax.kill(obj, ...) 即可。

snax.self() 用来获取自己这个服务对象,它等价于 snax.bind(skynet.self(), SERVICE_NAME)

snax.exit(...) 退出当前服务,它等价于 snax.kill(snax.self(), ...) 。

注:snax 的接口约定是通过分析对应的 lua 源文件完成的。所以无论是消息发送方还是消息接收方都必须可以读到其消息处理的源文件。不仅 snax.newservice 会加载对应的源文件生成新的服务;调用者本身也会在自身的 lua 虚拟机内加载对应的 lua 文件做一次分析;同样, snax.bind 也会按 typename 分析对应的源文件。

RPC 调用

snax 服务中可以定义一组函数用于响应其它服务提出的请求,并给出(或不给出)回应。一个非 snax 服务也可以调用 snax 服务的远程方法。

In snax service, a set of functions can be defined to handle the requests of other services with or without a response. A non-snax service can also call a snax RPC method.

要定义这类远程方法,可以通过定义 function response.foobar(...) 来声明一个远程方法。foobar 是方法名,response 前缀表示这个方法一定有一个回应。你可以通过函数返回值来回应远程调用。

To define these kinds of remote methods, declare a remote method using function response.foobar(...) . foobar is the method name, response prefix means that a response is required. You can use the return value as the response of RPC.

调用这个远程方法,可以通过 obj.req.foobar(...) 来调用它。obj 是服务对象,req 表示这个调用需要接收返回值。foobar 是方法的名字。其实,在创建出 obj 对象时,框架已经了解了这个 foobar 方法有返回值,这里多此一举让使用者明确 req 类型是为了让调用者(以及潜在的代码维护者)清楚这次调用是阻塞的,会导致服务挂起,等待对方的回应。

To call this RPC, you can use obj.req.foobar(...) . obj is the service object, req means it requires a return from this call. foobar is the method name. Actually, when creating this obj, the framework already knows there is a return for this foobar method, this extra step is to make sure that the caller knows this type of req type can cause blocking, service suspension, and waiting of response.

如果你设计的协议不需要返回值,那么可以通过定义 function accept.foobar(...) 来声明。这里可以有和 response 组相同的方法名。通过 accept 前缀来区分 response 组下同名的方法。这类方法的实现函数不可以有返回值。

If your protocol doesn't require any return, you can declare it with function accept.foobar(...) . It can have the same naming as the response group. And use accept prefix to distinguish the response group when it has the same name. Functions implemented with this type of method cannot have a return value.

调用这类不需要返回值的远程方法,应该使用 obj.post.foobar(...) 。这个调用不会阻塞。所以你也不用担心这里服务会挂起,因为别的消息进入改变了服务的内部状态。

You are supposed to use obj.post.foobar(...) for these remote methods that don't require a return value. This call is non-blocking, so you don't need to worry about service suspension or internal state change of service caused by other messages.

注: 除了用于本地消息或 master/slave 结构下的消息,post 现在也适用于 Cluster 模式, 。

Note: besides using it for local messages or master/slave messages, post is also good for Cluster mode now.

热更新 (Hotfix)

snax 是支持热更新的(只能热更新 snax 框架编写的 lua 服务)。但热更新更多的用途是做不停机的 bug 修复,不应用于常规的版本更新。所以,热更新的 api 被设计成下面这个样子。更适合打补丁。

snax also supports hotfix (limited for lua services written with snax framework). But it's mainly used for bug hotfix without services terminating, not for regular updates. So if your hotfix is designed as below, it's better to use a patch.

你可以通过 snax.hotfix(obj, patchcode) 来向 obj 提交一个 patch 。

You can submit a patch to obj with snax.hotfix(obj, patchcode) .

举个例子,你可以向上面列出的 pingserver 提交一个 patch :

For example, you can submit a patch of the pingserver mentioned above :

snax.hotfix(obj, [[

local i
local hello

function accept.hello()
	i = i + 1
	print ("fix", i, hello)
end

function hotfix(...)
	local temp = i
	i = 100
	return temp
end

]])

这样便改写了 accept.hello 方法。在 patch 中声明的 local i 和 local hello 在之前的版本中也有同名的 local 变量。 snax 的热更新机制会重新映射这些 local 变量。让 patch 中的新函数对应已有的 local 变量,所以你可以安全的继承服务的内部状态。

And this overwrites the method of accept.hello. local i and local hello declared in a patch have the same local variable in previous versions. snax's hotfix strategy will map these local variables. With a new function in a patch having its own local variables, you can inherit the state of service safely.

patch 中可以包含一个 function hotfix(...) 函数,在 patch 提交后立刻执行。这个函数可以用来查看或修改 snax 服务的线上状态(因为 local 变量会被关联)。hotfix 的函数返回值会传递给 snax.hotfix 的调用者。

patch can also have a function hotfix(...) function and it will be executed once a patch is released. This function can be used for checking or changing the online state of snax service (because local variables will be linked). The return of the hotfix function will be passed to the caller of snax.hotfix.

所以,你也可以提交一个仅包含 hotfix 函数的 patch ,而不修改任何代码。这样的 patch 通常用于查看 snax 服务的内部状态(内部 local 变量的值),或用于修改它们。

So, you can also submit a patch with hotfix functions only without changing any code. This kind of patch is usually used for checking the internal state of service (the values of local variables) or changing them.

注:不可以在 patch 中增加新的远程方法。

Note: Please don't add new remote methods in a patch.


test/testping.lua 是对 pingserver 的调用范例,你可以查看它加深理解。

test/testping.lua has an example of pingserver, check it for more detail.

snax 的实现分两部分:让服务工作起来的框架,实现在 service/snaxd.lua ;snax 的 api ,在 lualib/snax.lua 以及 lualib/snax/* 中。有兴趣的同学可以查看。

The implementation of snax contains two parts: the framework to make a service run, it's in service/snaxd.lua; the api of snax, it's in lualib/snax.lua and lualib/snax/*. Check it if you are interested.

Clone this wiki locally