HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Развитие любого веб-сайта происходит на основе аналитических данных. Одна из часто используемых метрик - количество посетителей страницы. Иногда эту метрику показывают пользователям в интерфейсе. Как лучше реализовать такой счетчик на бэкенде?</p>
1 <p>Развитие любого веб-сайта происходит на основе аналитических данных. Одна из часто используемых метрик - количество посетителей страницы. Иногда эту метрику показывают пользователям в интерфейсе. Как лучше реализовать такой счетчик на бэкенде?</p>
2 <p>Сперва можно попробовать хранить записи о посещаемости в таблице реляционной базы данных. Структура таблицы visits будет следующей: идентификатор с автоинкрементом, абсолютная ссылка на страницу, количество посещений и дата. На связку (url, date) нужно добавить уникальный индекс, чтобы не было дублей в счетчиках.</p>
2 <p>Сперва можно попробовать хранить записи о посещаемости в таблице реляционной базы данных. Структура таблицы visits будет следующей: идентификатор с автоинкрементом, абсолютная ссылка на страницу, количество посещений и дата. На связку (url, date) нужно добавить уникальный индекс, чтобы не было дублей в счетчиках.</p>
3 <p>Когда пользователь открывает страницу, выполняется запрос</p>
3 <p>Когда пользователь открывает страницу, выполняется запрос</p>
4 <p>Такой запрос является атомарным и будет правильно выполняться в конкурентной среде. Если на веб-странице низкая посещаемость, то данное решение сработает. Однако, что если 1000 пользователей зайдут на страницу в один момент времени? Счетчик в базе увеличится на 1000. С консистентностью все хорошо, но что будет с производительностью? Классические базы данных тратят примерно 15% времени на полезную работу непосредственно с запросом. Все остальное время тратится на блокировки и управление транзакциями в конкурентной среде. Из-за блокировок транзакции обновление счетчика будут выполняться последовательно. Если у первого пользователя запрос выполнится за 20 мс, то у второго уже за 40 мс, и так далее. Причем рост задержки ответа не будет линейным. То есть 500-ый пользователь будет ждать не 500 * 20мс = 10 000 мс (10с), а намного больше. Естественно, ни один бэкенд не будет ждать более пары секунд выполнения запроса в БД и просто отвалится по таймауту. Итого, при нагрузке в 1000 одновременных запросов теряется ~90% данных, что естественно недопустимо.</p>
4 <p>Такой запрос является атомарным и будет правильно выполняться в конкурентной среде. Если на веб-странице низкая посещаемость, то данное решение сработает. Однако, что если 1000 пользователей зайдут на страницу в один момент времени? Счетчик в базе увеличится на 1000. С консистентностью все хорошо, но что будет с производительностью? Классические базы данных тратят примерно 15% времени на полезную работу непосредственно с запросом. Все остальное время тратится на блокировки и управление транзакциями в конкурентной среде. Из-за блокировок транзакции обновление счетчика будут выполняться последовательно. Если у первого пользователя запрос выполнится за 20 мс, то у второго уже за 40 мс, и так далее. Причем рост задержки ответа не будет линейным. То есть 500-ый пользователь будет ждать не 500 * 20мс = 10 000 мс (10с), а намного больше. Естественно, ни один бэкенд не будет ждать более пары секунд выполнения запроса в БД и просто отвалится по таймауту. Итого, при нагрузке в 1000 одновременных запросов теряется ~90% данных, что естественно недопустимо.</p>
5 <p>Счетчики - отличный кейс использования Redis, и сейчас разберемся почему.</p>
5 <p>Счетчики - отличный кейс использования Redis, и сейчас разберемся почему.</p>
6 <h3>Инкремент</h3>
6 <h3>Инкремент</h3>
7 <p>Представим, что на платформе необходимо отображать количество онлайн пользователей в режиме реального времени. Для этого можно хранить счетчик по ключу page:{url}:online_count. При подключении нового пользователя выполняется увеличение счетчика на 1 (инкремент) с помощью команды incr:</p>
7 <p>Представим, что на платформе необходимо отображать количество онлайн пользователей в режиме реального времени. Для этого можно хранить счетчик по ключу page:{url}:online_count. При подключении нового пользователя выполняется увеличение счетчика на 1 (инкремент) с помощью команды incr:</p>
8 <p>На каждый запрос возвращается новое значение после увеличения.</p>
8 <p>На каждый запрос возвращается новое значение после увеличения.</p>
9 <p>Также существует возможность увеличить счетчик на N за раз с командой incrby:</p>
9 <p>Также существует возможность увеличить счетчик на N за раз с командой incrby:</p>
10 <h3>Декремент</h3>
10 <h3>Декремент</h3>
11 <p>В определенный момент пользователь закрывает страницу, и необходимо уменьшить счетчик онлайна. Используется команда decr:</p>
11 <p>В определенный момент пользователь закрывает страницу, и необходимо уменьшить счетчик онлайна. Используется команда decr:</p>
12 <p>Как и с увеличением есть команда для уменьшения счетчика на N:</p>
12 <p>Как и с увеличением есть команда для уменьшения счетчика на N:</p>
13 <h3>Взаимодействие со счетчиком</h3>
13 <h3>Взаимодействие со счетчиком</h3>
14 <p>Со счетчиком можно работать так же, как и с обычным ключом: получать значение, устанавливать время жизни, удалять:</p>
14 <p>Со счетчиком можно работать так же, как и с обычным ключом: получать значение, устанавливать время жизни, удалять:</p>
15 <h2>Резюме</h2>
15 <h2>Резюме</h2>
16 <ul><li>в простых проектах онлайн-счетчик можно реализовать с помощью классических РСУБД</li>
16 <ul><li>в простых проектах онлайн-счетчик можно реализовать с помощью классических РСУБД</li>
17 <li>онлайн-счетчики имеют простую структуру данных и небольшое количество идентификаторов (ключей), что отлично ложится на архитектуру хранения в оперативной памяти, в частности в Redis</li>
17 <li>онлайн-счетчики имеют простую структуру данных и небольшое количество идентификаторов (ключей), что отлично ложится на архитектуру хранения в оперативной памяти, в частности в Redis</li>
18 <li>Redis отлично справляется с консистентностью данных в высоконагруженной конкурентной среде, потому что все запросы выполняются атомарно, последовательно и за пару миллисекунд</li>
18 <li>Redis отлично справляется с консистентностью данных в высоконагруженной конкурентной среде, потому что все запросы выполняются атомарно, последовательно и за пару миллисекунд</li>
19 </ul>
19 </ul>