Площадка объявлений на смену авито. Пост №3

Привет!

Уже какое-то время я делаю большой проект, а именно площадку объявлений. Причём на не совсем обычных технологиях: я взял для этого kdb/kphp Павла Дурова, которые он вместе со своей командой создали для Вконтакте и теперь ещё и в Telegram messenger. Как оказалось эти технологии дают определённую свободу в плане создания больших и нагруженных проектов любой тематики.

Хотите узнать как устроен Вконтакте и Телеграм и почему им удаётся так быстро работать? Тогда проходите в пост!

Площадка объявлений на смену авито. Пост №3 ВКонтакте, Разработка, Дневник разработки, Технологии, Доска объявлений, Telegram, Длиннопост

1.Как так?

Итак, как удаётся телеграмму, №9 социальных сетей и №4 мессенджеров в мире, и Вконтакте, который №1 в России так быстро и стабильно работать

Сотни миллионов сообщений, десятки миллионов фото и видео каждый день - как со всем этим удаётся справляться? К тому же очень небольшой технической командой.

Ответ на этот вопрос: семейство "движков" баз данных KittenDB разработанное Павлом Дуровым, Николаем Дуровым и группой программистов-олимпиадников специально для Вконтакте.

Движки KittenDB создавались для супервысоких нагрузок RPS и хранения очень больших объёмов данных. Они обладают следующими характеристиками:

Высокая скорость записи на уровне memcache

Высокая скорость чтения на уровне memcache

Простое и сравнительно быстрое добавление серверов для разделения нагрузки

Простое восстановление после сбоя

Высокая плотность хранения данных на жестких дисках серверов

Сравнительная простота обслуживания

Итого, если простыми словами, то KittenDB это автомат Калашникова: быстро, просто, надёжно.

С момента как эти движки были выпущены в опенсорс в 2014 году прошло 9 лет, и до сих пор никто не сделал ничего подобного по уровню, хотя бы близко. И, конечно, за это время семейство KittenDB претерпело доработки и дополнения что во Вконтакте, что в Телеграмме, но заложенная суть отсаётся неизменной.

И я сейчас постараюсь рассказать почему они настолько круты. Постараюсь это сделать простым языком чтобы было понятно широкому кругу читателей.

2. Распределённая архитектура и rpcproxy

Когда вы пишите сообщение и нажимаете кнопку отправить (если кто не знает, то где вы читаете посты, нажимаете кнопки, ставите лайки - называется frontend) c вашего устройства отправляется запрос на сервер (в программу на сервере, которая называется backend). Опустим пока балансировщик нагрузки, которые делит поток запросов на несколько бэкендов. Так вот пришёл запрос на наш единственный бэкенд, воркер смотрит что нужно отдать в ответ на запрос и начинает запрашивать данные, асинхронно - прошу заметить (мы же на kphp всё-таки). Запрашиваются данные (или заносятся) в KittenDB через специальный движки rpcproxy и mcproxy, есть также dbproxy для разнесения таблиц mysql по разным серверам. Можно и напрямую к движкам KittenDB обращаться, гораздо удобнее это делать через *proxy. Воркеру чтобы получить данные нужно установить всего 1 соединение с rpcproxy. Сам rpcproxy (и mcproxy) держит связь со всеми кластерами всех имеющихся данных. Rpcproxy это развитие более раннего mcproxy который основан на memcache и использовал его формат запросов. Запросы имеют ограничение на длину, поэтому был создан протокол RPC-запросов, где такого сильного ограничения нет, но скорость соединения осталась такой же высокой.

3. Надежность транзакций

Движки KittenDB складывают получаемые данные в специальные файлы - бинлогами. Бинлог это файл в двоичном формате. Файл очень просто устроен: для каждого движка он содержит свой опознавательный код в начальной части файла, размер которой, как правило, всего 24 байта, остальное это данные. Все "транзакции" пишутся в бинлог потоком сверху, то есть данные не заменяются, а "падают сверху". Пример: для user_id: 115, username: Вася, затем кто-то меняет для user_id: 115 -> username = Петя, бинлог будет содержать обе операции, но итоговым значением будет последнее. Это даёт нам высочайшую скорость работы, надёжность, и простоту восстановления после сбоев, и как отдельный плюс, получается практически ACID (Atomicity - атомарность, consistency - консистентность, isolation - изолированность, durability - стойкость) - стандартный набор свойств, которые гарантируют надежность транзакции.

