Чертов урок! Никогда не используйте эти команды Redis в продакшене.

Java

Эй, маленький черный брат недавно сделал что-то не так.

Это так.Некоторое время назад производственная транзакция компании Xiaoheige время от времени сообщала об ошибке.После некоторого расследования последняя причина заключалась в том, что время выполнения команды Redis истекло.

Но что вызывает недоумение, так это то, что производственная транзакция использует только простую команду Redis set, которая логически не может выполняться так медленно.

Так что именно вызывает эту проблему?

Чтобы выяснить эту проблему, мы проверили и проанализировали недавний медленный журнал Redis и, наконец, обнаружили, что команда требует больше времени.keys XX*

Увидев префикс клавиш, управляемых этой командой, Сяохэй понял, что это было приложение, за которое он отвечал. Но младший черный брат проверил, хотя его код не проявил инициативу использованияkeysкоманда, но базовая структура используется косвенно, поэтому сегодняшняя проблема.

проблема вызывает

Приложение, за которое отвечает Сяо Хей, является фоновым приложением управления.Среда Shiro используется для управления разрешениями.Поскольку существует несколько узлов, необходимо использовать распределенный сеанс, поэтому Redis используется здесь для хранения информации о сеансе.

Голос за кадром: Я не знаю о распределенной сессии, вы можете прочитать, что ранее писал Сяохэй.Назовите 4 распределенных согласованных метода реализации сеанса на одном дыхании, и интервью будет использовано ~

Поскольку Shiro не предоставляет компонент сеанса хранилища Redis напрямую, Сяо Хей должен использовать Github, компонент с открытым исходным кодом.shiro-redis.

Поскольку платформе Shiro необходимо периодически проверять, является ли сессия действительной, нижний уровень Shiro будет вызыватьSessionDAO#getActiveSessionsПолучить всю информацию о сеансе.

иshiro-redisпросто наследуйSessionDAOЭтот интерфейс, нижний слой использует keysКоманда для поиска всех магазинов RedisSessionключ.

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    }finally{
        jedis.close();
    }
    return keys;
}

Найдите причину проблемы, решение относительно простое, найдите решение на github, обновите егоshiro-redis до последней версии.

В этой версииshiro-redis использоватьscanкоманда вместо этогоkeys, тем самым устраняя проблему.

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();

    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor,params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        }while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    }finally{
        jedis.close();
    }
    return keys;

}

Хотя проблема была успешно решена, маленький черный брат все еще был немного озадачен.

Почему keysИнструкции заставляют другие команды выполняться медленнее?

Почему KeysКомандный запрос будет таким медленным?

Почему ScanЕсть проблема с командой?

Как Redis выполняет команды

Сначала давайте посмотрим на первый вопрос, почему keysИнструкции заставляют другие команды выполняться медленнее?

Чтобы ответить на этот вопрос, давайте сначала посмотрим, как клиент Redis выполняет команду:

С точки зрения клиента выполнение команды делится на три этапа:

  1. отправить команду
  2. Выполнение заказа
  3. вернуть результат

Но это только процесс, о котором думает клиент, а на самом деле одновременно может быть много клиентов, отправляющих команды в Redis, а мы все знаем, что Redis использует однопоточную модель.

Чтобы обрабатывать команды запросов всех клиентов одновременно, Redis использует внутренний метод очереди для постановки в очередь на выполнение.

Таким образом, клиенту на самом деле требуется четыре шага для выполнения команды:

  1. отправить команду
  2. очередь команд
  3. Выполнение заказа
  4. вернуть результат

Поскольку Redis выполняет команды в одном потоке, он может последовательно выполнять задачи только из очереди.

Пока скорость выполнения команд этого процесса слишком низкая, другие задачи в очереди вынуждены ждать.Внешним клиентам Redis кажется заблокированным и не получил ответа.

Поэтому не используйте процесс Redis для выполнения инструкций, которые должны выполняться в течение длительного времени, что может привести к блокировке Redis и повлиять на выполнение других инструкций.

КЛЮЧИ принцип

Приступим к ответу на второй вопрос, почему KeysКомандный запрос будет таким медленным?

Прежде чем ответить на этот вопрос, вспомните базовую структуру хранилища Redis.

