Мы познакомили вас с основами, связанными с Redis Lua, и если вы можете написать несколько простых сценариев Lua, поздравляем вас с окончанием средней школы Lua.
В университетском курсе мы в основном изучаем две части: отладку Lua-скриптов и принцип выполнения Lua в Redis.
Отладка Lua-скриптов
Redis поддерживает отладку сценариев Lua, начиная с версии 3.2, а имя отладчика — LDB. Он имеет несколько важных особенностей:
- Он использует режим сервер-клиент, поэтому это удаленная отладка. Сервер Redis является сервером отладки, а клиент по умолчанию — redis-cli. Другие клиенты, которые следуют серверному протоколу, также могут быть разработаны.
- По умолчанию каждый сеанс отладки является новым сеансом. То есть в процессе отладки сервер не будет заблокирован. Может по-прежнему использоваться другими клиентами или открывать новые сеансы. Это также означает, что все изменения, сделанные во время отладки, в конце откатываются.
- При желании режим отладки можно установить на синхронный, чтобы сохранить изменения в наборе данных. В этом режиме сервер блокируется на время отладки.
- Поддержка пошагового выполнения
- Поддержка статических и динамических точек останова
- Поддержка печати журналов отладки в консоль отладки из скриптов.
- Проверьте переменные Lua
- Отслеживание выполнения команд Redis
- Хорошая поддержка печати значений Redis и Lua.
- Обнаружение бесконечного цикла и длительного выполнения, имитация точек останова
Практика отладки Lua-скриптов
Перед началом отладки сначала напишите простой Lua-скрипт script.lua:
local src = KEYS[1]
local dst = KEYS[2]
local count = tonumber(ARGV[1])
while count > 0 do
local item = redis.call('rpop',src)
if item ~= false then
redis.call('lpush',dst,item)
end
count = count - 1
end
return redis.call('llen',dst)
Этот скрипт вставляет элементы в SRC в головку элемента DST в свою очередь.
С этим скриптом мы можем начать отладку.
мы можем использоватьredis-cli —eval
команду для запуска этого скрипта, и если вы хотите выполнить отладку, вы можете добавить параметр --ldb, поэтому сначала мы выполняем следующую команду:
redis-cli --ldb --eval script.lua foo bar , 10
Страница покажет некоторую справочную информацию и войдет в режим отладки.
Вы можете видеть, что страница справки сообщает нам
- воплощать в жизньquitМожет выйти из режима отладки
- воплощать в жизньrestartможно повторно отладить
- воплощать в жизньhelpДополнительную справочную информацию можно найти
Здесь мы выполняем команду справки, проверяем справочную информацию и распечатываем множество команд, которые можно выполнить в режиме отладки.Содержимое в квадратных скобках «[]» представляет собой аббревиатуру команды.
Среди часто используемых:
- шаг/следующий: выполнить строку
- продолжить: выполнить до точки останова
- список: показать исходный код
- print: напечатать какое-то значение
- перерыв: точка останова
Также в скрипте можно использоватьredis.breakpoint()
Добавьте динамические точки останова.
Вот простая демонстрация
Теперь я вставляю кодcount = count - 1
Удалите эту строку, сделайте бесконечный цикл программы, а затем отладьте ее.
Видно, что мы не нарушили точку останова, но программа все равно останавливается, потому что истекло время выполнения, и отладчик смоделировал точку останова, чтобы остановить программу. Как видно из исходного кода, таймаут здесь 5с.
/* Check if a timeout occurred. */
if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) {
mstime_t elapsed = mstime() - server.lua_time_start;
mstime_t timelimit = server.lua_time_limit ?
server.lua_time_limit : 5000;
if (elapsed >= timelimit) {
timeout = 1;
ldb.step = 1;
} else {
return; /* No timeout, ignore the COUNT event. */
}
}
Поскольку режим отладки Redis по умолчанию является асинхронным, данные в Redis не будут изменены после отладки.
Конечно, вы также можете выбрать выполнение в синхронном режиме, просто поставьте **—ldb в команде выполнения.параметр изменен на--ldb-sync-mode** сделает свое дело.
Интерпретация команды EVAL
Мы уже подробно представили команду EVAL в предыдущей статье, студенты, которые не знакомы с ней, могут просмотреть ее еще раз.Учебное пособие Redis Lua Script для средней школы (часть 1). Сегодня мы продолжаем исследовать команду EVAL в сочетании с исходным кодом.
В файле server.c мы знаем, что команда eval выполняет функцию evalCommand. Реализация этой функции находится в файле scripting.c.
Стек вызовов функций
evalCommand
(evalGenericCommandWithDebugging)
evalGenericCommand
lua_pcall //Lua函数
Функция evalCommand очень проста. Она просто определяет, находится ли он в режиме отладки. Если он находится в режиме отладки, вызовите функцию evalGenericCommandWithDebugging. Если нет, вызовите функцию evalGenericCommand напрямую.
В функции evalGenericCommand сначала определите правильность количества ключей
/* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
return;
if (numkeys > (c->argc - 3)) {
addReplyError(c,"Number of keys can't be greater than number of args");
return;
} else if (numkeys < 0) {
addReplyError(c,"Number of keys can't be negative");
return;
}
Затем проверьте, не находится ли скрипт уже в кеше, если нет, посчитайте контрольную сумму SHA1 скрипта, если он уже есть, преобразуйте контрольную сумму SHA1 в нижний регистр
/* We obtain the script SHA1, then check if this function is already
* defined into the Lua state */
funcname[0] = 'f';
funcname[1] = '_';
if (!evalsha) {
/* Hash the code if this is an EVAL call */
sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr));
} else {
/* We already have the SHA if it is a EVALSHA */
int j;
char *sha = c->argv[1]->ptr;
/* Convert to lowercase. We don't use tolower since the function
* managed to always show up in the profiler output consuming
* a non trivial amount of time. */
for (j = 0; j < 40; j++)
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
sha[j]+('a'-'A') : sha[j];
funcname[42] = '\0';
}
Здесь переменная funcname хранит контрольную сумму f_ +SHA1, Redis определит скрипт как функцию Lua, а funcname — это имя функции. Телом функции является сам скрипт.
sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
char funcname[43];
dictEntry *de;
funcname[0] = 'f';
funcname[1] = '_';
sha1hex(funcname+2,body->ptr,sdslen(body->ptr));
sds sha = sdsnewlen(funcname+2,40);
if ((de = dictFind(server.lua_scripts,sha)) != NULL) {
sdsfree(sha);
return dictGetKey(de);
}
sds funcdef = sdsempty();
funcdef = sdscat(funcdef,"function ");
funcdef = sdscatlen(funcdef,funcname,42);
funcdef = sdscatlen(funcdef,"() ",3);
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
funcdef = sdscatlen(funcdef,"\nend",4);
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
if (c != NULL) {
addReplyErrorFormat(c,
"Error compiling script (new function): %s\n",
lua_tostring(lua,-1));
}
lua_pop(lua,1);
sdsfree(sha);
sdsfree(funcdef);
return NULL;
}
sdsfree(funcdef);
if (lua_pcall(lua,0,0,0)) {
if (c != NULL) {
addReplyErrorFormat(c,"Error running script (new function): %s\n",
lua_tostring(lua,-1));
}
lua_pop(lua,1);
sdsfree(sha);
return NULL;
}
/* We also save a SHA1 -> Original script map in a dictionary
* so that we can replicate / write in the AOF all the
* EVALSHA commands as EVAL using the original script. */
int retval = dictAdd(server.lua_scripts,sha,body);
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
incrRefCount(body);
return sha;
}
Перед выполнением скрипта сохраните входящие параметры и выберите правильную базу данных.
/* Populate the argv and keys table accordingly to the arguments that
* EVAL received. */
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
/* Select the right DB in the context of the Lua client */
selectDb(server.lua_client,c->db->id);
Затем вам нужно настроить хуки.Автоматические точки останова при тайм-ауте выполнения скрипта, о которых мы упоминали ранее, и команда SCRPIT KILL для остановки скрипта и команда SHUTDOWN для остановки сервера реализованы через хуки.
/* Set a hook in order to be able to stop the script execution if it
* is running for too much time.
* We set the hook only if the time limit is enabled as the hook will
* make the Lua script execution slower.
*
* If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */
server.lua_caller = c;
server.lua_time_start = mstime();
server.lua_kill = 0;
if (server.lua_time_limit > 0 && ldb.active == 0) {
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
delhook = 1;
} else if (ldb.active) {
lua_sethook(server.lua,luaLdbLineHook,LUA_MASKLINE|LUA_MASKCOUNT,100000);
delhook = 1;
}
Теперь, когда все готово, вы можете напрямую вызвать функцию lua_pcall для выполнения скрипта. После выполнения также удаляем хук и сохраняем результат в буфер.
Выше описан весь процесс выполнения скрипта, после которого Redis также решит некоторые проблемы с синхронизацией скриптов. В предыдущей статье мы также представилиУчебное пособие Redis Lua Script для средней школы (часть 1)》
Суммировать
На этом серия сценариев Redis Lua завершена. Хотя статья закончена, обучение далеко не закончено. Если у вас есть какие-либо вопросы, добро пожаловать, чтобы обсудить со мной. учиться вместе, прогрессировать вместе
Студенты, интересующиеся Lua, могут прочитать "Программирование на Lua" и попытаться поддержать подлинную версию, если это возможно. Если вы хотите сначала увидеть качество, вы можете ответить на фоне моего официального аккаунта.LuaПолучить электронную книгу.