Redis — это хранилище значений ключей в памяти, обычно используемое для кэшей и аналогичных механизмов ускорения сетевых приложений.
Однако мы храним все наши данные в Redis, нашей основной базе данных.
В сети полно предупреждений и поучительных историй об использовании этого подхода.
Ходят ужасные истории о потере данных, исчерпании памяти или о том, что люди не могут эффективно управлять данными в Redis. Вы можете спросить: «О чем вы думаетеЭ» Итак, вот наша история о том, почему мы решили использовать Redis и как мы преодолели все эти проблемы.
Прежде всего, хотелось бы подчеркнуть, что большинству приложений не приходится обращать внимание на костыли, используемые для прохождения этого пути.
Это было важно для нашего варианта использования, но, возможно, это крайний случай.
Redis как хранилище данных
Редис быстрый.Когда я говорю «быстро», я имею в виду «быстро» с большой буквы.
По сути, это memcached с более умными типами данных, чем просто строковые значения.
Даже некоторые сложные операции, такие как пересечение множеств и выбор диапазона zset, выполняются невероятно быстро.
Есть все основания использовать Redis для быстро меняющихся, активно запрашиваемых данных.
Он довольно часто используется в качестве кэша, который можно перестроить, используя данные из резервной базы данных.
Это мощная замена memcached, обеспечивающая более продвинутое кэширование различных типов хранимых вами данных.
Как и в memcached, все находится в памяти.
Redis сохраняет данные на диск, но не синхронизирует их с тем, как вы их записываете.
Есть две причины, по которым Redis — отстой в качестве основного хранилища: - Вы вынуждены уместить все свои данные в памяти, и.
— Если сервер выйдет из строя между двумя синхронизациями с диском, вы потеряете все, что было в памяти.
Из-за этих двух проблем Redis занял компактную нишу временного кеша для данных, которыми можно пожертвовать, но не основного хранилища данных.
Обеспечение быстрого доступа к часто необходимым данным с возможностью восстановления при необходимости.
Недостатком использования более традиционных хранилищ Redis являются проблемы с производительностью этих хранилищ.
Вам придется пожертвовать производительностью, чтобы гарантировать сохранение данных на диске.
Вполне нормальное дело практически для любого приложения.
Вы можете получить отличную производительность чтения и «хорошую» производительность записи.
Я должен уточнить, что «хорошо» для меня вполне может быть безумно быстрым для большинства людей.
Достаточно сказать, что «хорошая» производительность записи должна удовлетворять большинству, кроме наиболее сильно нагруженных, приложений.
Я предполагаю, что вы могли бы выполнить запрос на запись в Redis, а затем сохранить его, используя реляционное хранилище, но тогда у вас все равно будет тот же риск сбоя Redis и потери данных очереди записи.
Что нам нужно?
Moot предлагается как совершенно бесплатный продукт. Поэтому нам необходимо иметь возможность справляться с большими нагрузками при очень небольшом количестве железа.Если нам нужна куча больших баз данных для форума, обслуживающего несколько миллионов пользователей в месяц, то остаться бесплатным сервисом не получится.
Поскольку мы хотим, чтобы Moot был одновременно бесплатным и неограниченным, нам пришлось оптимизировать его до предела.
Мы могли бы просто избежать этого, установив некоторые ограничения на бесплатные сервисы и взимая деньги за просмотр страниц или публикаций.
Не знаю, как вы, но мне вообще не нравятся продукты, которые бесплатны «пока вы не получите повышение».
Допустим, вы создали форум, а затем что-то на вашем сайте стало вирусным.
Внезапно вам выставят счет за превышение уровня бесплатного пользования.
И теперь то, что начиналось как развлечение, из-за внезапной популярности вашего блога о теории заговора, превращается в ужас будущего аккаунта.
Вы наказаны за свой успех.
Это то, чего нам хотелось бы избежать.
Мы также можем решить монетизировать рекламу и понести более высокие эксплуатационные расходы.
Однако это полностью противоречит нашим основным ценностям как бизнеса.
По нашему мнению, если кто-то будет размещать рекламу на вашем сайте, то это должны быть вы, а не мы.
Moot должен предлагаться без условий, ограничений и дополнений.
Учитывая все вышесказанное, необходимо добиться непревзойденной производительности постинга и чтения вне зависимости от инженерных сложностей.
Это основа нашей работоспособности.
Нашей первоначальной целью было обеспечить обработку всех вызовов API менее чем за 10 мс, даже при высокой нагрузке и даже при обработке больших сложных списков или поисков.
Redis, очевидно, может обеспечить нам такую производительность, но остаются две большие проблемы: как, черт возьми, мы можем использовать Redis, когда у нас могут быть сотни гигабайт данных, и что делать при сбоях сервера?
Что делать сейчас?
Так началось наше исследование способов проектирования в рамках этих ограничений.С самого начала у нас было четкое понимание целей Moot и наших ценностей как компании, поэтому нам повезло, что мы смогли подумать об этих функциях до написания первых строк кода.
Я считаю, что эти проблемы были бы непомерно трудными, если бы мы решили пойти по этому пути с большим количеством готового кода.
Все данные находятся в памяти.
Дерьмо.
Это более сложная из двух проблем.
Объем памяти, который может быть на одном компьютере, конечен.
Самое большое количество на EC2 — это сервер на 244 ГБ.
Хотя это все еще ограниченная сумма, для начала это довольно хороший предел.
К сожалению, это означает, что ваш 16-ядерный сервер будет использовать только одно ядро для Redis. А как насчет добавления подчиненного процесса Redis на каждое ядро? Тогда у вас останется по 15 ГБ памяти для каждого экземпляра.
Опять ерунда! Это плохое ограничение, если вы хотите получить максимальную мощность от сервера.
Этого недостаточно данных для хостинга.
Мы решили с самого начала спроектировать наше хранилище Redis таким образом, чтобы оно было разделено на множество кластеров Redis. Мы хэшируем и делим данные на блоки, содержащие все структуры, относящиеся к данному сегменту данных.
Данные с самого начала сильно секционированы, и при необходимости мы можем быстро и легко создавать новые блоки.
Чтобы разбить данные, мы храним таблицы хешей и адресов примерно так:
shards.map = hash:{ 'shard hash' : [shard id] } shards.[shard id] = hash:{ master : [master ip/port], slave0 : [slave 0 ip/port], slave1 : [slave 0 ip/port], .Когда данные поступают, мы вычисляем хэш на основе наших требований к подключению данных, затем проверяем в shards.map, был ли он назначен блоку, и если да, мы можем маршрутизировать вызовы к этому блоку.} shards.list = zset:{ shard1:[weight], shard2:[weight], .
}
Если хэш еще не присвоен блоку, мы создаем список доступных блоков, умножая их в зависимости от их веса.
Например, если вы это сделаете:
redis.call('zadd', 'shards.list', 1, 'shard1') redis.call('zadd', 'shards.list', 2, 'shard2') redis.call('zadd', 'shards.list', 1, 'shard3')Список будет выглядеть примерно так:
[shard1, shard2, shard2, shard3]После этого назначаем случайный блок из списка, сохраняем его на карту распределения и идем дальше.
Используя эту схему, мы можем легко контролировать, сколько данных попадает в блоки, добавлять новые блоки или даже исключать блоки из рассмотрения, если видим, что они заполнены.
На самом деле мы начали с сотен блоков, поэтому не нужно беспокоиться об ограничениях нагрузки на сервер и памяти.
Отдельные блоки остаются очень маленькими.
Один сервер содержит множество блоков баз данных Redis, и если эти блоки увеличиваются в размерах, мы можем легко разделить базы данных Redis на независимые экземпляры.
Допустим, у нас есть экземпляр Redis со 100 блоками, мы видим, что некоторые блоки увеличиваются в размерах, и разделяем Redis на два экземпляра по 50 блоков каждый.
Мы можем точно настроить веса, чтобы поддерживать распределение между блоками в реальном времени.
Самое сложное — выяснить, как именно вы сегментируете свои данные.
Это очень специфично и наш вариант сегментации — пожалуй, тема для отдельного поста.
Эта стратегия хранения должна быть заложена в приложение с самого начала.
Часто люди пытаются разделить данные, которые не предназначены для этого, и в этом проблема использования Redis. Поскольку мы четко знали, что ограничения памяти будут проблемой, мы смогли внедрить решение в самое ядро нашей системы управления данными еще до того, как написали хотя бы одну строку кода.
Сервер выходит из строя
Справиться с отказами оказалось, как ни странно, проще.У нас было 3 разные роли для кластера Redis: - Мастер, где происходили практически все операции записи, - Подчиненный, где проходили почти все чтения, — Хранитель, занимающийся хранением данных.
Главный и подчиненный устройства работают так же, как и все остальные в кластере Redis. В этом нет ничего интересного.
Новым мы сделали то, что в каждом кластере есть два сервера, которые используются в качестве хранителей.
Эти серверы: - Не принимайте никаких входящих соединений и не несут никакой нагрузки по запросам Redis, кроме простой репликации.
— Хранение AOF в каждом втором режиме - Ежечасный снимок RDB — Синхронизировать AOF и RDB в S3. Поскольку параметры производительности хранилища могут незначительно отличаться, один сервер-хранитель может обрабатывать разное количество блоков.
Мы просто запускаем один экземпляр для каждого блока, который необходимо сохранить.
Другими словами, нет необходимости в отношении 1 к 1 между блоками и серверами с ролью хранителя.
У нас эти два сервера расположены в разных зонах доступности, так что даже если одна из зон выйдет из строя, у нас будет работающий, актуальный сервер-хранитель.
Таким образом, чтобы мы потеряли данные, нам понадобится довольно серьезный сбой в EC2, и даже тогда мы потеряем только около секунды данных.
Если вы рассматриваете сценарий сбоя сетевого подключения, при котором ведущее устройство может быть изолировано от ведомых устройств, его можно смягчить, проверив репликацию ведомых устройств (установите произвольное значение произвольного ключа и проверьте, обновил ли ведомое устройство данные).
мастер изолирован, прекращаем запись: Согласованность и Устойчивость к потере связи из-за доступности.
Redis Sentinel тоже мог бы помочь нам в этом, но Sentinel был выпущен после того, как мы реализовали большую часть системы.
Мы не исследовали, как Sentinel может вписаться в наше уравнение.
Конечный результат
В итоге нам удалось построить систему, которая под нагрузкой выполняет вызовы API примерно за 2 мс.Значение 2 мс соответствует обслуживанию самого тяжелого вызова API — вызова API инициализации.
Многие наши запросы обслуживаются гораздо быстрее (лайки, например, часто занимают 0,6-0,7 мс).
Мы можем выполнять 1000 запросов API в секунду на одном сервере API. И для создания страницы требуется один вызов API. Измерение включает в себя всю проверку данных, управление блоками, аутентификацию, управление сеансами, подключениями, сериализацию JSON и т. д. Серверы API, которые могут обрабатывать такого рода рабочие нагрузки, стоят всего 90 долларов в месяц, поэтому мы можем поддерживать и масштабировать довольно большие рабочие нагрузки за очень небольшие деньги.
Еще одним дополнительным преимуществом фрагментированных данных является то, что горизонтальное масштабирование тривиально.
Во многом это связано не только с ТЕМИ решениями для Redis. Есть еще несколько хитростей, позволяющих обеспечить эффективную работу системы при высокой параллельной нагрузке.
Одна из хитростей в том, что почти половина нашего кода написана на Lua и запускается непосредственно в Redis. Это еще одна вещь, которую людям обычно советуют не делать.
Что касается того, как и почему у нас есть тысячи строк кода Lua — ждите следующего поста об использовании Redis. Посмотрите на наши реальные результаты: мы запустили пару дней назад и получили хороший первоначальный всплеск.
Мы обслуживали 50 API-вызовов в секунду, а процессор нашего основного API-сервера (мы по-прежнему отправляем весь трафик на один) полностью простаивал.
Вот графики от нашего запуска до момента написания поста.
Во время пиковых нагрузок все тихо.
Вы можете заметить пару брызг, когда мы выпускали исправления, но в остальном шума нет. Более поздние пакеты соответствуют обновлениям системы, исправлениям и другим выполняемым системным работам.
Общая нагрузка также включает в себя повышенные затраты на ведение журнала, которые мы сделали во время первоначального бета-тестирования.
Уточнение: я называю сервер API измеряемым, поскольку наш сервер приложений и сервер Redis — это одно и то же.
Сервер API несет в себе как несколько блоков, так и приложение.
Идея заключалась в том, чтобы направить трафик на сервер, где в основном расположен этот блок, чтобы использовать сокеты unix для подключения к Redis. Это позволит избежать ненужного сетевого трафика, поэтому нет особой разницы между сервером приложений, главным и подчиненным Redis. Любой API-сервер может обработать любой запрос, мы просто уделяем гораздо более высокий приоритет главному серверу задействованного сегмента данных.
Все серверы являются серверами приложений, и все серверы являются ведущими для одних блоков и подчиненными для других.
вр; доктор Есть много причин не использовать Redis в качестве основного хранилища на жестком диске, но если по какой-то причине этого требует ваш вариант использования, вам придется начать с самого начала.
Вам следует спроектировать свои данные так, чтобы они были секционированы, и помнить о дополнительных расходах на выделенные серверы хранения.
ПОПРАВКА Я только что понял, что посчитал стоимость одного сервера и забыл добавить амортизированную единовременную стоимость зарезервированных экземпляров.
Должно быть 213 долларов.
Теги: #softcraft #NoSQL #redis #Анализ и проектирование систем #NoSQL
-
Планшеты Android, Дешевый Планшетный Пк
19 Oct, 24 -
Хайку R1/Альфа 1
19 Oct, 24 -
Преподаем И Изучаем Unity3D В Калининграде
19 Oct, 24 -
От Mtv До Lx.tv И Интернета
19 Oct, 24