Add scripting persistence and better replication support.

This commit is contained in:
Yossi Gottlieb
2013-01-11 22:05:29 +02:00
parent b26163c2e8
commit 142a8099cb
6 changed files with 122 additions and 4 deletions
+15
View File
@@ -16,6 +16,21 @@ The 'dbversion' value is stored in the RDB, which makes it incompatible with
stock Redis RDB files.
Script Replication
------------------
This version of Redis provides a mechanism to store scripts in RDB files. As
a results, scripts are as persistent as the database itself. Also, this
allows slave servers to receive scripts when initiating a replication link.
In addition, Redis behaves a bit differently in order to fully support
script replication:
- EVAL command with a DB-modifying script will be replicated as-is.
- EVAL command with a non-DB modifying script will be replicated as a
SCRIPT LOAD command (to save processing on the slave).
- The same applies to EVALSHA, which is replicated as an EVAL or SCRIPT LOAD.
Different BGSAVE Types
----------------------
+40
View File
@@ -31,6 +31,7 @@
#include "lzf.h" /* LZF compression library */
#include "zipmap.h"
#include "endianconv.h"
#include "lua.h"
#include <math.h>
#include <sys/types.h>
@@ -700,6 +701,20 @@ int rdbSave(char *filename) {
}
dictReleaseIterator(di);
}
/* Store scripts */
di = dictGetSafeIterator(server.lua_scripts);
while ((de = dictNext(di)) != NULL) {
robj key, *o = dictGetVal(de);
sds keystr = sdsnew(REDIS_RDB_SCRIPT_KEY_PREFIX);
keystr = sdscatsds(keystr, dictGetKey(de));
keystr = sdscat(keystr, REDIS_RDB_SCRIPT_KEY_SUFFIX);
initStaticStringObject(key, keystr);
if (rdbSaveKeyValuePair(&rdb,&key,o,-1,-1) == -1) goto werr;
}
dictReleaseIterator(di);
di = NULL; /* So that we don't release it again on error. */
/* EOF opcode */
@@ -1190,6 +1205,31 @@ int rdbLoad(char *filename) {
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
/* Read value */
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
/* Handle meta-keys */
if (val->type == REDIS_STRING && (*(char *)key->ptr & 0xff) == 0xdb) {
sds keystr = (sds) key->ptr;
if (sdslen(keystr) == REDIS_RDB_SCRIPT_KEY_LEN &&
!memcmp(keystr, REDIS_RDB_SCRIPT_KEY_PREFIX, sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1)) {
char funcname[43];
robj *shakey = createStringObject(keystr + sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1, 40);
funcname[0] = 'f';
funcname[1] = '_';
memcpy(funcname + 2, shakey->ptr, 40);
funcname[42] = 0;
if (luaCreateFunction(NULL, server.lua, funcname, val) != REDIS_OK) {
redisLog(REDIS_WARNING, "FATAL: Data file contains an invalid LUA script.\n");
exit(1);
}
decrRefCount(shakey);
decrRefCount(key);
continue;
}
}
/* Check if the key already expired. This function is used when loading
* an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is
+3
View File
@@ -42,6 +42,9 @@
/* Special key used to store dbversion in a backward-compatible manner */
#define REDIS_RDB_DBVERSION_KEY "\xDB__dbversion__\xDB"
#define REDIS_RDB_SCRIPT_KEY_PREFIX "\xDB__lua_script__"
#define REDIS_RDB_SCRIPT_KEY_SUFFIX "__\xDB"
#define REDIS_RDB_SCRIPT_KEY_LEN (sizeof(REDIS_RDB_SCRIPT_KEY_PREFIX)-1 + sizeof(REDIS_RDB_SCRIPT_KEY_SUFFIX)-1 + 40)
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
+1
View File
@@ -1075,6 +1075,7 @@ char *sentinelHandleConfiguration(char **argv, int argc);
/* Scripting */
void scriptingInit(void);
int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body);
/* Git SHA1 */
char *redisGitSHA1(void);
+25 -4
View File
@@ -757,16 +757,20 @@ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body
funcdef = sdscatlen(funcdef," end",4);
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
lua_tostring(lua,-1));
if (c != NULL) {
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
lua_tostring(lua,-1));
}
lua_pop(lua,1);
sdsfree(funcdef);
return REDIS_ERR;
}
sdsfree(funcdef);
if (lua_pcall(lua,0,0,0)) {
addReplyErrorFormat(c,"Error running script (new function): %s\n",
lua_tostring(lua,-1));
if (c != NULL) {
addReplyErrorFormat(c,"Error running script (new function): %s\n",
lua_tostring(lua,-1));
}
lua_pop(lua,1);
return REDIS_ERR;
}
@@ -911,6 +915,22 @@ void evalGenericCommand(redisClient *c, int evalsha) {
resetRefCount(createStringObject("EVAL",4)));
rewriteClientCommandArgument(c,1,script);
}
/* When we have slaves we want to always replicate scripts, but avoid
* EVAL when a script has no impact on the slave.
*/
if (!server.lua_write_dirty) {
robj *script = createStringObject("SCRIPT", 6);
robj *load = createStringObject("LOAD", 4);
/* Rewrite as SCRIPT LOAD. Note that SHA1 expansion has already
* been done here.
*/
rewriteClientCommandVector(c, 3, script, load, c->argv[1]);
decrRefCount(script);
decrRefCount(load);
server.dirty++;
}
}
void evalCommand(redisClient *c) {
@@ -1004,6 +1024,7 @@ void scriptCommand(redisClient *c) {
}
addReplyBulkCBuffer(c,funcname+2,40);
sdsfree(sha);
server.dirty++; /* Replicate this command */
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
if (server.lua_caller == NULL) {
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
+38
View File
@@ -367,3 +367,41 @@ start_server {tags {"scripting repl"}} {
} {a 1}
}
}
start_server {tags {"persistent script repl"}} {
start_server {} {
test {Load read-only script 99 into the main instance using EVAL} {
r eval {return 99} 0
} {99}
test {Connect a slave to the main instance} {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 role] eq {slave} &&
[string match {*master_link_status:up*} [r -1 info replication]]
} else {
fail "Can't turn the instance into a slave"
}
}
test {EVALSHA against slave to run script 99} {
r -1 evalsha a2eba4a6e7d6643bc4b14aa4f9e0eedd55dbae3d 0
} {99}
test {Load read-only script 98 into the main instance using EVAL} {
r eval {return 98} 0
} {98}
test {EVALSHA against slave to run script 98} {
r -1 evalsha 069e4d5d0c3afa79c9a545d8c620dbb94c2a5996 0
} {98}
test {Load read-only script 97 into the main instance using SCRIPT LOAD} {
r script load {return 97}
} {3f1673c4bedd6a9bc81500f26a454cd277092f80}
test {EVALSHA against slave to run script 97} {
r -1 evalsha 3f1673c4bedd6a9bc81500f26a454cd277092f80 0
} {97}
}
}