Большой объем данных Redis (десятки миллиардов) Ключевые требования к хранилищу и решения

Redis задняя часть Архитектура

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

Фон спроса

Для этого сценария приложения требуется кэш-память DMP.DMP должен управлять большим количеством данных сторонних идентификаторов, включая отношение сопоставления между каждым мультимедийным файлом cookie и его собственным файлом cookie (далее в совокупности именуемым супперидом), а также тегом заполнения супперид , идентификатор мобильного терминала (в основном называемый супперидом) — это метка заполнения idfa и imei), а также некоторые идентификаторы черного списка, ip и другие данные.

Нетрудно хранить сотни миллиардов записей в автономном режиме с помощью hdfs, но DMP также должен обеспечивать миллисекундный запрос в реальном времени. Поскольку идентификатор самого файла cookie нестабилен,Таким образом, поведение при просмотре многих реальных пользователей приведет к созданию большого количества новых файлов cookie. Только синхронизируя данные сопоставления во времени, можно достичь тега заполнения DMP. Невозможно получить более высокое попадание с помощью предварительного нагрева. , что сильно влияет на объем кэш-памяти..

После реальных испытаний,Для вышеперечисленных данных обычное хранение более 5 миллиардов записей kv требует более 1T памяти.Если требуется высокая доступность и многократные копии, потребление будет огромным.Кроме того, неравномерная длина kv также принесет много фрагментация памяти, что требует сверхбольших решений для хранения данных для решения вышеуказанных проблем.

2 Какие данные хранятся

Демографические теги в основном представляют собой файлы cookie, imei, idfa и соответствующие им пол (пол), возраст (возрастная группа), гео (регион) и т. д. Отношение сопоставления в основном представляет собой сопоставление медиафайлов cookie с супперидом. Ниже приведен пример хранилища данных:

  1. Идентификатор на стороне ПК:
媒体编号-媒体cookie=>supperid

supperid => { age=>年龄段编码,gender=>性别编码,geo=>地理位置编码 }
  1. Идентификатор устройства:
imei or idfa => { age=>年龄段编码,gender=>性别编码,geo=>地理位置编码 }

Очевидно, что данные ПК должны хранить два типа ключей => значение и ключ => хэш-карта, в то время как данные устройства должны хранить один тип

ключ => хэш-карта.

Три функции данных

  • Короткий ключ и короткое значение:
其中superid为21位数字:比如1605242015141689522;
imei为小写md5:比如2d131005dc0f37d362a5d97094103633;
idfa为大写带”-”md5:比如:51DFFC83-9541-4411-FA4F-356927E39D04;
  • Собственные файлы cookie СМИ различаются по длине;
  • Необходимость предоставления услуг для полных данных, supperid — это десятки миллиардов, медиа-мэппинг — сотни миллиардов, а мобильные идентификаторы — миллиарды;
  • Ежедневно генерируются миллиарды картографических отношений;
  • Горячие данные можно прогнозировать в более широком временном окне (осталось несколько стабильных файлов cookie);
  • Для текущих данных сопоставления горячие данные не могут быть предсказаны, и многие из них представляют собой недавно созданные файлы cookie;

4 Существующие технические проблемы

1)Разная длина может легко вызвать фрагментацию памяти.;

2)Из-за наличия большого количества указателей скорость расширения памяти относительно высока, обычно в 7 раз, что является распространенной проблемой при хранении в чистой памяти.;

3)Хотя популярность файлов cookie можно предсказать по поведению файлов cookie, по-прежнему каждый день генерируется много новых идентификаторов (процент более чувствителен и пока не будет разглашаться).;

4)Поскольку служба должна находиться в пределах 100 мс от общедоступной сетевой среды (задержка внутренней общедоступной сети составляет менее 60 мс), в принципе, недавно обновленные метки отображения и населения за день должны быть все в памяти, и запрос будет не падать на холодные данные бэкенда.;

5)С точки зрения бизнеса, в принципе, все данные хранятся не менее 35 дней и более.;

6)Память пока относительно дорогая, и обязательно нужно использовать десятки миллиардов ключей и даже сотни миллиардов решений для хранения!

5 решений

5.1 Стратегия ликвидации

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

Количество пользователей сети далеко от масштабов миллиардов, а id имеет определенный жизненный цикл и будет продолжать меняться. Таким образом, идентификатор, который мы храним, на самом деле недействителен. На самом деле логика внешнего интерфейса запроса представляет собой рекламное воздействие, которое связано с поведением человека, поэтому идентификатор будет иметь определенную степень повторения поведения идентификатора при доступе в определенное временное окно (может быть, кампания, половина месяц, несколько месяцев).