Как исторически сложилось, данные в бинлоги заносятся и запрашиваются по id - то есть для id: 2259 записать кусочек данных. Id может быть идентификатором пользователя, группы, списка чего-то или типа того.

4. Оптимизированное хранение данных в бинлоге

Оптимальное использование дисков сервера: поддержка символов utf сделана таким образом что если вы испоьлзуете 4-байтные символы, например смайлы (емоджи), то нет необходимости отводить по 4 байта для всех символов. То есть вы имеете возможность использовать 4-байтные символы когда это нужно, но в основном символы тратят 2 байта. В итоге значительно экономится место на жестких дисках, в отличии от, например, того же mysql.

Какого-то сжатия типа данных не используется, но движки формируют компактные снапшоты с самыми актуальными состояниями данных, например: для user_id: 115, username: Вася, затем кто-то меняет для user_id: 115 -> username = Петя, снапшот будет содержать только username = Петя. То есть если у нас есть 1000 кусков бинлога по 1гб, то снапшот может быть размером всего 10 частей по 1гб, но самых актуальных состояний.

5. Продуманное и удобное хранение бинлогов

Бинлог может быть неограниченного размера в сумме, но есть важный момент: он автоматически делится движком на куски по ~1гб и все эти пронумерованные куски образуют собой единый бинлог. В таких 1гб кусках бинлоги удобно администрировать, переносить, бэкапить, реплицировать, ну что ещё можно с ними сделать. Кстати, это настолько продуманный момент для больших бд, что, хотя и спустя столько лет, яндекс в своей база YBD, также использует это решение, которое они называют "таблетки". Но в решении KittenDB неоспоримый плюс: это не выглядит настолько монструозным и сложным в контроле.

6. Сделать кластер - легко.

Чтобы сделать кластер нужно определиться с количеством частей (инстансов или серверов - отдельных движков, работающих с собственным бинлогом и получающий запросы от прокси. при этом инстансы могут работать как на одном сервере, так и на разных - как позволяют мощности физических серверов), создать начальные бинлоги, настроить секцию конфига rpcproxy (или mcproxy), перезапустить rpcproxy (или mcproxy), и запустить все инстансы кластера. Например: мы хотим создать кластер мессенджера на 2 инстанса, тогда 1 будет принимать запросы с userid где остаток от деления на 2 будет 0, а второй - 1. Запросы с userid 10, 12, 14, 16... будут направленны на первый инстанс, а с userid 11, 13, 15, 17, 19... - на второй.

7. Почти гиперскалярная архитектура

Для любой базы KittenDB относительно несложно добавить новых вычислительных мощностей.

Если кластер из 2 инстансов достиг пределов нагрузки, то есть смысл из него сделать кластер, например, на 4 инстанса. Это делается простым делением бинлогов специальной утилитой KittenDB. Тогда бинлог который обслуживал userid где остаток от деления на 2 будет 0 поделится на 2 бинлога которые будут обслуживать userid где остаток от деления уже на 4 будет 0 (8,12,16,20...) и 2 (10, 14, 18...), а бинлог который обслуживал userid где остаток от деления на 2 будет 1 поделится на 2 бинлога которые будут обслуживать userid где остаток от деления уже на 4 будет 1 (9,13,17,21...) и 3 (7, 11, 15...). Эти бинлоги переносятся на новые физические сервера, запускаются инстансы, и вносятся коррекции в конфиг прокси.

8. Простое восстановление

