Учебное пособие Redis Lua Scripting University

Redis
Учебное пособие Redis Lua Scripting University

Мы познакомили вас с основами, связанными с 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

Страница покажет некоторую справочную информацию и войдет в режим отладки.

lua_debug_help

Вы можете видеть, что страница справки сообщает нам

  • воплощать в жизньquitМожет выйти из режима отладки
  • воплощать в жизньrestartможно повторно отладить
  • воплощать в жизньhelpДополнительную справочную информацию можно найти

Здесь мы выполняем команду справки, проверяем справочную информацию и распечатываем множество команд, которые можно выполнить в режиме отладки.Содержимое в квадратных скобках «[]» представляет собой аббревиатуру команды.

Среди часто используемых:

  • шаг/следующий: выполнить строку
  • продолжить: выполнить до точки останова
  • список: показать исходный код
  • print: напечатать какое-то значение
  • перерыв: точка останова

Также в скрипте можно использоватьredis.breakpoint()Добавьте динамические точки останова.

Вот простая демонстрация

lua_debug_display

Теперь я вставляю кодcount = count - 1Удалите эту строку, сделайте бесконечный цикл программы, а затем отладьте ее.

lua_debug_dead_loop

Видно, что мы не нарушили точку останова, но программа все равно останавливается, потому что истекло время выполнения, и отладчик смоделировал точку останова, чтобы остановить программу. Как видно из исходного кода, таймаут здесь 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 не будут изменены после отладки.

lua_debug_asyn

Конечно, вы также можете выбрать выполнение в синхронном режиме, просто поставьте **—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Получить электронную книгу.