Авторство статьи принадлежит нашему сотруднику Степану Карамышеву skob
Инвайта у него пока нет, потому выкладываю за него.
Далее повествование от его имени.
Данный текст не претендует на гениальность, описанные в нем методики не являются ни панацеей, ни тем более серебряной пулей. Просто в результате исследования, проведенного во время описанной ниже аварии, не было найдено ни одного источника ни на русском, ни на английском (ни, кажется, на немецком) языках, который давал бы подобную информацию. Учтите: информация предоставляется «как есть», ее использование без вдумчивого изучения может быть губительно!
Дедка за red'ку
Народная сказка
Случаи бывают разные. Иногда даже самые надежные системы могут дать сбой. Так и в этот раз, опустившись в кресло своего командного центра, я с ужасом и горечью обнаружил более ста сообщений в скайп-чате. Чтение по диагонали истории показало, что redis у наших клиентов вспух, ушел в себя и был убит при помощи OOM-killer'а. Естественно, корректно отложить append-only file не успел. Магический redis-check-aof --fix на файл радостно рапортовал о починке, попутно предлагая обрезать 8-гигабайтный файл до 900-мегабайтного.
— О, ужас! — кричали клиенты.
— Фиаско! — кричали клиенты.
— $#%![_***#%! — кричали клиенты.
В aof-файлах, если кто не знает, лежит последовательно выполненные команды redis.
По счастью, перед тем, как запускать --fix «на боевую», файл был скопирован. Параллельно было принято административное решение о том, что без починки файла мы не уйдем.
Первая же попытка загрузить демон с файлом показала «HQET command not found». Дальше начались игры разума. Файл был от греха прикопан повторно, после чего подвергнут жестокой вивисекции. Первым делом была открыта страница http://redis.io/commands, где доступны все возможные управляющие команды redis'а.
Уровень первый. Hurt me plenty.
yum install -y hexedit < — ставим hexedit
hexedit <aof-файл> < — открываем hexedit'ом файл
< — переходим в ascii-режим
</> < — переходим в поиск
HQET-> < — ищем искомую команду, правим
<ctrl-X> < — выходим из hexedit
После исправления первой волны команд (HQET, HSAT, ZASD), на файл был повторно натравлен redis-check-aof. Результаты выполнения привели к
Уровню второму. Ultra-Violence.
Теперь файл требовал внимания по поводу:
0x308a7042: Expected prefix 'rn', got: '0f0a'
hexedit <aof-файл> < — открываем hexedit'ом файл
< — переходим по адресу
<правим 0f0a на 0d0a> < — напоминаю, 0d0a это и есть rn в хексе.
Вроде стало еще легче, но очередной прогон redis-check-aof начал рассказывать уже о
Уровне третьем. Nightmare!
0x 308a7046: Expected prefix '*', got: '$'
Были скачаны исходники редиса, пересобран redis-check-aof с расширенным логированием. В моем случае, я предпочел вывести последовательность символов _после_ проблемного места для более точной идентификации, получив что-то типа:
'34' 'D' 'A' '0'
Делается это логированием не только buf[0], но и либо нескольких символов после, либо всего buf.
Дополнительно обращу внимание, что в hexedit строка из примера выше будет представлена как '34 0D 0A 00', т.е. двумя символами с лидирующим нулем.
Далее необходимо очень внимательно читать ASCII _перед_ проблемным местом.
Если разбирать на примере, приведенном выше, то в ASCII увидим:
HSET..$16..{u204237826932}g..$3..150..$1..1..*6..$4..HSET..$16..{u204237826932}g..$3
..139..$1..1..*4..$4..
далее, внимательнейшим образом нами были изучены исходники redis, особенно следующий кусок:
if (buf[0] != '*') goto fmterr;
argc = atoi(buf+1);
if (argc < 1) goto fmterr;
argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
if (buf[0] != '$') goto fmterr;
len = strtol(buf+1,NULL,10);
argsds = sdsnewlen(NULL,len);
if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
argv[j] = createObject(REDIS_STRING,argsds);
if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
}
и в какой-то момент мы пришли к пониманию, что строка выше:
HSET..$16..{u204237826932}g..$3..150..$1..1..*6..$4..HSET..$16..{u204237826932}g..$3
..139..$1..1..*4..$4..
будет нормально съедена проверкой, если ее исправить на:
HSET..$16..{u204237826932}g..$3..150..$1..1..*4..$4..HSET..$16..{u204237826932}g..$3
..139..$1..1..*4..$4..
для ленивых — разница в замене 6 на 4 перед вторым HSET, что дало нам изменение длины строки аргументов в коде выше, что позволило пройти участок «goto fmterr» и спокойно пройти дальше.
В итоге, файл был заправлен в ручном режиме, redis запущен, клиенты успокоены, проект возвращен к работе, репутация чуть подсушена, бекап файла перенастроен и перепроверен. На всякий случай дважды.
Автор: akhaustov