- Socket 编程发展
- OpenResty 简介
- Lua 入门
- Nginx
- 子查询
- 不同阶段共享变量
- 防止 SQL 注入
- 如何发起新 HTTP 请求
- 访问有授权验证的 Redis
- select+set_keepalive 组合操作引起的数据读写错误
- redis 接口的二次封装(简化建连、拆连等细节)
- redis 接口的二次封装(发布订阅)
- pipeline 压缩请求数量
- script 压缩复杂请求
- 动态生成的 lua-resty-redis 模块方法
- LuaCjsonLibrary
- json解析的异常捕获
- 稀疏数组
- 空table编码为array还是object
- PostgresNginxModule
- 调用方式简介
- 不支持事务
- 超时
- 健康监测
- SQL注入
- LuaNginxModule
- 执行阶段概念
- 正确的记录日志
- 热装载代码
- 阻塞操作
- 缓存
- sleep
- 定时任务
- 禁止某些终端访问
- 请求返回后继续执行
- 调试
- 请求中断后的处理
- 我的 lua 代码需要调优么
- 变量的共享范围
- 动态限速
- shared.dict 非队列性质
- 正确使用长链接
- 如何引用第三方 resty 库
- 典型应用场景
- 怎样理解 cosocket
- 如何安全启动唯一实例的 timer
- 如何正确的解析域名
- LuaRestyDNSLibrary
- 使用动态 DNS 来完成 HTTP 请求
- LuaRestyLock
- 缓存失效风暴
- HTTPS 时代
- 动态加载证书和 OCSP stapling
- TLS session resumption
- 测试
- Web 服务
- 火焰图
- 如何定位问题
- module 是邪恶的
- FFI
- 什么是 JIT
script 压缩复杂请求
从 pipeline 章节,我们知道对于多个简单的 Redis 命令可以汇聚到一个请求中,提升服务端的并发能力。然而,在有些场景下,我们每次命令的输入需要引用上个命令的输出,甚至可能还要对第一个命令的输出做一些加工,再把加工结果当成第二个命令的输入。pipeline 难以处理这样的场景。庆幸的是,我们可以用 Redis 里的 script 来压缩这些复杂命令。
script 的核心思想是在 Redis 命令里嵌入 Lua 脚本,来实现一些复杂操作。Redis 中和脚本相关的命令有:
- EVAL
- EVALSHA
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD
官网上给出了这些命令的基本语法,感兴趣的同学可以到这里查阅。其中 EVAL 的基本语法如下:
EVAL script numkeys key [key ...] arg [arg ...]
EVAL 的第一个参数 script 是一段 Lua 脚本程序。这段 Lua 脚本不需要(也不应该)定义函数。它运行在 Redis 服务器中。
EVAL 的第二个参数 numkeys 是参数的个数,后面的参数 key(从第三个参数),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1],KEYS[2],以此类推)。
在命令的最后,那些不是键名参数的附加参数 arg [arg ...],可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1]、ARGV[2],诸如此类)。下面是执行 eval 命令的简单例子:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
OpenResty 中已经对 Redis 的所有原语操作进行了封装。下面我们以 EVAL 为例,来看一下 OpenResty 中如何利用 script 来压缩请求:
# you do not need the following line if you are using
# the ngx_openresty bundle:
lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;";
server {
location /usescript {
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000) -- 1 sec
-- or connect to a unix domain socket file listened
-- by a redis server:
-- local ok, err = red:connect("unix:/path/to/redis.sock")
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("failed to connect: ", err)
return
end
--- use scripts in eval cmd
local id = 1
local res, err = red:eval([[
-- 注意在 Redis 执行脚本的时候,从 KEYS/ARGV 取出来的值类型为 string
local info = redis.call('get', KEYS[1])
info = cjson.decode(info)
local g_id = info.gid
local g_info = redis.call('get', g_id)
return g_info
]], 1, id)
if not res then
ngx.say("failed to get the group info: ", err)
return
end
ngx.say(res)
-- put it into the connection pool of size 100,
-- with 10 seconds max idle time
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.say("failed to set keepalive: ", err)
return
end
-- or just close the connection right away:
-- local ok, err = red:close()
-- if not ok then
-- ngx.say("failed to close: ", err)
-- return
-- end
}
}
}
从上面的例子可以看到,我们要根据一个对象的 id 来查询该 id 所属 gourp 的信息时,我们的第一个命令是从 Redis 中读取 id 为 1 (id 的值可以通过参数的方式传递到 script 中)的对象的信息(由于这些信息一般 json 格式存在 Redis 中,因此我们要做一个解码操作,将 info 转换成 Lua 对象)。然后提取信息中的 groupid 字段,以 groupid 作为 key 查询 groupinfo。这样我们就可以把两个 get 放到一个 TCP 请求中,做到减少 TCP 请求数量,减少网络延时的效果啦。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论