Разработка высококонкурентной системы подсчета просмотров страниц статей

Java

В последнее время в браузере Chrome есть ошибка, поэтому я планирую повторно реализовать эту функцию.

Первоначальная реализация очень проста: каждый раз, когда вы нажимаете на страницу сведений о статье, внешний интерфейс отправляет запрос GET.articles/idПолучить подробную информацию о статье. В это время количество просмотров этой статьи будет +1, а затем сохранено в базе данных.

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

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

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

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

Поскольку я недавно узнал о высоком уровне параллелизма, я также рассмотрю, как разработать систему подсчета просмотров страниц с высоким уровнем параллелизма?

Сначала позаботимся о потребностях.

необходимость

  1. Пользователи могут быть анонимными и не должны входить в систему
  2. Всякий раз, когда пользователь нажимает на страницу сведений о статье, количество просмотров этой статьи должно быть +1.
  3. Пользователи должны иметь возможность видеть сразу после просмотра собственных +1 кликов Обратная связь
  4. Данные о просмотрах страниц хранятся в Mysql и ElasticSearch, и в конечном итоге они должны быть непротиворечивыми (строгая согласованность не требуется).
  5. Автор может отредактировать статью в фоновом режиме, а затем сохранить статью. Если в этот период наблюдается увеличение просмотров страниц, статья не должна быть перезаписана при сохранении статьи.
  6. Запрос пользователя должен быть дедуплицирован на стороне сервера, чтобы пользователь не мог постоянно обновлять или использовать поисковый робот для постоянного запроса API (рекомендуется передавать IP)
  7. Для фильтрации запросов краулеров Baidu и Google (судя по заголовку User-Agent, можно не делать это первым)
  8. Для достижения высокой производительности функция «просмотр списка наиболее просматриваемых статей».
  9. Максимально оптимизируйте производительность, чтобы удовлетворить высокие требования к параллельной работе нескольких пользователей.

Идеи дизайна

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

Процесс запроса:

  1. Пользователь нажимает на страницу сведений о статье
  2. Передняя часть отправляетPUTпросить/articles/{id:\\d+}/view.
  3. Серверная часть, использующая пул асинхронных потоков для выполнения задачи, немедленно возвращается на передний план200отклик.
  4. передняя часть получает200Сразу после ответа количество просмотров текущей статьи равно +1, что соответствует требованию 3.

请求流程.png

Основная логика бэкенда:

Основная идея серверной части состоит в том, чтобы временно поместить увеличенные просмотры страниц (при условии, что статья имеет номер n) в Redis, а затем через регулярные промежутки времени обновить их в базе данных Mysql и хранилище ElasticSearch, чтобы количество просмотров страниц этого статьи находятся на существующей основе.Добавьте n, а затем обнулите просмотры этой статьи в Redis.

  1. Бэкэнд сначала определяет, есть ли запись о просмотре этой статьи по текущему IP-адресу в Redis.Этот ключ:isViewd:articleId:ip.如果有,就说明之前浏览过,就什么也不做,直接返回。如果没有,就加上这个key。时间可以设置为1小时过期,防止占用过多内存。这里使用Redis的stringтип.
  2. Если результат шага 5 отрицательный, то +1 количество просмотров этой статьи в Redis. Этот Redis поддерживает атомарные операции, поэтому не беспокойтесь о проблемах параллелизма. ключviewCount:articleId, а значение — количество просмотров страниц в кэше. После завершения текущая задача потока завершается. Здесь используется Redisstringтип. Эти ключи не должны иметь срока действия.
  3. Получите запланированное задание, например каждые 5 минут, перейдите в Redis, чтобы получить кешированные просмотры страниц, обновите его до базы данных и ElasticSearch после его получения и очистите данные Redis до нуля. Чтобы предотвратить проблемы, вызванные параллелизмом, мы должны получить m здесь и вычесть m из Redis вместо того, чтобы напрямую устанавливать его равным 0.
  4. В целях экономии памяти следует удалять ненужные ключи.По бизнес-логике, если статью давно не просматривали, то она может быть «старой», и мы можем рассмотреть вопрос об удалении ее ключа в Redis. Таким образом, мы можем записывать следующую временную метку каждый раз, когда операция pageview+1 выполняется в Redis на шаге 6. Таким образом, Redis может использоватьhashТип, одно поле хранит время последней операции, а одно поле хранит просмотры страниц. На шаге 7 мы можем удалить ключи, последнее время работы которых меньше десяти дней назад.
  5. При сохранении обновленных статей следует обновлять только другие поля, а не поле просмотров страниц. Или снова выполните логику шага 7. Из-за атомарности операций сложения и вычитания Redis здесь не нужно беспокоиться о проблемах параллелизма. Если текущий поток уменьшает количество просмотров статьи на m в Redis, поток задач с синхронизацией должен получить результат после уменьшения m, чтобы данные были согласованными.
  6. Что касается требования 8, когда параллелизм не особенно велик, мы все равно идем на выборку данных в базе данных и сортируем их по просмотрам страниц в базе данных, но мы можем добавить кеш в приложение, и время кеша должно быть то же, что и в шаге 7. Задание на время такое же, и здесь оно установлено на 5 минут.

Если параллелизм особенно велик, вы можете рассмотреть возможность хранения просмотров страниц не в базе данных, а только в Redis, чтобы можно было получить хранилище просмотров страниц, близкое к реальному времени, и упорядочивание требований 8 также в реальном времени (с использованиемzset), но это может потреблять много ресурсов памяти.

后端逻辑.png

постскриптум

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

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