«По нашему (общеизвестно ограниченному) опыту, Redis настолько быстр, что самая медленная часть поиска в кэше — это время, затрачиваемое на чтение и запись байтов в сеть» — stackoverflow.com
Могут ли базы данных работать с ними интересно?
Очень редко проект может заставить инженера быть взволнованным перспективой работы с базой данных, с которой инженер никогда не работал ранее, особенно когда это реляционная. Это в основном сводится к тому, что большинство из них являются неуклюжими чудовищами, которые мучительно медленны и заставляют нас морщиться при мысли о необходимости интегрировать их в наши приложения, не говоря уже о необходимости собирать грубые и чрезмерно сложные операторы SQL ,
Но что, если вам представили базу данных NoSQL, которая была бы не только быстрой [1], но и несложной, надежной, интересной в работе и просто выполняла свою работу (и действительно очень хорошо)? Тогда, может быть, вы просто простите, что взволнованы работой с базами данных. Шутки в сторону. Без шуток. Если ты мне не веришь, читай дальше.
Введите моего нового друга в мире разработки приложений — Redis .
Выбор Redis
Недавно завершенный проект для крупного телекоммуникационного потребителя вызвал серьезную потребность бизнеса в ежедневной обработке, хранении и обслуживании 17,2 миллиона записей и обновлении дополнительных 2,1 миллиона записей каждые 30 минут. Кроме того, эти данные необходимо было беспрепятственно синхронизировать на 10 узлах веб-сервера, одновременно продолжая асинхронно продолжать обслуживать любых клиентов (веб-уровень) без каких-либо значительных падений производительности. Наконец, эти данные затем необходимо было доставить с минимальной задержкой, т.е. с точностью до миллисекунд. Это не значит, что подвиг любой фантазии.
Мы обратились к Redis, хранилищу данных ключ-значение с открытым исходным кодом, чтобы помочь нам реализовать эти требования по нескольким причинам:
- Это быстро
- Это надежно.
- Имеет асинхронную репликацию.
- Это масштабируемо.
- Он имеет относительно зрелый набор API [2] для создания приложений.
- Это с открытым исходным кодом и BSD лицензированы.
Это просто отметило все коробки для нас.
Убер-основы
В Интернете есть много хороших статей [3], в которых детально рассматриваются Redis, а также разбираются в его внутренних механизмах. Эта запись в блоге не предназначена для того же. Вместо этого он призван дать краткое представление о Redis, о том, как он работает на базовом уровне (но включает более сложную тему репликации) и как мы использовали его для выполнения бизнес-требований этого конкретного проекта. В нем также описываются некоторые подводные камни, с которыми мы столкнулись, и мы надеемся, что их может обойти любой, кто задумывается об использовании Redis в любых будущих (или даже текущих) проектах.
Это не просто кеш
По своей сути Redis — это не что иное, как прославленная хэш-карта. И, как инженеры (я полагаю, что большинство читающей аудитории являются разработчиками программного обеспечения того или иного рода), мы все знаем, как работают хэш-карты и как они предназначены для скорости и эффективности. Redis хранит все свои данные в памяти, но это не просто кеш. Я повторю это — Redis хранит все свои данные в памяти, но это не просто кеш .
Причина несколько педантичного повторения двояка:
-
Redis хранит все свои данные в памяти — смешанный режим недоступен. Мы исследовали эту тему напрасно. Сохранение некоторых частей набора данных в памяти и других частей на диске просто невозможно в текущей версии. Это все или ничего, когда дело доходит до использования Redis, и на самом деле именно в этом заключается сила Redis — вы точно знаете, что вы получаете с этим. Без сюрпризов. Нет моментов WTF. Ничего не сделано сложным.
-
Но это не просто кеш — Redis обладает способностью моделировать структуры данных, например списки, очереди, отсортированные наборы и т. Д. Также можно изменить значение, если оно было «SET». Наконец, он может быть сохранен и восстановлен с диска, что делает его подходящим кандидатом для любого проекта, который требует такого типа функциональности (то есть постоянства).
Ключи и ценности
По сути, Redis работает только с «Ключами» и «Значениями», как и любая другая структура карты. Вы устанавливаете ключ с его значением, вы запрашиваете значение обратно, используя ключ, и все готово. Легко. Следующего очень простого примера должно быть достаточно для демонстрации работы Redis на самом примитивном уровне:
redis 127.0.0.1:6379> set mykey somevalue OK redis 127.0.0.1:6379> get mykey "somevalue" redis 127.0.0.1:6379> set mykey “a new value” OK redis 127.0.0.1:6379> get mykey "a new value" redis 127.0.0.1:6379> del mykey OK redis 127.0.0.1:6379> get mykey (nil)
Redis & Enterprise Solutions
Когда мы немного покопались в Интернете, мы были рады (и взволнованы), обнаружив, что Redis, похоже, зацепил себя некоторыми крупными игроками в отрасли, которые используют его для своих корпоративных решений. Некоторые из самых больших включают [4]:
- GitHub
- Переполнение стека
- щебет
- Tumblr
- logstash
Redis явно подходит для идеального хранилища данных для любой архитектуры предприятия. Как уже упоминалось, Redis является масштабируемым и предлагает надежные функции репликации / резервирования прямо из коробки. Но именно скорость, с которой он работает и выполняет, отделяет его от всех остальных. Это не имеет себе равных.
С самого начала проекта команда четко осознавала, что решение должно обрабатывать огромное количество пропускной способности, генерируемой ~ 50 клиентскими сайтами с высоким профилем / трафиком, одновременно выполняя обновления набора данных без влияния на них.
В качестве дополнительного уровня сложности (всегда есть «дополнительный уровень сложности»), он также должен был справиться с « флеш и толчок » этих 17,2 миллионов записей каждый день, что означает удаление всех данных ( «заподлицо») и восстановить его с нуля («толчок»). Очистка базы данных и восстановление ее с таким количеством обновлений должны были быть выполнены как можно быстрее по очевидным причинам. Этот относительно [5] плавный процесс был реализован нами в среднем за 29 минут. Это примерно 590 тыс. Обновлений в минуту или почти 10 тыс. В секунду.
Однако, как я собираюсь показать, эта цифра не указывает на истинную скорость Redis. Это быстрее, чем это. На самом деле, это намного быстрее! Запустив некоторые тесты и используя встроенный инструмент для тестирования производительности, поставляемый с Redis, мы смогли оценить, что это массовое обновление могло быть выполнено примерно за 5 минут. Найдите минутку, чтобы эта фигура погрузилась. Да, вы правильно прочитали. Более 17 миллионов обновлений набора данных за 5 минут [6]. Сейчас мы говорим.
Мы запустили инструмент тестирования на одном из наших серверов со следующими параметрами:
- 99 параллельных клиентских подключений
- случайно сгенерированные ключи в диапазоне от 0 до 50K
- 1 миллион запросов
- полезная нагрузка 100 байт (средняя полезная нагрузка реальных данных)
redis-benchmark -h [removed] -c 99 -r 50000 -n 1000000 -d 100 ====== SET ====== 1000000 requests completed in 18.07 seconds 99 parallel clients 100 bytes payload keep alive: 1 0.23% <= 1 milliseconds 86.90% <= 2 milliseconds 98.71% <= 3 milliseconds 99.85% <= 4 milliseconds 100.00% <= 5 milliseconds 100.00% <= 5 milliseconds 55340.34 requests per second ====== GET ====== 1000000 requests completed in 11.90 seconds 99 parallel clients 100 bytes payload keep alive: 1 97.96% <= 1 milliseconds 99.69% <= 2 milliseconds 99.79% <= 3 milliseconds 99.96% <= 4 milliseconds 99.99% <= 5 milliseconds 100.00% <= 5 milliseconds84012.44 requests per second
Так почему же Redis не работает на таких скоростях для нашего приложения, т.е. ~ 55K запросов в секунду? Ну, ответ был действительно довольно прост.
К сожалению, проект имел некоторые ограничения в результате некоторых других бизнес-требований. Это означало, что данные, которые должны быть переданы на наши узлы Redis, должны быть сначала извлечены из базы данных Oracle. Это стало причиной существенного узкого места в процессе обновления. И поскольку приложение было основано на Java, и, таким образом, для извлечения этих данных использовался JDBC, это наносило ущерб нашему времени обновления. Однако нам не удалось устранить это ограничение, и нам пришлось рассчитывать на среднее время 29 минут для каждого обновления «сбрасывай и толкай».
Помимо ежедневного запуска, приложению также необходимо обновлять 2,1 миллиона записей (вставлять / обновлять и удалять) каждые 30 минут. Это обновление занимает в среднем 100 секунд, чтобы вытолкнуть весь стек (10 узлов) в процессе производства. [Хотя это все еще быстро (иш), я знаю, что эта цифра не складывается по сравнению с результатами тестов. Это потому, что мы намеренно не используем пакетные вставки или конвейерную обработку для этой части обновления. CSV-файл обрабатывается построчно, а затем каждая строка определяется как SET или DEL и выполняется в этой точной последовательности в соответствии с требованием проекта.]
В качестве небольшого примечания (к сожалению, это выходит за рамки данного блога), Redis в настоящее время выходит в облако и предлагается в качестве корпоративного решения от растущего числа поставщиков, например Redis Cloud . Они четко осознали его потенциал, и Redis, похоже, предназначен для величия NoSQL.
копирование
Одним из требований проекта (и, безусловно, самым сложным) была бесшовная репликация набора данных на всех 10 узлах. Несколько подходов были обсуждены и проанализированы для решения этого конкретного требования.
Наиболее очевидным решением было использование конфигурации кластера (master <-> slave) и использование встроенной функциональности асинхронной репликации Redis. Хотя мы исследовали репликацию в некоторых деталях, мы изначально не хотели идти по этому пути по двум основным причинам:
-
Предыдущий опыт работы с другими конфигурациями кластеров баз данных подорвал нашу уверенность при работе в среде такого типа. Они всегда оказывались головной болью при настройке и обслуживании, и решение таких проблем, как коррумпированные мастера, всегда было проблемой.
-
Мы хотели сохранить горизонтальную масштабируемость, но введя кластерную конфигурацию, которая отрицательно сказалась бы на этой гибкости.
Как мы скоро выяснили, не доверяя Redis и не используя то, что он может предложить, мы собирались выстрелить себе в ногу.
Репликация Take 1 — Fake It Используя API (неправильный путь)
Наша первая попытка реплицировать данные по всем 10 узлам была (положительно отражаясь на ней) интересным опытом обучения. У нас возникла идея, что мы могли бы как-то реализовать нашу собственную конфигурацию кластера «на лету», используя API и подключившись к команде «SLAVEOF». Общий рабочий процесс будет:
- Выполните обновления только на одном из узлов (неважно, какой из них, но для краткости, скажем, это был узел 01)
- После обновления узла 01 каждый из 9 других узлов будет переброшен (по одному за раз), чтобы быть подчиненными узла 01, таким образом, заставляя узел 01 действовать как суррогатный мастер на время обновления.
- Когда репликация была завершена на ведомом устройстве, она затем была бы перевернута назад как ведущая, и тот же процесс был выполнен на следующем узле в стеке и так далее.
Следуя этому подходу, мы устраняем необходимость в реальной конфигурации кластера и сохраняем горизонтальную масштабируемость. Однако мы сразу же упали головой в некоторые ловушки [7]:
-
Когда между ведущим и ведомым устанавливается связь, между ними выполняется полная синхронизация. Это означает, что полный набор данных отправляется по сети ведомому устройству. И мы вызывали это каждые 30 минут ( мы только что наткнулись на яму, и мы в свободном падении ).
-
Обновление 2,1 миллиона записей занимало где-то 25-30 минут, тогда как для этого требовалось лишь часть этого времени. И это было без узкого места Oracle, как в случае с ежедневным обновлением (мы читали прямо из файла и передавали непосредственно в Redis). Наша попытка репликации «на лету» начинала давать сбои, и начали появляться трещины ( сейчас мы несемся на полной скорости к дну ямы ).
-
Когда устанавливается связь между ведущим и ведомым, главный разветвляется и выполняет фоновое (асинхронное) сохранение своих данных на диск. Наш набор данных набрал около 1,8 г, и его более или менее непрерывно заставляли записывать на диск в результате точки, непосредственно предшествующей этой ( сейчас мы достигли дна ямы со скоростью миллиард миль в час и распался в небольшой слой пыли ).
Излишне говорить, что после первоначального тестирования и просмотра результатов этот первый подход был отменен, и мы дали клятву, о которой больше никогда не будет сказано (за исключением записи об этом в этом сообщении в блоге).
Тиражирование дубль 2 — прыжок веры (правильный путь)
Take 1 считался несчастным провалом от нашего имени и ни в коем случае не по вине Redis. Redis сделал именно то, что сказал, но мы нагло злоупотребили его функциональностью и мощностью. Итак, мы вернулись к чертежной доске, и после бесчисленных обсуждений было окончательно решено, что нам следует поверить и поверить в функциональность репликации Redis.
Мы сразу же приступили к тестированию правильной конфигурации кластера, что было очень просто:
-
Мы добавили только 1 строку конфигурации в каждый из файлов конфигурации Redis для всех узлов, которые мы хотели быть ведомыми (т.е. «slaveof node01»).
-
Затаив дыхание, мы провели наш первый тест и отправили 17,2 миллиона записей на узел 01 и отслеживали, как Redis обрабатывает репликацию с этим томом. Это не пропустило ни секунды. Мы несколько раз пытались сломать его и запутать. Мы добавили неожиданные сценарии, которые, как мы думали, могут привести к его падению. Но каждый раз Редис просто игнорировал наши попытки и смеялся нам в лицо. Это оказалось пуленепробиваемым.
Это было похоже на поэзию в движении. Так элегантно. Так быстро. Так легко .
Когда мы более внимательно посмотрели на то, что на самом деле происходило во время процесса репликации (с помощью команды MONITOR), было легко увидеть, как Redis собирается во время репликации. Ведущий для каждого полученного запроса просто перенаправляет этот запрос всем подключенным ведомым. Например, если ведущий принимает команды 100K SET, мы можем видеть, что те же самые команды 100K перенаправляются всем подчиненным и обрабатываются ими.
В итоге мы остановились на использовании конфигурации кластера на самом базовом уровне. То есть только один хозяин и девять рабов. Тем не менее, ничто не мешает вашему дизайну войти в более сложную структуру, такую как структура графа, то есть рабов рабов. Кроме того, мы также изучили параметры репликации с точки зрения их настройки / изменения, но настройки по умолчанию работали для нас совершенно нормально.
Pipelining
Еще одна важная тема, которую нужно быстро затронуть, — это «конвейерная обработка», важная функция Redis, которую нужно понимать и осознавать. Хотя этот метод существует уже некоторое время и используется в других отраслях промышленности, он все еще остается (на удивление) неизвестным для многих инженеров. Мы решили использовать конвейеризацию в нашем проекте, и это оказалось мудрым шагом, так как это значительно улучшило нашу производительность.
Документация Redis подробно описывает конвейеризацию, и вы можете прочитать множество более подробных статей по этой теме в Интернете. Но на очень высоком уровне:
-
Redis — это TCP-сервер, использующий базовый протокол запросов / ответов. Другими словами, (без включенной конвейеризации), когда клиент отправляет запрос на сервер, он читает данные из сокета и ожидает ответа сервера, например, «ОК», чтобы подтвердить, что команда была обработана. Сервер обрабатывает запрос и отправляет ответ обратно. Должно быть очевидно, что это приведет к значительному снижению производительности.
-
При включенной конвейерной обработке можно отправлять пакет команд на сервер и не заставлять его ждать, пока клиент прочитает ответы, что позволяет ему продолжать обслуживать входящие команды. Вместо этого все ответы читаются за один шаг.
Благодаря такому подходу мы смогли значительно сократить время обработки Redis. Единственным недостатком конвейерной обработки является то, что если вы заинтересованы в ответе от сервера (т. Е. Убедитесь, что все команды были выполнены успешно), вам потребуется написать некоторый шаблонный код для перебора списка ответов и объединения запросов, которые были отправлены на ответы, которые были получены. Это было несколько сложно сделать.
Хотя мы потратили время на написание кода для обработки этих массовых ответов, мы заметили, однако, что Redis ни разу не удалось обработать запрос, который мы ему отправили. Ни в тестировании, ни в производстве мы никогда не сталкивались с неудачными командами, такими как SET, DEL, FLUSHDB и т. Д. Redis, похоже, работает каждый раз.
Здесь я хотел бы отметить, что использование протокола Redis для массовой вставки также считается жизнеспособным вариантом для ускорения времени вставки. Однако, наше исследование этой темы обнаружило, что большинство людей, которые использовали эту технику, вставляли ключи в миллиарды. Мы были только на миллионной территории. У нас действительно была массовая вставка в качестве следующего подхода к тестированию, если конвейер не был удовлетворительным с точки зрения времени. Но, как оказалось, мы были более чем довольны результатами конвейерной обработки.
Вывод
Redis — как тот супер надежный друг, которого все хотят иметь. Вы знаете один. Тот, который никогда не подводит тебя. Несложный. Тот, который никогда не лжет вам и всегда дает вам хороший совет. Тот, кто всегда вовремя приходит на вечеринку, отлично одет для этого случая, но в мгновение ока способен совершить ослепительный набор движений на танцполе на головокружительной скорости [8], но никогда не ломает пот это делает. И тот, который делает этот день за днем, никогда не жалуясь.
Redis тверд как скала. Это заставляет даже начинающих пользователей хорошо выглядеть. Он легко обрабатывает большие данные и, кажется, никогда не выкрикнет и не упадет, что бы вы на него ни бросали. Он делает именно то, что говорит на банке, а затем еще немного. Репликация очень проста в настройке и управлении, тогда как в большинстве других сред она является абсолютной головной болью.
Взволнован еще? Попробуйте, и вы будете.
Примечания:
1 Когда вы просматриваете Google Redis, 9 раз из 10 вы увидите, что его называют «невероятно быстрым» или «быстрым освещением» и так далее. Это. Но я пытался воздерживаться от использования таких чрезмерно прилагательных в этом блоге.
2 Jedis был Java API, который мы использовали.
3 См. Http://pauladamsmith.com/articles/redis-under-the-hood.html для отличного чтения.
4 Для получения полной информации о том, как эти компании используют Redis, см. Http://blog.togo.io/redisphere/redis-roundup-what-companies-use-redis/
5 Пока база данных перестраивается, существует короткий период, когда набор данных неполон и, следовательно, для некоторых ключей данные не могут быть возвращены
6 ((18,07 * 17,2)) / 60
7 Я использую «подводные камни» , но на самом деле все это поведение документированы на сайте Redis. Однако мы предпочитаем игнорировать это, полагая, что предлагаемое нами решение будет работать независимо.
8 Наконец-то поддались прилагательное для описания скорости Redis