Практическая дорога архитектора

Redis

Старший архитектор Qiniu однажды сказал эту фразу:

Nginx + уровень бизнес-логики + база данных + уровень кеша + очередь сообщений, эта модель может почти адаптироваться к большинству бизнес-сценариев.

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

Я начал использовать кеш 10 лет назад, от локального кеша к распределенному кешу, к многоуровневому кешу, и наступил на многие ямы. Ниже я расскажу о своем понимании кэширования на основе собственной истории использования кэширования.

01 Локальный кеш

1. Кэширование на уровне страницы

Я использую кэширование очень давно, я использовал OSCache примерно в 2010 году, когда он в основном использовался на страницах JSP для реализации кэширования на уровне страниц. Такой псевдокод:

<cache:cache key="foobar" scope="session">   
      some jsp content   
</cache:cache>`

Средний код JSP будет кэшироваться в сеансе с ключом = "foobar", чтобы другие страницы могли совместно использовать это кэшированное содержимое. В сценарии с использованием древней технологии JSP после внедрения OSCache скорость загрузки страницы действительно очень быстро повышается.

Однако с разделением клиентской и серверной части и распространением распределенного кэширования кэширование на уровне страниц на стороне сервера используется редко. Но во внешнем мире кэширование на уровне страниц по-прежнему популярно.

2. Кэширование объектов

Примерно в 2011 году сладкоежка из Open Source China написал много статей о кэшировании. Он упомянул: Китай с открытым исходным кодом может обрабатывать миллионы динамических запросов в день только с одним 4-ядерным сервером 8G благодаря структуре кэширования Ehcache.

Меня это очень увлекает.Простой фреймворк может обеспечить такую ​​автономную производительность, что мне не терпится попробовать. Итак, ссылаясь на пример кода Brother Sweet Potato, я впервые использовал Ehcache в сервисе вывода средств с баланса компании.

Логика также очень проста, то есть кэшировать заказы в статусе успеха или отказа, чтобы в следующий раз, когда вы будете запрашивать, вам не нужно было запрашивать сервис Alipay. Такой псевдокод:

После добавления кеша эффект оптимизации очевиден, и время выполнения задачи сокращается с исходных 40 минут до 5~10 минут.

Приведенный выше пример представляет собой типичный «кеш объектов», который является наиболее распространенным сценарием приложения для локального кэширования. По сравнению с кэшированием страниц, его гранулярность более тонкая и гибкая, и он часто используется для кэширования данных, которые редко изменяются, таких как: глобальная конфигурация, заказы, статус которых выполнен и т. д., для повышения общей скорости запросов.

3. Стратегия обновления

В 2018 году мы с друзьями разработали центр конфигурации.Для того, чтобы клиент мог читать конфигурацию с максимальной скоростью, для локального кеша используется Guava.Общая архитектура показана на следующем рисунке:

Как обновляется локальный кеш? Есть два механизма:

  • Клиент запускает запланированную задачу и извлекает данные из центра конфигурации.

  • При изменении данных в центре конфигурации они будут активно переданы клиенту. Здесь я использовал не websocket, а коммуникационную среду RocketMQ Remoting.

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

▍ механизм часов зоозащитника

При запуске soul-admin записывает все данные в zookeeper, а при последующих изменениях данных постепенно обновляет узлы zookeeper. В то же время soul-web будет отслеживать информацию о конфигурации узла, и как только информация изменится, он обновит локальный кеш.

▍ механизм веб-сокетов

Механизм websocket и zookeeper несколько похож. Когда шлюз и администратор впервые устанавливают соединение через веб-сокет, администратор один раз передаст полный объем данных. Если данные конфигурации изменятся позже, добавочные данные будут активно отправлены. к soul-web через веб-сокет.

▍ http механизм длинного опроса

После того, как http-запрос достигает сервера, он не отвечает немедленно, а использует асинхронный механизм Servlet 3.0 для ответа на данные. При изменении конфигурации сервер один за другим удаляет из очереди длинные запросы на опрос и сообщает, в какой группе данные изменились.После получения ответа шлюз снова запрашивает данные конфигурации группы.

Не знаю, все ли нашли?

  • Режим вытягивания необходим
  • Инкрементальный толчок аналогичен

Длинный опрос — интересная тема, этот режим также используется в потребительской модели RocketMQ, которая близка к реальному времени и может снизить нагрузку на сервер.

02 Распределенный кеш

Что касается распределенного кэширования, memcached и Redis должны быть наиболее часто используемыми вариантами технологий. Я полагаю, что друзья-программисты хорошо знакомы с ним, и здесь я поделюсь двумя случаями.

1. Разумный контроль размера объекта и стратегии чтения

В 2013 году я обслуживал лотерейную компанию, и наш модуль подсчета очков также использовал распределенное кэширование. В то время я столкнулся с частой онлайн-проблемой с Young GC.Проверив с помощью инструмента jstat, я обнаружил, что новое поколение заполняется каждые две секунды.

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

После обнаружения основной причины, как я могу это исправить? В то время у меня не было ясного представления. Итак, я пошел на сайты своих коллег, чтобы изучить, как они достигли той же функции, в том числе: лотерея 360, австралийский Ke.com. Я нашел две вещи:

1. Формат данных очень компактный, на фронтенд возвращаются только нужные данные, а часть данных возвращается в виде массивов

2. Используя веб-сокет, отправьте полный объем данных после входа на страницу и отправьте добавочные данные при изменении данных.

Вернемся к моему вопросу, каково решение в конце концов? В то время формат кеша нашего модуля с результатами в реальном времени представлял собой массив JSON, и каждый элемент массива содержал более 20 пар ключ-значение.В следующем примере JSON я перечислил только 4 свойства.

[{
     "playId":"2399",
     "guestTeamName":"小牛",
     "hostTeamName":"湖人",
     "europe":"123"
 }]

С этой структурой данных, в общем-то, проблем нет. Однако, когда полей более 20 и много игр в день, легко вызвать проблемы при высоких одновременных запросах.

Исходя из периода строительства и соображений риска, мы, наконец, приняли более консервативный план оптимизации:

1) Изменить размер нового поколения, с оригинального 2G на 4G

2) Измените формат кэшированных данных с JSON на массив следующим образом:

[["2399","小牛","湖人","123"]]

После завершения модификации размер кеша уменьшился в среднем примерно с 300k до примерно 80k, значительно снизилась частота YGC, а также стал намного быстрее отклик страницы.

Но через некоторое время загрузка процессора мгновенно возрастет. Видно, что хоть мы и уменьшили размер кеша, чтение больших объектов по-прежнему потребляет много системных ресурсов, в результате чего частота полного GC не низкая.

3) Чтобы полностью решить эту проблему, мы используем более совершенную стратегию чтения кэша.

Мы разделяем кеш на две части, первая часть — полные данные, а вторая часть — инкрементные данные (небольшой объем данных). Страница впервые запрашивает получение полного объема данных. При изменении оценки добавочные данные проталкиваются через веб-сокет.

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

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

2. Запрос списка пейджинга

Кэширование списков — это навык, которым я очень хочу поделиться со всеми вами. Этому я также научился на Open Source China в 2012 году. Позвольте мне взять в качестве примера сценарий «запроса списка блогов».

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

В этой схеме степень детализации кеша относительно велика, и если блог часто обновляется, кеш легко выйдет из строя. Ниже я представляю второй вариант: кэшировать только блог. Процесс примерно такой:

1) начните с запроса к базе данных текущего списка идентификаторов страниц блога, sql, например:

select id from blogs limit 0,10 

2) Получите данные кеша, соответствующие списку идентификаторов блога, из кеша партиями и запишите идентификаторы блогов, которые не попали. Если список идентификаторов, который не попал, больше 0, снова запросите базу данных и поместите ее в кеш.SQL аналогичен:

select id from blogs where id in (noHitId1, noHitId2)

3) Сохраните некэшированный объект блога в кеше

4) Вернуть список объектов блога

Теоретически, если кеш прогрет, простой запрос к базе данных и пакет выборок из кеша могут вернуть все данные. Кроме того, как добиться пакетного получения кеша?

  • Локальный кеш: очень высокая производительность, только для цикла
  • memcached: используйте команду mget
  • Redis: если структура кэшированного объекта проста, используйте команды mget и hmget; если структура сложная, рассмотрите возможность использования режима pipleline и lua script.

Первое решение подходит для сценариев, в которых редко меняются данные, такие как списки лидеров, новости главной страницы и т. д.

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

03 Многоуровневый кеш

Прежде всего, почему вы хотите использовать многоуровневое кэширование?

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

Есть поговорка хорошо,Чем ближе кэш к пользователю, тем он эффективнее!

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

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

Процесс чтения кэша выглядит следующим образом:

1. Когда бизнес-шлюз только запущен, в локальном кеше нет данных, прочитайте кеш Redis, если в кеше Redis нет данных, вызовите службу гида по покупкам через RPC, чтобы прочитать данные, а затем запишите данные в локальный кеш и Redis, если кеш Redis не пуст, то кэшированные данные будут записаны в локальный кеш.

2. Поскольку локальный кеш был предварительно прогрет на шаге 1, последующие запросы напрямую считывают локальный кеш и возвращают его клиенту.

3. Guava настроен с механизмом обновления, который вызывает пользовательский пул потоков LoadingCache (максимум 5 потоков, 5 основных потоков) через регулярные промежутки времени для синхронизации данных с локальным кешем и Redis для службы гида по покупкам.

После оптимизации производительность очень хорошая, а среднее время составляет около 5 мс. Сначала я думал, что вероятность проблем очень мала, но однажды ночью я вдруг обнаружил, что данные, отображаемые на главной странице приложения, иногда совпадают, а иногда различаются.

То есть: хотя поток LoadingCache вызывает интерфейс для обновления информации о кеше, данные в локальном кеше каждого сервера не полностью согласованы. Указано два очень важных момента:

1. Ленивая загрузка по-прежнему может вызывать несогласованность данных на нескольких машинах.

2. Количество пулов потоков LoadingCache настроено неправильно, что приводит к накоплению потоков.

В конечном итоге наше решение было таким:

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

2. Надлежащим образом увеличьте параметры пула потоков LoadigCache и закопайте точки в пуле потоков, чтобы контролировать использование пула потоков.Когда поток занят, может быть выдано предупреждение, а затем параметры пула потоков могут быть динамически изменены.

напиши в конце

Кэширование — очень важное техническое средство. Если вы сможете освоить его вглубь от принципа до практики, это должно быть самым приятным занятием для технических специалистов.

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

Я думаю, что я должен общаться со своими друзьями, как систематически изучать новую технологию.

  • Выберите классическую книгу по технологии, чтобы понять основные концепции
  • Создайте контекст знаний для технологии
  • Единство знаний и действий, практика в производственной среде или создание собственных колес
  • Продолжайте анализировать и думать о том, есть ли лучшее решение

В будущем я буду сериализовать некоторый контент, связанный с кешем: в том числе механизм высокой доступности кеша, принцип кодиса и т. д. Пожалуйста, продолжайте обращать внимание.

Что касается кэширования, если у вас есть собственный опыт или вы хотите узнать о нем больше, оставьте сообщение в области комментариев.


Об авторе: Мастер 985, бывший инженер Amazon, ныне технический директор 58 Zhuanzhuan.

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