В любой вычислительной системе случаются сбои, обычно это происходит по причине деградации и износа оборудования. Например для баз данных это сбои записи на жесткий диск. Обычно когда происходит такая ошибка, база данных падает, нередко с потерей важных данных. В KittenDB довольно элегантно решили эту проблему: при старте движок просматривает бинлог и если существует ошибка, то он он записывает в лог ошибок точную позицию в файле бинлога где есть ошибка. Бинлог можно попробовать исправить специальной утилитой KittenDB, а если это не принесло результата, то ошибка исправляется отрезанием хвоста бинлога с этой позиции. Да, это отрежет какие-то записи, но мы знаем что для user_id: 115 у нас есть несколько записей внизу бинлога, username: Вася, затем кто-то меняет для user_id: 115 -> username = Петя, а затем кто-то меняет для user_id: 115 -> username = Паша, допустим эта последняя запись была повреждена и была только что орезана, в итоге бинлог будет содержать username = Петя, если эти свежие данные ещё не успели попасть в снапшот, конечно же. В общем же, восстановление работоспособности отдельного инстанса (пока 1 инстанс кластера лежит, например для userid, остаток отделения которых на 100 равен 3 - это 103,203,303,403... - всего 1 процент пользователей не получает данных из этого кластера, все остальные пользователи получают данные из своих инстансов.) происходит относительно быстро и по понятной схеме: проверить бинлог утилитой, если не помогло - отрезать хвост на позиции из логов, запустить инстанс. Всё. Вот так просто.

9. Вот список основных движков KDB с киллерфичами

pmemcache - persistent memcache - это мамкеш, но с записью в бинлог и возможностью шардирования. То есть у вас может быть memcache на 100 серверов с единым пространством ключей. А ещё данные не пропадут если сервер выключить или перезагрузить: все данные будут доступны после запуска инстанса который эти данные обслуживал. Фантастически удобная штука. Можно применять для хранения сессий пользователей + необходимого набора данных, чтобы не ходить в базу для каждого запроса.

text - этот движок создан для хранения больших объёмов текстов: посты, сообщения, коментарии и т.п. Имеет свой опциональный http-порт и используется для диалогов, мессенджеров, стен, форумов, групп+

list - движок списков, например: подписчики группы, кто поставил лайк или репостнул - это всё списки.

queue - движок уведомлений - колокольчик. Имеет свой http-порт к которому подключаются js странацы пользователя для получения моментальных уведомлений о событиях.

search - движок для поиска всего что угодно. Имеет большое текстовое поле, "теги" - типа хештегов к постам, и дюжину чиловых полей для ранжирования (например, имеем объявление: продаю мерседес почти не ушатанный в хлам, 1999 года, пробег 564000км, 100т.р. в Москве, заносим "продаю мерседес почти не ушатанный в хлам" в текстовое поле, 1999 год, пробег 564000км, 100т.р и таймштамп размешения - в числовые поля, мерседес и Москва - в теги. Поисковый элемент готов!) Движок имеет затухание поисковых элементов по времени, то есть свежие элементы будут в выдаче выше.

storage - движок для хранения огромных объёмов файлов и фото. Хранит фото в бинлогах в кусках по 1 гб. ~20-30% экономичнее расходует дисковое пространство чем хранение файлов и фото россыпью в папках.

friends - движок для организации прав доступа к страницам, фото, постам, видео, и т.п. Ещё хранит списки "друзей" и "онлайн/был онлайн 15 минут назад"

bayes - движок позволяет выявлять и отфильтровывать спам. Даже не представляю что было бы без него.

hints - движок подсказок для поисковой строки, помогает подобрать фразу для поискового запроса по начальным символам.

logs - движок для сбора логов. Все логи можно сливать в одну большую кучу и делать к ней запросы.

stats - движок статистики: кто что посмотрел, откуда, какого пола и возраста, с какого устройста.

В общем-то этих движков достаточно чтобы сделать что-то эдакое, например вконтакте 14 года.

Вот схема как это примерно можно распределить по серверам:

Площадка объявлений на смену авито. Пост №3 ВКонтакте, Разработка, Дневник разработки, Технологии, Доска объявлений, Telegram, Длиннопост

Так что, нет недостатка в технологиях, и, создать что-нибудь классное, типа телеграмма или на худой конец - авито, может почти любой, кто хорошо учился и достаточно компетентен в ИТ. Знаете, это забавно, осознать что ты сейчас сидишь в собственном "телеграме" и переписываешься из двух браузеров... хоть и нету mtproto, ботов и зелёной подложки. Хотя, погодите, зелёная подложка вот - уже есть :)

Подписывайтесь, ставьте лайк - будет интересно!