В последнее время в браузере Chrome есть ошибка, поэтому я планирую повторно реализовать эту функцию.
Первоначальная реализация очень проста: каждый раз, когда вы нажимаете на страницу сведений о статье, внешний интерфейс отправляет запрос GET.articles/id
Получить подробную информацию о статье. В это время количество просмотров этой статьи будет +1, а затем сохранено в базе данных.
Эта реализация могла бы добиться этой функции, но позже я обнаружил, что допустил роковую ошибку: запись данных производилась в бизнес-логике GET-запроса!
В принципе, GET-запросы должны быть идемпотентными, то есть два одинаковых GET-запроса одновременно за короткий промежуток времени должны возвращать одинаковый результат. И моя первоначальная реализация нарушила идемпотентность GET-запросов.
Как это бывает, в браузере Chrome страница сведений о моей статье отправляет два запроса GET. Предполагается, что это ошибка между браузером Chrome и рендерингом сервера nuxt, и конкретная причина еще не установлена.
Но в любом случае бэкенд должен уметь избегать таких багов, даже если пользователь запрашивает два и более раза за короткий промежуток времени, счетчик просмотров страниц должен увеличиваться только один раз.
Поскольку я недавно узнал о высоком уровне параллелизма, я также рассмотрю, как разработать систему подсчета просмотров страниц с высоким уровнем параллелизма?
Сначала позаботимся о потребностях.
необходимость
- Пользователи могут быть анонимными и не должны входить в систему
- Всякий раз, когда пользователь нажимает на страницу сведений о статье, количество просмотров этой статьи должно быть +1.
- Пользователи должны иметь возможность видеть сразу после просмотра собственных +1 кликов Обратная связь
- Данные о просмотрах страниц хранятся в Mysql и ElasticSearch, и в конечном итоге они должны быть непротиворечивыми (строгая согласованность не требуется).
- Автор может отредактировать статью в фоновом режиме, а затем сохранить статью. Если в этот период наблюдается увеличение просмотров страниц, статья не должна быть перезаписана при сохранении статьи.
- Запрос пользователя должен быть дедуплицирован на стороне сервера, чтобы пользователь не мог постоянно обновлять или использовать поисковый робот для постоянного запроса API (рекомендуется передавать IP)
- Для фильтрации запросов краулеров Baidu и Google (судя по заголовку User-Agent, можно не делать это первым)
- Для достижения высокой производительности функция «просмотр списка наиболее просматриваемых статей».
- Максимально оптимизируйте производительность, чтобы удовлетворить высокие требования к параллельной работе нескольких пользователей.
Идеи дизайна
Если вы хотите обеспечить высокий уровень параллелизма, сначала рассмотрите возможность использования асинхронности и кэширования. Поэтому рассмотрите решение с использованием многопоточности плюс Redis.
Процесс запроса:
- Пользователь нажимает на страницу сведений о статье
- Передняя часть отправляет
PUT
просить/articles/{id:\\d+}/view
. - Серверная часть, использующая пул асинхронных потоков для выполнения задачи, немедленно возвращается на передний план
200
отклик. - передняя часть получает
200
Сразу после ответа количество просмотров текущей статьи равно +1, что соответствует требованию 3.
Основная логика бэкенда:
Основная идея серверной части состоит в том, чтобы временно поместить увеличенные просмотры страниц (при условии, что статья имеет номер n) в Redis, а затем через регулярные промежутки времени обновить их в базе данных Mysql и хранилище ElasticSearch, чтобы количество просмотров страниц этого статьи находятся на существующей основе.Добавьте n, а затем обнулите просмотры этой статьи в Redis.
- Бэкэнд сначала определяет, есть ли запись о просмотре этой статьи по текущему IP-адресу в Redis.Этот ключ:
isViewd:articleId:ip
.如果有,就说明之前浏览过,就什么也不做,直接返回。如果没有,就加上这个key。时间可以设置为1小时过期,防止占用过多内存。这里使用Redis的stringтип. - Если результат шага 5 отрицательный, то +1 количество просмотров этой статьи в Redis. Этот Redis поддерживает атомарные операции, поэтому не беспокойтесь о проблемах параллелизма. ключ
viewCount:articleId
, а значение — количество просмотров страниц в кэше. После завершения текущая задача потока завершается. Здесь используется Redisstringтип. Эти ключи не должны иметь срока действия. - Получите запланированное задание, например каждые 5 минут, перейдите в Redis, чтобы получить кешированные просмотры страниц, обновите его до базы данных и ElasticSearch после его получения и очистите данные Redis до нуля. Чтобы предотвратить проблемы, вызванные параллелизмом, мы должны получить m здесь и вычесть m из Redis вместо того, чтобы напрямую устанавливать его равным 0.
- В целях экономии памяти следует удалять ненужные ключи.По бизнес-логике, если статью давно не просматривали, то она может быть «старой», и мы можем рассмотреть вопрос об удалении ее ключа в Redis. Таким образом, мы можем записывать следующую временную метку каждый раз, когда операция pageview+1 выполняется в Redis на шаге 6. Таким образом, Redis может использоватьhashТип, одно поле хранит время последней операции, а одно поле хранит просмотры страниц. На шаге 7 мы можем удалить ключи, последнее время работы которых меньше десяти дней назад.
- При сохранении обновленных статей следует обновлять только другие поля, а не поле просмотров страниц. Или снова выполните логику шага 7. Из-за атомарности операций сложения и вычитания Redis здесь не нужно беспокоиться о проблемах параллелизма. Если текущий поток уменьшает количество просмотров статьи на m в Redis, поток задач с синхронизацией должен получить результат после уменьшения m, чтобы данные были согласованными.
- Что касается требования 8, когда параллелизм не особенно велик, мы все равно идем на выборку данных в базе данных и сортируем их по просмотрам страниц в базе данных, но мы можем добавить кеш в приложение, и время кеша должно быть то же, что и в шаге 7. Задание на время такое же, и здесь оно установлено на 5 минут.
Если параллелизм особенно велик, вы можете рассмотреть возможность хранения просмотров страниц не в базе данных, а только в Redis, чтобы можно было получить хранилище просмотров страниц, близкое к реальному времени, и упорядочивание требований 8 также в реальном времени (с использованиемzset), но это может потреблять много ресурсов памяти.
постскриптум
Несмотря на то, что параллелизм и сложность в конце были взвешены, логика просмотра статей на моем личном веб-сайте не полностью соответствовала приведенным выше идеям дизайна, но приведенные выше идеи являются моими мыслями о разработке системы подсчета количества просмотров страниц с высоким количеством одновременных просмотров. шанс в будущем Версия с открытым исходным кодом может быть написана.
Это может быть сложнее в реализации. В зависимости от количества параллелизма в коде будут некоторые различия. Приведенные выше идеи приведены только для справки.