snax 의 동기는 skynet 서비스구현을 도와주기위한 간단한 프레임워크이다. 하지만 skynet 을 간소화하려는 목표에 도달하지 못한것같다. 오히려 사용자에게 많은 어려움을 가져다 주었다. 그래서 더는 사용하는것을 추천하지 않는다 (skynet api 를 사용하여 적당한 자신만의 프레임워크를 만들기를 추천한다). 하위버전 호환을 위해 유지되고 있다.
使用 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 서비스를 사용하려면 먼저 Config 에 snax 경로를 설정해야 한다. 모든 snax 서비스는 서비스기동을 위한 이름을 가지고있고, lua 모듈이름규칙을 지키는것을 추천한다, 하지만 이름에 “.” 을 포함 하는 것은 추천하지 않는다 (“.” 을 “/”로 바꾸는 규칙을 지원하지 않음). 서비스 기동시 경로 검색에 찾기경로를 통해 대응하는 파일을 검색한다.
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.
snax 서비스는 lua 로 쓰여진다, 하지만 독립적인 lua 모듈이 아니다. snax 프레임워크 에의해 로딩되는 루아 함수 세트를 포함하게 된다.
test/pingserver.lua 就是一个简单的 snax 服务范例:
test/pingserver.lua is a simple snax service example:
test/pingserver.lua 는 간단한 snax 서비스 예제이다:
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 서비스의 기동과 종료 모든 snax 서비스는 init 함수를 정의해야하며, 서비스의기동은 이함수를 호출하며, 시작시 파라메터를 이 함수에 넘긴다.
snax 服务还可以定义一个 exit 函数用于响应服务退出的事件,同样能接收一些参数。
snax service also has its own exit function for exit event of service and it can also accept parameters.
snax 서비스는 또한 exit 함수를 정의할수 있고 서비스 종료이벤트에 대응한다, 같은 방법으로 파라메터를 받아들인다.
和标准的 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.
표준 skynet 서비스와는 다르게, 이 파라메터의 형식은 제한이 없으며, lua 의 복잡한 데이터 형이 될수 있다 (skynet의 저수준 제한은, 문자열만 받을수 있다).
启动一个 snax 服务有三种方式:
There are 3 ways to start a snax service:
snax 서비스를 시작하는것은 세가지 방법이 있다.
local snax = require "snax"
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.
snax.newservice(name, …): 하나의 서비스를 여러개 시작한다. 서비스이름과 파라메터를 넘기며, 객체를 반환하며, 이 객체를 사용하여 상호작용한다. newservice 를 같은 이름으로 여러번 호출하더라도, 여러개의 서비스 인스턴스를 생성하게 된다. 각각은 독립적이며, 객체들은 서로 다르다.
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.
snax.uniqueservice(name, …): 위의 API 와 비슷하다, 하지만 한개의 노드에 오직 한개의 서비스만 시작될수 있다. 만약 여러번 실행할경우, 동일한 객체를 반환하게된다.
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).
snax.globalservice(name, …): 위의 api 와 비슷하다, 하지만 skynet 네트워크상(여러개의 노드를 띄었을 경우), 오직 한개의 동일한 이름의 서비스만 존재할 수 있다.
前一种方式可以看成是启动了一个匿名服务,启动后只能用地址(以及对服务地址的对象封装)与之通讯;后两种方式都可以看成是具名服务,之后可以用名字找到它。
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.
앞의 첫번째 방식은 익명서비스를 기동 하는 것으로 볼 수 있으며, 시작후 주소(서비스 주소의 객체 WRAPPING)만을 사용하여 통신; 후에 두개는 이름 지어진 서비스로 볼수 있고 이름을 통해 찾을 수 있다.
后两种方式是对具名服务惰性初始化。如果你在代码中写了多处服务启动,第一次会生效,后续只是对前面启动的服务的查询。往往我们希望明确服务的启动流程(在启动脚本里就把它们启动好);尤其是全局(整个 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:
뒤에 두개 함수는 이름지어진 서비스의 지연된 초기화이다. 서비스 시작을 코드의 여러군데에 넣으면, 처음에는 동작하고, 이후에는 그저 이전에 기동했던 서비스를 검색한다. 보통의 경우 서비스의 시작 흐름(시작스크립트내에서 서비스들 시작완료)이 명확하기를 원한다; 특히 이런 글로벌(skynet네트워크상유일)서비스일 경우, 그 서비스가 어떤 노드상에서 시작되었는지 명확히 하고 싶어한다(만약 지연된 초기화일경우, 이 서비스가 어느 노드에서 시작되었는지 알 방법이 없다). 이럴때, 아래API 들을 사용한다:
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.
snax.queryservice(name): 현재 노드의 이름지어진 서비스들을 검색하며, 서비스의 객체를 반환한다. 만약 서비스가 시작되지 않았다면, 서비스가 기동될 때 까지 블럭 된다.
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.
snax.queryglobal(mame): 글로벌 이름지어진 서비스를 검색하며, 서비스의 객체를 반환한다. 만약 서비스가 시작되지 않았다면, 기동될 때 까지 블럭 된다.
对于匿名服务,你无法在别处通过名字得到和它交互的对象。如果你有这个需求,可以把对象的 .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.
익명 서비스에 대하여, 이름을 통해 상호작용 객체를 얻어낼 수 없다. 이럴 필요가 있다면, 객체의 “.handle” 을 메시지를 통해 다른이에게 발송한다. handle 은 숫자이고, snax 서비스의 skynet 서비스 주소이다.
这个数字的接收方可以通过 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.bind(handle, type) 을 통하여 서비스의 오브젝트로 변환할 수 있다. 여기서 2번째 파라메터는 전달되는 서비스의 시작 이름이여야 하며, 이를 통해 어떤 서비스의 어떤 원격메서드가 호출되야하는지 사용한다. 물론 코드상에 쓰지않고 .type 과 .handle 을 동시에 보내는 방법도 있다.
如果你想让一个 snax 服务退出,调用 snax.kill(obj, …) 即可。
만약 snax 서버를 종료시키고 싶으면, snax.kill(obj, …) 를 호출한다.
snax.self() 用来获取自己这个服务对象,它等价于 snax.bind(skynet.self(), SERVICE_NAME)
snax.self() 자신의 서비스 객체를 획득한다, snax.bind(skynet.self(), SERVICE_NAME) 과 동일하다
snax.exit(…) 退出当前服务,它等价于 snax.kill(snax.self(), …) 。 snax.exit(…) 현재 서비스를 종료한다, snax.kill(snax.self(), …). 와 동일하다
注:snax 的接口约定是通过分析对应的 lua 源文件完成的。所以无论是消息发送方还是消息接收方都必须可以读到其消息处理的源文件。不仅 snax.newservice 会加载对应的源文件生成新的服务;调用者本身也会在自身的 lua 虚拟机内加载对应的 lua 文件做一次分析;同样, snax.bind 也会按 typename 分析对应的源文件。
주의: snax 의 인터페이스 약속은 Lua 소스코드의 분석을 통하여 완성된다. 그러니 메시지 발송자든 메시지 수신자든 그 메시지 처리의 소스코드를 읽을 수 있어야 한다. snax.newservice는 해당 소스파일을 불러와서 새로운 서비스를 생성할 뿐 아니라, 호출자 자신도 자신의 lua VM 내에 대응하는 lua 파일을 읽어와 분석한다; 같은방식으로, snax.bind 또한 typename 에 따라 대응하는 소스파일을 분석한다.
RPC 调用
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.
snax 서비스에 다른서비스의 요청에 대응하고 응답을 하거나 말거나 하는 한세트의 함수를 정의 할수 있다. snax 의 서비스가 아니여도 snax RPC 호출을 사용할 수 있다.
要定义这类远程方法,可以通过定义 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.
이런 종류의 RPC 를 정의하기 위해서, function response.foobar(…) 형식을 통해 RPC 를 정의한다. foobar 는 메서드이름, response 머릿말은 이함수가 응답이 꼭 필요하다는 의미이다. 함수 반환값을 통해 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.
이 RPC 호출은 obj.req.foobar(…) 를 사용하여 이루어진다. obj 는 서비스객체이며, req 는 이 호출이 응답값을 받아야함을 의미한다. foobar 는 메서드 이름이다. 사실, obj 를 생성시, framework 는 이미 이 foobar 메서드가 응답값이 있음을 알고 있다. 이는 사용자가 이 타입이 req 타입이며 block 될수 있으며, 이는 서비스가 상대방의 응답을 받을 때 까지 중지될 수 있음을 명확하게 하기 위함이다.
如果你设计的协议不需要返回值,那么可以通过定义 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.
당신이 설계한 프로토콜이 응답이 필요 없다면, function accept.foobar(…) 로 선언한다. 여기에는 response 그룹과 동일한 이름의 메서드가 있을 수 있다. accept 접두사를 통해 response 그룹과 동일한 이름을 구별한다. 이 타입의 메서드는 return 값을 가질 수 없다.
调用这类不需要返回值的远程方法,应该使用 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.
응답값이 필요하지 않은 RPC 를 호출하는것은 obj.post.foobar(…). 를 사용하는 것이 옳을것 이다. 이 함수는 블럭되지 않는다. 그러니 서비스가 정지하거나, 다른 메시지로 인하여 서비스의 내부 상태가 변경될 것을 걱정하지 않아도 된다.
注: 除了用于本地消息或 master/slave 结构下的消息,post 现在也适用于 Cluster 模式, 。
Note: besides using it for local messages or master/slave messages, post is also good for Cluster mode now.
주: 로컬 메시지나 master/slave 메시지들 뿐 아니라, post 는 현재 cluster mod 에서도 활용하고 있다.
热更新 (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 는 핫픽스를 지원한다 (snax framework 로 쓰여진 lua 서비스만 가능하다). 하지만 핫픽스의 용도는 서비스를 정지하지 않고 bug 를 수정하는 것이다, 일반적인 버전 업데이트 용도가 아니다, 그러한 이유로 핫픽스의 api 는 아래와 같이 설계되었다. 이것은 패치에 사용하기 좋다.
你可以通过 snax.hotfix(obj, patchcode) 来向 obj 提交一个 patch 。
You can submit a patch to obj with snax.hotfix(obj, patchcode) .
snax.hotfix(obj, patchcode) 를 사용하여 obj 에 patch 를 넣을수 있다.
举个例子,你可以向上面列出的 pingserver 提交一个 patch :
For example, you can submit a patch of the pingserver mentioned above :
예를들면, 위에서 본 pingserver 에 patch 를 적용한다
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.
이렇게 accept.hello 를 변경한다. patch 에 선언된 local i 와 local hello 는 이전 버전에도 동일한 local 변수가 있었다. snax 의 핫픽스 메커니즘은 이런 local 변수를 재 매핑한다. patch 내의 새로운 함수는 자신의 local 변수를 가지게된다. 이렇게 서비스의 내부상태를 안전하게 계승하게 된다.
역주: 영어랑 중국어가 반대로 써있네.
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.
patch 내에는 function hotfix(…) 함수를 포함할수 있다. patch 적용후 즉시 실행된다. 이 함수는 snax 서비스의 온라인 상태(local 변수가 연결 되기 때문에)를 검색하거나 변경하는데 사용된다. hotfix 의 함수 반환값은 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.
그러하니, 어떤 코드도 변경하지않고, 그저 hotfix 함수만을 포함하는 패치를 적용할 수도 있다. 이런 patch 는 snax 서버의 내부 상태(내부 local 함수값)를 살펴보거나 수정하는데 사용된다.
注:不可以在 patch 中增加新的远程方法。
Note: Please don’t add new remote methods in a patch.
주: 패치에서 새로운 RPC 를 추가할수 없다.
test/testping.lua 是对 pingserver 的调用范例,你可以查看它加深理解。
test/testping.lua has an example of pingserver, check it for more detail.
test/testping.lua 는 pingserver 의 사용 예제이며, 깊이 이해하려면 참고하자.
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.
snax 의 구현은 두개의 부분이다: 서비스를 동작시키는 프레임워크는 service/snaxd.lua 에 구현되어있다; snax 의 api 는 lualib/snax.lua 그리고 lualib/snax/* 에 구현되어있다. 관심이 있으면 살펴보자