Перед инициализацией данных мы сначала используем hbase для агрегирования и дедупликации идентификаторов журналов, а также определяем диапазон TTL, который обычно составляет 35 дней, чтобы идентификаторы, которые не появлялись в течение последних 35 дней, можно было отсечь. Кроме того, в Redis установлено время истечения 35 дней, при доступе и попадании ключ будет обновлен для продления срока действия, а те, которые не появятся в течение 35 дней, будут устранены естественным образом. Это может быть эффективно для стабильных cookie или id.Доказано, что метод продления жизни более практичен для idfa и imei, а длительным накоплением можно добиться очень идеального попадания..

5.2 Уменьшить раздувание

Размер пространства хэш-таблицы и количество ключей определяют частоту конфликтов (или измеряется коэффициентом загрузки).В пределах разумного диапазона чем больше ключей, тем больше пространство хэш-таблицы и больше потребление памяти.Кроме того, большое количество указателей сами по себе являются длинными целыми числами, поэтому расширение памяти очень существенно.. Сначала поговорим о том, как уменьшить количество ключей.

Давайте сначала разберемся со структурой хранения. Мы ожидаем сохранить ключ1=>значение1 в Redis, затем мы можем сохранить его в соответствии со следующим процессом.Сначала используйте случайное значение хэша md5 (ключ) фиксированной длины в качестве ключа Redis, который мы называем BucketId.и сохранить ключ1=>значение1 в структуре хэш-карты, чтобы при запросе клиент мог вычислить хэш в соответствии с описанным выше процессом, чтобы запросить значение1.

Изменение процесса просто описывается как: get(key1) -> hget(md5(key1), key1) для получения value1.

Если мы предварительно вычислим,Если в пространстве BucketId могут конфликтовать многие ключи, можно считать, что к BucketId прикреплено несколько ключей. Например, под каждым BucketId в среднем по 10 ключей, тогда теоретически мы уменьшим количество ключей redis более чем на 90%.

Есть некоторые проблемы с реализацией, и вам нужно подумать о масштабе емкости, прежде чем использовать этот метод.md5, который мы обычно используем, представляет собой 32-битную шестнадцатеричную строку (шестнадцатеричные символы), ее пространство составляет 128 бит, эта величина слишком велика, нам нужно хранить десятки миллиардов, около 33 бит (2 в 33-й степени), поэтому нам нужен механизм чтобы вычислить хэш соответствующего количества цифр, и для экономии памяти нам нужно использовать все типы символов (коды ASCII от 0 до 127) для заполнения вместо HexString, чтобы длину ключа можно было сократить до половина.

Ниже приведен конкретный метод реализации


public static byte [] getBucketId(byte [] key, Integer bit) {

    MessageDigest mdInst = MessageDigest.getInstance("MD5");

    mdInst.update(key);

    byte [] md = mdInst.digest();

    byte [] r = new byte[(bit-1)/7 + 1];// 因为一个字节中只有7位能够表示成单字符,ascii码是7位

    int a = (int) Math.pow(2, bit%7)-2;

    md[r.length-1] = (byte) (md[r.length-1] & a);

    System.arraycopy(md, 0, r, 0, r.length);

    for(int i=0;i<r.length;i++) {

    if(r[i]<0) r[i] &= 127;

    }

    return r;

}

Бит параметра определяет размер конечного пространства BucketId, а набор размера пространства представляет собой дискретное значение целой степени числа 2. Вот объяснение, почему в байте доступно только 7 бит, потому что Redis должен быть ASCII (0 ~ 127) при хранении ключей, а не массивов байтов.Если мы планируем десятки миллиардов хранилищ и планируем разделить 10 kv на ведро, то нам нужно только 2^30=1073741824 ведра, что является количеством конечных ключей..

5.3 Уменьшить фрагментацию

Основная причина фрагментации заключается в том, что память не может быть выровнена, а память не может быть перераспределена после удаления с истекшим сроком действия.С помощью описанного выше метода мы можем хранить метки населения и данные сопоставления указанным выше способом.Преимущество этого заключается в том, что ключ Redis имеет одинаковую длину.. Кроме того, мы еще и оптимизировали ключ в хэшмапе, перехватывая последние шесть цифр куки или deviceid в качестве ключа, что также может обеспечить выравнивание памяти.Теоретически вероятность конфликта есть, но вероятность того же суффикс в том же сегменте одинаков Очень низкий (представьте, что идентификатор представляет собой почти случайную строку, вероятность случайных 10 суффиксов идентификатора, состоящих из более длинных символов * количество выборок сегмента = ожидаемое значение конфликта

Кроме того, есть очень низкий, но эффективный способ уменьшить фрагментацию, перезапустить слейв, а затем принудительно переключить мастер-слейв при отказе, что эквивалентно фрагментации памяти, организованной мастером.

Рекомендуется выделение памяти Google-tcmalloc, facebook-jemalloc, что может уменьшить фрагментацию памяти и потребление памяти, когда значение невелико. Некоторые люди измерили большое значение, но libc более экономична..