# script壓縮復雜請求
從[pipeline那一章節](https://github.com/moonbingbing/openresty-best-practices/blob/master/redis/pipeline.md),我們知道對于多個簡單的redis命令可以匯聚到一個請求中,提升服務端的并發能力。然而,在有些場景下,我們每次命令的輸入需要引用上個命令的輸出,甚至可能還要對第一個命令的輸出做一些加工,再把加工結果當成第二個命令的輸入。pipeline難以處理這樣的場景。慶幸的是,我們可以用redis里的script來壓縮這些復雜命令。
script的核心思想是在redis命令里嵌入Lua腳本,來實現一些復雜操作。Redis中和腳本相關的命令有:
- EVAL
- EVALSHA
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD
官網上給出了這些命令的基本語法,感興趣的同學可以到[這里](http://redis.io/commands/eval)查閱。其中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 '
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"
ok, err = red:eval([[
local info = redis.call('get', KEYS[1])
info = json.decode(info)
local g_id = info.gid
local g_info = redis.call('get', g_id)
return g_info
]], 1, id)
if not ok then
ngx.say("failed to get the group info: ", err)
return
end
-- 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請求數量,減少網絡延時的效果啦。
- 序
- Lua簡介
- Lua環境搭建
- 基礎數據類型
- 表達式
- 控制結構
- if/else
- while
- repeat
- 控制結構for的使用
- break,return
- Lua函數
- 函數的定義
- 函數的參數
- 函數的返回值
- 函數回調
- 模塊
- String庫
- Table庫
- 日期時間函數
- 數學庫函數
- 文件操作
- 元表
- 面向對象編程
- FFI
- LuaRestyRedisLibrary
- select+set_keepalive組合操作引起的數據讀寫錯誤
- redis接口的二次封裝(簡化建連、拆連等細節)
- redis接口的二次封裝(發布訂閱)
- pipeline壓縮請求數量
- script壓縮復雜請求
- LuaCjsonLibrary
- json解析的異常捕獲
- 稀疏數組
- 空table編碼為array還是object
- 跨平臺的庫選擇
- PostgresNginxModule
- 調用方式簡介
- 不支持事務
- 超時
- 健康監測
- SQL注入
- LuaNginxModule
- 執行階段概念
- 正確的記錄日志
- 熱裝載代碼
- 阻塞操作
- 緩存
- sleep
- 定時任務
- 禁止某些終端訪問
- 請求返回后繼續執行
- 調試
- 調用其他C函數動態庫
- 我的lua代碼需要調優么
- 變量的共享范圍
- 動態限速
- shared.dict 非隊列性質
- 如何添加自己的lua api
- 正確使用長鏈接
- 如何引用第三方resty庫
- 使用動態DNS來完成HTTP請求
- 緩存失效風暴
- Lua
- 下標從1開始
- 局部變量
- 判斷數組大小
- 非空判斷
- 正則表達式
- 不用標準庫
- 虛變量
- 函數在調用代碼前定義
- 抵制使用module()函數來定義Lua模塊
- 點號與冒號操作符的區別
- 測試
- 單元測試
- API測試
- 性能測試
- 持續集成
- 灰度發布
- web服務
- API的設計
- 數據合法性檢測
- 協議無痛升級
- 代碼規范
- 連接池
- c10k編程
- TIME_WAIT問題
- 與Docker使用的網絡瓶頸
- 火焰圖
- 什么時候使用
- 顯示的是什么
- 如何安裝火焰圖生成工具
- 如何定位問題