redis-2.6支持通过EVAL命令来执行lua脚本,对lua脚本的支持扩展了redis的应用场景,redis支持路脚本需要做2件事
- redis能执行lua脚本
- 在lua脚本里能执行redis的命令
接下来,我将通过一个简单的实例来解析redis如何完成上述两个工作的。
构建一个简单的redis
#define DICT_SIZE 100
struct redisDict {
char* key[DICT_SIZE];
char* value[DICT_SIZE];
int idx;
};
static void setCommand(const char *key, const char *value)
{
/* ignore memory issue for simple */
if (dict.idx + 1 <= DICT_SIZE) {
dict.key[dict.idx] = (char *)malloc(strlen(key) + 1);
strcpy(dict.key[dict.idx], key);
dict.value[dict.idx] = (char *)malloc(strlen(value) + 1);
strcpy(dict.value[dict.idx], value);
dict.idx += 1;
}
}
static const char *getCommand(const char *key)
{
int j;
for (j = 0; j <= dict.idx; j++) {
if (strcmp(dict.key[j], key) == 0) {
return dict.value[j];
}
}
return "KeyNotFound";
}
上述代码实现了一个伪redis,支持setCommand、getCommand。
C调用lua脚本
具体例子参考http://lua-users.org/wiki/SimpleLuaApiExample
/*
* All Lua contexts are held in this structure. We work with it almost
* all the time.
*/
lua_State *L = luaL_newstate();
luaL_openlibs(L); /* Load Lua libraries */
/* Load the file containing the script we are going to run */
status = luaL_loadfile(L, "script.lua");
/* Ask Lua to run our little script */
result = lua_pcall(L, 0, LUA_MULTRET, 0);
上述代码片段中,其中script.lua是一个lua脚本。redis里稍有不同,redis里的脚本是通过EVAL命令传递到服务器端,redis将脚本拼成一个lua函数,然后调用loadbuffer,而这里从文件执行脚本调用的loadfile。
lua调用C函数
下面的lua代码里,调用的是redis的setCommand和getCommand。
redis.call("set", "foo", "bar");
return redis.call("get", "foo");
要想lua脚本能调用C代码,需要现在lua环境注册对应的C函数,参考redis的scriptingInit函数。
static int call(lua_State *L)
{
int argc = lua_gettop(L);
const char *cmd = lua_tostring(L, 1);
const char *key = lua_tostring(L, 2);
if (strcmp(cmd, "set") == 0) {
assert(argc == 3);
const char *value = lua_tostring(L, 3);
setCommand(key, value);
return 0;
} else if (strcmp(cmd, "get") == 0) {
assert(argc == 2);
lua_pushstring(L, getCommand(key));
return 1;
}
lua_pushstring(L, "Invalid Command");
return 1;
}
static void scriptingInit()
{
L = luaL_newstate();
luaL_openlibs(L);
/* Register the redis commands table and fields */
lua_newtable(L);
/* redis.call */
lua_pushstring(L, "call");
lua_pushcfunction(L, call);
lua_settable(L, -3);
/* Finally set the table as 'redis' global var. */
lua_setglobal(L, "redis");
}
完整示例代码
来自CODE的代码片
script.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
/* gcc -o test_lua lua.c -llua -lm -ldl */
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define DICT_SIZE 100
struct
redisDict
{
char
*
key
[
DICT_SIZE
];
char
*
value
[
DICT_SIZE
];
int
idx
;
};
struct
redisDict
dict
;
lua_State
*
L
;
static
void
setCommand
(
const
char
*
key
,
const
char
*
value
)
{
/* ignore memory issue for simple */
if
(
dict
.
idx
+
1
<=
DICT_SIZE
)
{
dict
.
key
[
dict
.
idx
]
=
(
char
*
)
malloc
(
strlen
(
key
)
+
1
);
strcpy
(
dict
.
key
[
dict
.
idx
],
key
);
dict
.
value
[
dict
.
idx
]
=
(
char
*
)
malloc
(
strlen
(
value
)
+
1
);
strcpy
(
dict
.
value
[
dict
.
idx
],
value
);
dict
.
idx
+=
1
;
}
}
static
const
char
*
getCommand
(
const
char
*
key
)
{
int
j
;
for
(
j
=
0
;
j
<=
dict
.
idx
;
j
++
)
{
if
(
strcmp
(
dict
.
key
[
j
],
key
)
==
0
)
{
return
dict
.
value
[
j
];
}
}
return
"KeyNotFound"
;
}
static
int
call
(
lua_State
*
L
)
{
int
argc
=
lua_gettop
(
L
);
const
char
*
cmd
=
lua_tostring
(
L
,
1
);
const
char
*
key
=
lua_tostring
(
L
,
2
);
if
(
strcmp
(
cmd
,
"set"
)
==
0
)
{
assert
(
argc
==
3
);
const
char
*
value
=
lua_tostring
(
L
,
3
);
setCommand
(
key
,
value
);
return
0
;
}
else
if
(
strcmp
(
cmd
,
"get"
)
==
0
)
{
assert
(
argc
==
2
);
lua_pushstring
(
L
,
getCommand
(
key
));
return
1
;
}
lua_pushstring
(
L
,
"Invalid Command"
);
return
1
;
}
static
void
scriptingRun
(
const
char
*
filename
)
{
int
status
=
luaL_loadfile
(
L
,
filename
);
if
(
status
)
{
fprintf
(
stderr
,
"Couldn't load file: %s
\n
"
,
lua_tostring
(
L
,
-
1
));
exit
(
1
);
}
/* Ask Lua to run our little script */
int
result
=
lua_pcall
(
L
,
0
,
LUA_MULTRET
,
0
);
if
(
result
)
{
fprintf
(
stderr
,
"Failed to run script: %s
\n
"
,
lua_tostring
(
L
,
-
1
));
exit
(
1
);
}
/* Get the returned value at the top of the stack (index -1) */
const
char
*
value
=
lua_tostring
(
L
,
-
1
);
printf
(
"%s
\n
"
,
value
);
lua_pop
(
L
,
1
);
/* Take the returned value out of the stack */
lua_close
(
L
);
/* Cya, Lua */
}
static
void
scriptingInit
()
{
/*
* All Lua contexts are held in this structure. We work with it almost
* all the time.
*/
L
=
luaL_newstate
();
/* Load Lua libraries */
luaL_openlibs
(
L
);
/* Register the redis commands table and fields */
lua_newtable
(
L
);
/* redis.call */
lua_pushstring
(
L
,
"call"
);
lua_pushcfunction
(
L
,
call
);
lua_settable
(
L
,
-
3
);
/* Finally set the table as 'redis' global var. */
lua_setglobal
(
L
,
"redis"
);
}
int
main
(
void
)
{
scriptingInit
();
scriptingRun
(
"script.lua"
);
return
0
;
}
|
来自CODE的代码片
lua.c