30 分钟入门 lua-yar¶
学习导向教程:面向 YAR 新手,边做边学。 如果你已熟悉 RPC 概念,只需解决方案,请直接看 How-to 指南。
本教程带你从零理解 YAR RPC 协议,并用 lua-yar 构建第一个可运行的服务。每步都有可执行代码和概念讲解。
你将学到¶
- 什么是 YAR RPC、它解决什么问题
- 请求/响应循环的工作原理
- 从零构建一个可运行的 RPC 服务
- packager、transport、hooks 如何协作
- 迁移到 OpenResty 生产环境
前置准备¶
# 安装 lua-yar 和 luasocket
luarocks install lua-yar
luarocks install luasocket
# 验证
lua -e 'local Yar = require("yar"); print(Yar.VERSION)'
第 1 步:理解 YAR 协议(5 分钟)¶
YAR(Yet Another RPC Framework)是 PHP 生态的轻量级 RPC 协议。RPC(Remote Procedure Call)让你像调用本地函数一样调用远程服务:
-- 本地调用
local result = add(1, 2) -- => 3
-- RPC 调用(看起来一样,但 add 在另一台机器上)
local result = client:call("add", {1, 2}) -- => 3
YAR 用二进制数据流交换消息。一条完整消息由三段组成:
+-------------------+-------------------+---------------------+
| Packager Name | Yar Header | Body |
| 8 字节 | 82 字节 | body_len 字节 |
+-------------------+-------------------+---------------------+
- Packager Name(8 字节):声明 Body 用什么格式编码(
JSON或Msgpack) - Yar Header(82 字节):固定头,含事务 ID、魔数
0x80DFEC60、认证信息 - Body(变长):序列化后的请求或响应数据
请求体长这样(JSON 编码时):
-i = 事务 ID(匹配请求与响应)
- m = 方法名
- p = 参数数组
响应体长这样:
-s = 状态(0 成功,1 错误)
- r = 返回值
- e = 错误消息
不用记这些细节,只需知道:客户端发
{i,m,p},服务端回{i,s,r,o,e}。完整规范见 protocol.md。
第 2 步:你的第一个 Server(5 分钟)¶
我们来写一个计算器 RPC 服务。创建 my_server.lua:
local Yar = require("yar")
local server = Yar.server.new({
add = function(a, b) return a + b end,
sub = function(a, b) return a - b end,
mul = function(a, b) return a * b end,
})
server:listen("http://0.0.0.0:8888")
server:loop()
运行它:
发生了什么?
Yar.server.new({...})创建了一个 Server,service表里的函数变成 RPC 方法:listen("http://0.0.0.0:8888")在 8888 端口监听,:loop()启动 accept 循环- server 收到 HTTP POST 请求时,解析 YAR 二进制消息,找到
add方法,调用它,把结果编码成 YAR 响应返回
用 curl 发一个原始 YAR 请求看看(Body 是二进制,这里用管道生成):
# 用 lua-yar client 测试更方便,见下一步
curl -X POST http://127.0.0.1:8888/ -d "$(lua -e 'print("")')" 2>/dev/null | xxd | head
⚠
loop()是单线程顺序 accept,一个慢请求会阻塞所有连接。仅供开发/测试。生产方案见 第 6 步。
第 3 步:你的第一个 Client(5 分钟)¶
server 跑着,另开一个终端写 client:
local Yar = require("yar")
local client = Yar.client.new("http://127.0.0.1:8888/")
print(client:call("add", {1, 2})) -- => 3
print(client:call("sub", {10, 3})) -- => 7
print(client:call("mul", {4, 5})) -- => 20
→ 完整示例(HTTP + TCP 双模式):example/client.lua
请求/响应循环:
client:call("add", {1, 2})生成事务 ID(如 12345)- 构造请求体
{i=12345, m="add", p={1,2}},用 JSON 编码 - 拼 YAR 消息:
JSON\0\0\0\0+ 82 字节头 + JSON body - 通过 HTTP POST 发给 server
- server 解析、调用
add(1,2)、返回{i=12345, s=0, r=3, o="", e=""} - client 用事务 ID 12345 匹配响应,取出
r=3返回给你
错误处理:
local ret, err = client:call("div", {1, 0}) -- 假设 div 除零报错
if not ret then
-- err 是 Yar.error 对象,通过 err.code 精确匹配
if err.code == Yar.error.EXCEPTION then
print("方法执行出错: " .. tostring(err))
end
end
第 4 步:切换 packager(5 分钟)¶
YAR 支持两种序列化格式。我们切换到 Msgpack 看看区别:
local Yar = require("yar")
local client = Yar.client.new("http://127.0.0.1:8888/")
client:set_options({ packager = Yar.PACKAGER_MSGPACK })
print(client:call("add", {1, 2})) -- => 3,结果一样
区别在哪?
| Packager | 请求 Body 大小(add(1,2)) | 格式 |
|---|---|---|
| JSON | 19 字节 | 文本 {"i":12345,"m":"add","p":[1,2]} |
| Msgpack | 14 字节 | 二进制,更紧凑 |
server 会自动从请求头读取 packager name,用对应格式解码。响应也用同一种 packager 编码。这就是协议自适应——你不用告诉 server 用什么格式,它在消息头里声明了。
Msgpack 在大量小消息场景下可节省带宽。JSON 更易调试(可读)。
第 5 步:加 hooks 观测(5 分钟)¶
hooks 是请求/响应的拦截点,用于日志、追踪、统计:
local Yar = require("yar")
local client = Yar.client.new("http://127.0.0.1:8888/")
client:set_options({
hooks = {
on_request = function(method, params)
print(string.format("[REQ] %s(%s)", method, table.concat(params, ", ")))
end,
on_response = function(method, retval, err)
if retval then
print(string.format("[RESP] %s => %s", method, tostring(retval)))
else
print(string.format("[RESP] %s => ERROR: %s", method, tostring(err)))
end
end,
},
})
client:call("add", {1, 2})
client:call("mul", {3, 4})
输出:
hooks 特点:
- pcall 保护——hook 报错不影响主流程,降级为 Log.warn
- 零开销——未配置时 nil 检查跳过
- 只读回调——不传 request/response 对象,不拦截数据流
server 端也有 hooks,用法一样:
server:set_options({ hooks = {...} })。
第 6 步:迁移到 OpenResty(5 分钟)¶
原生 Lua 的 loop() 是单线程顺序的。生产环境用 OpenResty 获得协程并发:
Server(OpenResty)¶
http {
lua_package_path "/path/to/lua-yar/src/?.lua;/path/to/lua-yar/src/?/init.lua;;";
server {
listen 8888;
location / {
content_by_lua_block {
local Yar = require "yar"
local server = Yar.server.new({
add = function(a, b) return a + b end,
})
ngx.req.read_body()
server:handle({
method = ngx.req.get_method(),
data = ngx.req.get_body_data() or "",
writer = function(status, headers, body)
ngx.status = status
for k, v in pairs(headers) do ngx.header[k] = v end
ngx.print(body)
end,
})
}
}
}
}
关键变化:
- 不用 server:loop() 顺序 accept,改用 Server.new() + handle() callback 模式
- handle() 的 callback 模式自动处理 HTTP 语义:状态码(200/400/413/500)、Content-Type 头、GET introspection、错误响应
- writer(status, headers, body) 回调签名对标 WSGI start_response,参数顺序 = HTTP 线序 = 回调执行序
- 并发由 nginx worker + cosocket 提供,不是库操心的事
Client(OpenResty)¶
local Yar = require "yar"
Yar.client.set_socket(ngx.socket) -- 注入 cosocket
local client = Yar.client.new("http://127.0.0.1:8888/")
client:set_options({
keepalive = { pool_size = 64, idle_timeout = 60000 }, -- 连接池
})
ngx.say(client:call("add", {1, 2}))
这就是 lua-yar 的设计哲学:
- 纯协议核心——协议处理与 I/O 分离,可被任意协程调度
- 并发由宿主提供——OpenResty / lua-eco / Skynet / copas / 原生协程均可
- 库不绑定运行时,不引用 ngx,通过注入获得能力
生产环境推荐直接用 lua-resty-yar(OpenResty 绑定,开箱即用)。
下一步¶
恭喜!你已掌握 lua-yar 的核心用法。继续深入:
| 想做什么 | 去哪里 |
|---|---|
| 查完整 API 签名 | api.md |
| 理解协议字节级布局 | protocol.md |
| 按问题找方案 | how-to.md |
| 理解设计选型 | design-rationale.md |
| 看设计决策记录 | design/ |
| 看多宿主并发示例 | server-implementations.md |