Не имеет значения, если вы не знаете своего друга, вы можете оглянуться на предыдущую статью Сяо Хэя ".Интервьюер Али: Вы знакомы с HashMap? Хорошо, давайте поговорим о словарях Redis!".

Здесь Xiaohei скопировал содержание предыдущей статьи.Нижний слой Redis использует структуру словаря, которая похожа на нижний слой Java HashMap.

keysКоманда должна вернуть все совпадения, соответствующие заданному шаблону.patternКлюч в Redis, чтобы добиться этого, Redis должен пройти по словарюht[0]Базовый массив хеш-таблицы, на этот раз сложностьO(N)(N — количество ключей в Redis).

Если количество ключей в Redis невелико, скорость выполнения все равно будет очень высокой. Когда количество ключей Redis постепенно увеличивается до миллионов, десятков миллионов или даже сотен миллионов, скорость выполнения будет очень низкой.

Ниже приведен эксперимент, проведенный Xiaohei локально с использованием сценария lua для добавления ключей 10W в Redis, а затем использованияkeysЗапросите все ключи, этот запрос будет заблокирован примерно на десять секунд.

eval "for i=1,100000  do redis.call('set',i,i+1) end" 0

Здесь Xiaohei использует Docker для развертывания Redis, и производительность может быть немного хуже.

принцип СКАН

Наконец, давайте рассмотрим третий вопрос, почему scanЕсть ли проблема с командой?

Это потому чтоscanКомандование использует своего рода черную технологию-итератор на основе курсора.

каждый звонокscanКоманда Redis вернет пользователю новый курсор и определенное количество ключей. В следующий раз, когда вы захотите продолжить получение оставшихся ключей, вам нужно передать курсор команде сканирования, чтобы продолжить предыдущий процесс итерации.

просто говоря,scanКоманда запрашивает Redis, используя разбиение на страницы.

Ниже приведен пример итеративного процесса для команды сканирования:

scanКоманда использует метод курсора, чтобы тонко разделить полный запрос на несколько раз, чтобы уменьшить сложность запроса.

Несмотря на то чтоscanСложность командного времени иkeysто же самое, обаO(N), Однако из-заscanКоманде нужно вернуть небольшое количество ключей, поэтому скорость выполнения будет высокой.

Наконец, хотяscanкоманда для решенияkeysНедостаточно, но также вносит некоторые другие дефекты:

  • Один и тот же элемент может быть возвращен несколько раз, что требует от нашего приложения увеличения функции обработки повторяющихся элементов.
  • Если элемент добавляется в redis во время итерации или удаляется во время итерации, этот элемент будет возвращен, а может и не быть.

Вышеуказанные дефекты необходимо учитывать при разработке.

КромеscanКроме того, в Redis есть несколько других команд для инкрементной итерации:

  • sscan: используется для перебора ключей базы данных в текущей базе данных, используется для разрешенияsmembersМожет вызвать проблемы с блокировкой
  • hscanКоманда используется для перебора пар ключ-значение в хэш-ключе, используемом для разрешенияhgetallМогут возникнуть проблемы с блокировкой.
  • zscanКоманда : используется для перебора элементов в отсортированном наборе (включая членов элементов и оценки элементов), используемых для генерацииzrangeМогут возникнуть проблемы с блокировкой.

Суммировать

Redis использует один поток для выполнения команд операций, все клиенты отправляют команды, Redis теперь поставит их в очередь, а затем последовательно достанет соответствующие команды из очереди и выполнит их.

Если какая-либо задача выполняется слишком медленно, это повлияет на другие задачи в очереди, поэтому с точки зрения внешних клиентов, если ответ от Redis задерживается, он кажется заблокированным.

Так что не выполняйте его в производствеkeys,smembers,hgetall,zrangeЭтот вид инструкций, которые могут вызвать блокировку, если вам действительно нужно выполнить, вы можете использовать соответствующийscanПоследовательный обход команд может эффективно предотвратить проблемы с блокировкой.

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: программа для общения, ежедневный толчок галантерейных товаров. Если вас интересует мой рекомендуемый контент, вы также можете подписаться на мой блог:studyidea.cn