Недавно была выпущена версия Redis 6.0.0 GA, которая является крупнейшим обновлением версии в истории Redis, включая множество обновлений, таких как кэширование на стороне клиента, ACL, многопоточный ввод-вывод и Redis Cluster Proxy.
Сегодня мы поговорим о необходимости, конкретном использовании, анализе принципа и реализации кэширования на стороне клиента по очереди.
Зачем вам кэширование на стороне клиента?
Все мы знаем, что основная цель использования Redis для кэширования данных — уменьшить доступ к базам данных, таким как MySQL, и обеспечить более высокую скорость доступа, ведь, как упоминалось в «Redis в действии», производительность Redis примерно такая же, как у обычных реляционных баз данных от 10 до 100 раз.
Поэтому, как показано на рисунке ниже, Redis используется для хранения горячих данных, и если Redis не будет поражен, он получит доступ к базе данных, что в большинстве случаев может удовлетворить требования к производительности.
Однако у Redis также есть свой верхний предел производительности, и доступ к Redis должен иметь определенные потери сетевого ввода-вывода и сериализации и десериализации. Поэтому кэши процессов часто вводятся для локального хранения самых горячих данных для дальнейшего ускорения доступа.
Как показано на рисунке выше, кэши процессов, такие как Guava Cache, используются в качестве кеша первого уровня, а Redis — в качестве кеша второго уровня:
- Перейдите к Guava Cache, чтобы сначала запросить данные, и вернуть их напрямую, если они попадут.
- Если в Guava Cache есть промах, то перейдите к Redis для запроса, если он попадет, верните данные и задайте эти данные в Guava Cache.
- Если Redis тоже промахивается, вы можете делать запросы только в MySQL, а затем поочередно устанавливать данные в Redis и Guava Cache.
При использовании только распределенного кеша Redis при обновлении данных приложение может напрямую аннулировать соответствующий кеш в Redis после обновления данных в MySQL для обеспечения согласованности данных.
Согласованность данных внутрипроцессного кеша сталкивается с более серьезными проблемами, чем распределенный кеш. Когда данные обновляются, как уведомить другие процессы об обновлении их собственных кешей?
Если мы будем следовать идее распределенного кэширования, мы можем установить очень короткое время инвалидации кеша, чтобы не было необходимости реализовывать сложный механизм уведомления.
Однако данные в разных процессах по-прежнему будут сталкиваться с проблемой несогласованности, а время аннулирования кеша разных процессов неодинаково.Один и тот же запрос к разным процессам может привести к повторным фантомным чтениям.
Бен предложил решение на RedisConf18 (ссылки на видео и PPT в конце статьи), через Redis Pub/Sub можно уведомить другие кеши процессов об удалении этого кеша. Если Redis зависает или механизм подписки ненадежен, вы все равно можете сделать практический результат, полагаясь на настройку тайм-аута.
Antirez (автор Redis) тоже решил поддерживать кэширование на стороне клиента в Redis Server после того, как выслушал предложение Бена, потому что вышеперечисленные проблемы лучше решаются с участием сервера.
Введение и демонстрация функций
Давайте воспользуемся Docker для установки Redis 6.0.1, а затем воспользуемся telnet, чтобы кратко продемонстрировать возможности кэширования Redis 6.0 на стороне клиента. Все связанные функции показаны на рисунке ниже: нормальный режим и широковещательный режим с использованием версии протокола RESP3, а также режим пересылки с использованием версии протокола RESP2. Сначала рассмотрим обычный режим.
нормальный режим
Сначала используйте redis-cli, чтобы установить значение кеша test=111, используйте telnet для подключения к Redis, а затем отправьте hello 3, чтобы включить протокол RESP3.
[root@VM_0_3_centos ~]# telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello 3
// telnet 输出结果格式化标准化后如下,否则换行太多并且是 RESP3 格式,不需要了解格式。
> HELLO 3
1# "server" => "redis"
2# "version" => "6.0.1"
3# "proto" => (integer) 3
4# "id" => (integer) 10
5# "mode" => "standalone"
6# "role" => "master"
7# "modules" => (empty array)
Обратите внимание,Сервер Redis будет отслеживать только значение ключа команды только для чтения, полученной клиентом в течение жизненного цикла соединения..Клиент Redis по умолчанию не включает режим отслеживания, вам нужно использовать команду для открытия, а затем вы должны сначала получить значение test, чтобы сервер Redis записал его.
client tracking on
+OK
get test
$3
111
Сервер Redis уведомляет этих клиентов, когда ключи изменяются или исключаются из-за истечения срока действия и политик maxmemory.. Мы просто обновляем значение test здесь, и telnet получит следующее уведомление
>2 // RESP3 中的 PUSH 类型,标志为 > 符号
$10
invalidate
*1
$4
test
Если вы снова обновите тестовое значение, на этот раз telnet больше не будет получать сообщение о недействительности. Если telnet снова не выполнит операцию получения, повторно отслеживая соответствующее значение ключа.
То есть информация отслеживания клиента, записанная сервером Redis, вступает в силу только один раз, и она будет удалена после отправки недопустимого сообщения.Только в следующий раз, когда клиент снова выполнит команду только для чтения для отслеживания, следующее сообщение будет сделано уведомление..
Команда для отмены отслеживания показана ниже.
client tracking off
+OK
режим трансляции
Redis также предоставляет широковещательный режим (BCAST), который является еще одной реализацией кэширования на стороне клиента. этим способомСервер Redis больше не потребляет слишком много памяти для хранения информации, но отправляет клиенту больше сообщений о недействительности..
Это компромисс между сервером, хранящим слишком много данных, потребляющим память, и клиентом, получающим слишком много сообщений, потребляющим пропускную способность сети.
// 已经 hello 3 开启 RESP3 协议,不然无法收到失效消息,下同
client tracking on bcast
+OK
// 此时设置 key 为 a 的键值,收到如下消息。
>2
$10
invalidate
*1
$1
a
Если вы не хотите получать сообщения о недействительности для всех значений ключа, вы можете ограничить префикс ключа.Следующая команда означает, что нужно обращать внимание только на сообщения значения ключа с префиксом test. Вообще говоря, кеш-ключ предприятия имеет единый префикс в зависимости от бизнеса, поэтому эта функция очень удобна.
client tracking on bcast prefix test
В отличие от правила, согласно которому ключ должен быть извлечен один раз в обычном режиме,В широковещательном режиме, пока ключ изменяется или удаляется, клиент, который соответствует правилам, получит сообщение о недействительности, и его можно получить несколько раз.
По сравнению с обычным режимом, хотя сохраняется меньше данных, он будет потреблять определенное количество ресурсов ЦП из-за необходимости соответствия правилам префикса.Будьте осторожны, чтобы не использовать слишком длинные префиксы.
режим переадресации
В приведенных выше операциях клиент должен сначала открыть RESP3.Redis предоставляет режим пересылки (перенаправления) для совместимости с протоколом RESP2.Вместо использования RESP3 для изначальной поддержки PUSH-сообщений сообщение передается другому клиенту через Pub. /Sub Конкретный процесс Как показано ниже.
Здесь требуются два телнета, на один из которых нужно подписаться_redis_:invalidate
канал. Затем другой телнет включает режим перенаправления и указывает отправить сообщение об аннулировании первому телнету через канал подписки.
# telent B
client id
:368
subscribe _redis_:invalidate
# telnet A,开启 track 并指定转发给 B
client tracking on bcast redirect 368
# telent B 此时有键值被修改,收到 __redis__:invalidate 信道的消息
message
$20
__redis__:invalidate
*1
$1
a
Вы обнаружите, что режим пересылки очень похож на механизм обновления в многоуровневом кэше, упомянутом в начале статьи, за исключением того, что в этой схеме бизнес-система отправляет уведомление о сообщении после изменения ключа, а здесь Redis сервер отправляет сообщения вместо уведомления бизнес-системы.
Варианты OPTIN и OPTOUT
Используйте OPTIN, чтобы дополнительно включить отслеживание. Отслеживаться будет только ключ следующей команды только для чтения после отправки client caching yes (команда CACHING в документе Redis, но в эксперименте она оказалась недействительной), в противном случае ключи других команд только для чтения будут не отслеживаться.
client tracking on optin
client caching yes
get a
get b
// 此时修改 a 和 b 的值,发现只收到 a 的失效消息
>2
$10
invalidate
*1
$1
a
Параметр OPTOUT наоборот, вы можете отказаться от отслеживания. Ключ следующей команды только для чтения после отправки клиентского кэширования не будет отслеживаться, а другие команды только для чтения будут отслеживаться.
OPTIN и OPTOUT предназначены для режима без BCAST, то есть только после отправки команды только для чтения для ключа соответствующий ключ будет отслеживаться. Режим BCAST заключается в том, что независимо от того, отправляете ли вы команду только для чтения для ключа или нет, только если Redis изменит ключ, он отправит соответствующее сообщение о недействительности ключа (сопоставление префикса).
опция NOLOOP
По умолчанию сообщение об аннулировании отправляется всем необходимым клиентам Redis, но в некоторых случаях клиенту, который запускает сообщение об аннулировании, т. е. клиенту, обновившему ключ, не требуется получать сообщение.
Установите NOLOOP, чтобы избежать этой ситуации, и клиенты, обновившие ключ, больше не будут получать сообщения.Эта опция работает как в обычном режиме, так и в широковещательном режиме.
Максимальное ограничение отслеживания tracking_table_max_keys
Как видно из вышеизложенного, большое количество отслеживаемых ключей и клиентской информации необходимо хранить в обычном режиме (конкретные хранимые данные будут объяснены ниже), поэтому, когда 10 000 клиентов используют этот режим для обработки миллионов ключей, потребляют много памяти, поэтому Redis вводит конфигурацию tracking_table_max_keys, по умолчанию — нет, без ограничений.
При отслеживании нового ключа, если количество отслеживаемых в настоящее время ключей больше, чем tracking_table_max_keys, ранее отслеженный ключ будет случайным образом удален, а соответствующему клиенту будет отправлено сообщение о недействительности.
Принцип и реализация исходного кода
Принцип нормального режима
Мы также сначала объясним принцип работы в обычном режиме. Сервер Redis использует TrackingTable для хранения клиентских данных в обычном режиме. Его тип данных — дерево счисления.
Основанное дерево — это дерево поиска с несколькими разветвлениями для поиска разреженных длинных целочисленных данных. Оно может быстро выполнить сопоставление и сэкономить место. Обычно оно используется для решения конфликтов хэшей и проблем с размером хэш-таблицы. Оно используется для управления памятью Linux.
Redis использует его для храненияключевой указательа такжеID клиентакартографические отношения. Потому что указатель на ключевой объект — это адрес памяти, то есть длинные целочисленные данные. Соответствующая операция клиентского кеша заключается в добавлении, удалении, изменении и проверке данных:
- Когда клиент с включенной функцией отслеживания получает определенное значение ключа, Redis вызывает
enableTracking
Метод использует дерево мощности для записи отношения отображения между ключом и clientId. - Когда ключ изменяется или удаляется, Redis вызывает
trackingInvalidateKey
Метод находит все соответствующие идентификаторы клиентов из TrackingTable по ключу, а затем вызываетsendTrackingMessage
Метод отправляет сообщения об аннулировании этим клиентам (он проверяет, включены ли флаги, связанные с CLIENT_TRACKING, и включен ли NOLOOP). - После отправки сообщения об аннулировании, согласнозначение указателя ключаУдалите отношение сопоставления из TrackingTable.
- После того, как клиент отключает функцию отслеживания, поскольку для удаления требуется много операций, Redis использует метод ленивого удаления, но удаляет только флаг клиента, связанный с CLIENT_TRACKING.
Принцип широковещательного режима
Широковещательный режим аналогичен обычному режиму, и Redis также используетPrefixTable
Сохраняет данные клиента в широковещательном режиме, в котором сохраняется отношение сопоставления между ** указателем строки префикса и (ключом, который необходимо уведомить, и идентификатором клиента) **. Самая большая разница между ним и широковещательным режимом заключается в том, что время фактической отправки сообщения о недействительности отличается:
- Когда клиент включает широковещательный режим, он
PrefixTable
Добавьте идентификатор клиента в список клиентов, соответствующий префиксу. - Когда ключ изменяется или удаляется, Redis вызывает
trackingInvalidateKey
метод,trackingInvalidateKey
метод, если найденPrefixTable
не пусто, звонитеtrackingRememberKeyToBroadcast
Пройдите все префиксы по очереди, если ключ соответствует правилу префикса, запишите вPrefixTable
соответствующее место. - Он будет вызываться в функции beforeSleep функции цикла обработки событий Redis.
trackingBroadcastInvalidationMessages
функция для фактической отправки сообщения.
обрабатывать максимальную отслеживающую кепку
Redis будет вызывать после выполнения каждой команды (метод processCommand)trackingLimitUsedSlots
Чтобы определить, требуется ли очистка:
- Определить, больше ли количество ключей в TrackingTable, чем tracking_table_max_keys;
- В течение определенного периода времени (не слишком долго, блокируя основной процесс) случайным образом выберите ключ из таблицы отслеживания для удаления, пока число не станет меньше или не истечет время.
Конкретный исходный код
Что касается исходного кода, в файле tracking.c мы рассматриваем здесь только наиболее важные из них.trackingInvalidateKey
функция иsendTrackingMessage
Функция, поймите, что эти две функции, режим вещания и связанные с ними функции, такие как обработка максимального ограничения отслеживания, аналогичны.
void trackingInvalidateKey(client *c, robj *keyobj) {
if (TrackingTable == NULL) return;
sds sdskey = keyobj->ptr;
// 省略,如果广播模式的记录基数树不为空,则先处理广播模式
// 1 根据键的指针去 TrackingTable 查找
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
if (ids == raxNotFound) return;
// 2 使用迭代器遍历
raxIterator ri;raxStart(&ri,ids);raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
// 3 根据 clientId 查找 client 实例
client *target = lookupClientByID(id);
// 4 如果未开启 track 或者是广播模式则跳过。
if (target == NULL ||
!(target->flags & CLIENT_TRACKING)||
target->flags & CLIENT_TRACKING_BCAST)
{ continue; }
// 5 如果开启了 NOLOOP 并且是导致key发生变化的client则跳过。
if (target->flags & CLIENT_TRACKING_NOLOOP &&
target == c)
{ continue; }
// 6 发送失效消息
sendTrackingMessage(target,sdskey,sdslen(sdskey),0);
}
// 7 减少数据统计,根据sdskey删除对应的记录
TrackingTableTotalItems -= raxSize(ids);
raxFree(ids);
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
}
Исходный код показан выше, метод trackingInvalidateKey в основном делает 7 вещей:
- По указателю ключа переходим в TrackingTable, чтобы найти список идентификаторов клиентов;
- Используйте итераторы для обхода списка;
- Найти экземпляр клиента на основе clientId;
- Пропустить, если для экземпляра клиента не включен режим отслеживания или вещания;
- Если в клиентском экземпляре включен NOLOOP и именно клиент вызывает изменение ключа, пропустите его;
- Вызовите метод sendTrackingMessage, чтобы отправить сообщение об аннулировании;
- Уменьшить статистику данных и удалить соответствующие записи в соответствии с sdskey
Давайте посмотрим, что на самом деле отправляет сообщениеsendTrackingMessage
функция, она в основном делает 6 вещей:
- Если client_tracking_redirection не пусто, режим переадресации включен;
- Найдите перенаправленный экземпляр клиента;
- Если клиент пересылки закрыт, исходный клиент должен быть уведомлен;
- Если клиент использует RESP3, отправьте PUSH-сообщение;
- Если это режим переадресации, на TrackingChannelName, который
_redis_:invalidate
Информация заголовка сообщения об ошибке, отправленного в канале; - Отправить ключ и другую информацию.
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
int using_redirection = 0;
// 1 如果 client_tracking_redirection 不为空,则开启了转发模式
if (c->client_tracking_redirection) {
// 2 找到转发的客户端实例
client *redir = lookupClientByID(c->client_tracking_redirection);
if (!redir) {
// 3 如果转发客户端关闭了,则必须通知原客户端
....
return;
}
c = redir;
using_redirection = 1;
}
if (c->resp > 2) {
// 4 如果是 RESP3 则发PUSH
addReplyPushLen(c,2);
addReplyBulkCBuffer(c,"invalidate",10);
} else if (using_redirection && c->flags & CLIENT_PUBSUB) {
// 5 转发模式,往 TrackingChannelName 信道中发送消息
addReplyPubsubMessage(c,TrackingChannelName,NULL);
} else {
return;
}
// 6 发送键等信息,和上边4,5操作连在一起的。
addReplyProto(c,keyname,keylen);
}
постскриптум
Я считаю, что маленькие друзья здесь немного устали, но, пожалуйста, ставьте лайки и комментируйте больше. В будущем мы узнаем о других особенностях Redis 6.0.0, пожалуйста, продолжайте обращать на это внимание.
Адрес личного блога, добро пожаловать на просмотр
Ссылаться на
- Redis.IO/темы/трагические…
- Специальности.Meituan.com/2017/03/17/…
- cloud.Tencent.com/developer/ ах…
- nuggets.capable/post/684490…
- www.kawabangga.com/posts/3590
- ppt Woohoo.slide share.net/Redis labs/day…
- видеовооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо